From 32cb38e0d7e63f8701536ce9c495d6a3841dd8c2 Mon Sep 17 00:00:00 2001 From: miker83z Date: Thu, 4 Sep 2025 22:30:22 +0200 Subject: [PATCH 1/2] feat(verifier): create an auth info based on a authenticate one time witness --- account_auth_example/Move.toml | 13 ++ .../sources/account_auth_example.move | 7 + .../tests/account_auth_example_test.move | 20 +++ .../iota-framework/sources/account.move | 34 ++++- .../iota-framework/sources/types.move | 3 + .../move-stdlib/sources/type_name.move | 36 +++++ crates/iota-protocol-config/src/lib.rs | 14 ++ .../latest/iota-move-natives/src/lib.rs | 20 +++ .../latest/iota-move-natives/src/types.rs | 96 +++++++++++++ .../authenticate_one_time_witness_verifier.rs | 131 ++++++++++++++++++ .../latest/iota-verifier/src/lib.rs | 1 + .../src/one_time_witness_verifier.rs | 4 +- .../latest/iota-verifier/src/verifier.rs | 7 +- 13 files changed, 380 insertions(+), 6 deletions(-) create mode 100644 account_auth_example/Move.toml create mode 100644 account_auth_example/sources/account_auth_example.move create mode 100644 account_auth_example/tests/account_auth_example_test.move create mode 100644 iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs diff --git a/account_auth_example/Move.toml b/account_auth_example/Move.toml new file mode 100644 index 00000000000..b3920787e74 --- /dev/null +++ b/account_auth_example/Move.toml @@ -0,0 +1,13 @@ +[package] +name = "account_auth__example" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[dependencies] +Iota = { local = "../crates/iota-framework/packages/iota-framework" } + +[addresses] +account_auth_example = "0x0" + +[dev-dependencies] + +[dev-addresses] diff --git a/account_auth_example/sources/account_auth_example.move b/account_auth_example/sources/account_auth_example.move new file mode 100644 index 00000000000..7b534f270dd --- /dev/null +++ b/account_auth_example/sources/account_auth_example.move @@ -0,0 +1,7 @@ +module account_auth_example::main_m; + +use iota::auth_context::AuthContext; + +public struct AUTH_ARG_VALUE has drop {} + +public fun arg_value(_val: u8, _auth_ctx: &AuthContext, _ctx: &TxContext) {} diff --git a/account_auth_example/tests/account_auth_example_test.move b/account_auth_example/tests/account_auth_example_test.move new file mode 100644 index 00000000000..942fd1a3fcb --- /dev/null +++ b/account_auth_example/tests/account_auth_example_test.move @@ -0,0 +1,20 @@ +#[test_only] +module account_auth_example::account_auth_example_tests; + +use account_auth_example::main_m::AUTH_ARG_VALUE; +use iota::account::create_auth_info_v1_fotw; + +public struct NOT_AOTW has drop {} + +// WON'T BUILD +// public struct AUTH_FAIL has drop {} + +#[test] +fun aotw_success() { + create_auth_info_v1_fotw(); +} + +#[test, expected_failure] +fun aotw_fail() { + create_auth_info_v1_fotw(); +} diff --git a/crates/iota-framework/packages/iota-framework/sources/account.move b/crates/iota-framework/packages/iota-framework/sources/account.move index 29e7d724c2a..5f417d0d892 100644 --- a/crates/iota-framework/packages/iota-framework/sources/account.move +++ b/crates/iota-framework/packages/iota-framework/sources/account.move @@ -3,12 +3,24 @@ module iota::account; +use iota::address::from_ascii_bytes; +use iota::object::id_from_address; +use iota::types; use std::ascii; +use std::type_name; + +/// Error code for non-OTW structures during the authenticator info creation. +const ENotAuthenticateOneTimeWitness: u64 = 0; +/// Error code empty function names during the authenticator info creation. +const EAuthFnNameIsEmpty: u64 = 1; /// Dynamic field key, where the system will look for a potential /// authenticate function. const AUTHENTICATOR_DF_NAME: vector = b"IOTA_AUTHENTICATION"; +// The length of the prefix used for AOTW +const AOTW_PREFIX_LEN: u64 = 5; //AUTH_ + #[allow(unused_field)] public struct AuthenticatorInfoV1 has copy, drop, store { package: ID, @@ -51,6 +63,26 @@ public fun authenticator_df_name(): vector { AUTHENTICATOR_DF_NAME } +public fun create_auth_info_v1_fotw(): AuthenticatorInfoV1 { + // Verify that the type is an AOTW + assert!(types::is_authenticate_one_time_witness(), ENotAuthenticateOneTimeWitness); + + let type_name = type_name::get_with_original_ids(); + let package = id_from_address(from_ascii_bytes(type_name.get_address().as_bytes())); + let module_name = type_name.get_module(); + let struct_name = type_name.get_struct(); + + // Remove the AOTW prefix and convert to lowercase + let function_name = struct_name.substring(AOTW_PREFIX_LEN, struct_name.length()).to_lowercase(); + assert!(!function_name.is_empty(), EAuthFnNameIsEmpty); + + AuthenticatorInfoV1 { + package, + module_name, + function_name, + } +} + /// Creates an `AuthenticatorInfoV1` instance for testing, skipping validation. #[test_only] public fun create_auth_info_v1_for_testing( @@ -58,5 +90,5 @@ public fun create_auth_info_v1_for_testing( module_name: ascii::String, function_name: ascii::String, ): AuthenticatorInfoV1 { - AuthenticatorInfoV1{ package: package.to_id(), module_name, function_name } + AuthenticatorInfoV1 { package: package.to_id(), module_name, function_name } } diff --git a/crates/iota-framework/packages/iota-framework/sources/types.move b/crates/iota-framework/packages/iota-framework/sources/types.move index 3b8dcf6e698..590c97b7241 100644 --- a/crates/iota-framework/packages/iota-framework/sources/types.move +++ b/crates/iota-framework/packages/iota-framework/sources/types.move @@ -10,3 +10,6 @@ module iota::types; /// Tests if the argument type is a one-time witness, that is a type with only one instantiation /// across the entire code base. public native fun is_one_time_witness(_: &T): bool; + +/// Tests if the argument type is an authenticate one-time witness. +public native fun is_authenticate_one_time_witness(): bool; diff --git a/crates/iota-framework/packages/move-stdlib/sources/type_name.move b/crates/iota-framework/packages/move-stdlib/sources/type_name.move index 3c47be9ad1d..695244bc23b 100644 --- a/crates/iota-framework/packages/move-stdlib/sources/type_name.move +++ b/crates/iota-framework/packages/move-stdlib/sources/type_name.move @@ -11,6 +11,9 @@ use std::ascii::{Self, String}; /// ASCII Character code for the `:` (colon) symbol. const ASCII_COLON: u8 = 58; +/// ASCII Character code for the `:` (less-than) symbol. +const ASCII_LESS: u8 = 60; + /// ASCII Character code for the `v` (lowercase v) symbol. const ASCII_V: u8 = 118; /// ASCII Character code for the `e` (lowercase e) symbol. @@ -122,6 +125,39 @@ public fun get_module(self: &TypeName): String { ascii::string(module_name) } +/// Get name of the struct. +/// Aborts if given a primitive type. +public fun get_struct(self: &TypeName): String { + assert!(!self.is_primitive(), ENonModuleType); + + // Starts after address and a double colon: `::` + let mut i = address::length() * 2 + 2; + let str_bytes = self.name.as_bytes(); + let colon = ASCII_COLON; + // Find the module name by reaching the first colon: `:` + loop { + i = i + 1; + if (&str_bytes[i] == &colon) break + }; + // Skip the second colon: `:` + i = i + 1; + // Finally, read the struct name up to a less-than sign `<` or up to the end of the string + let str_bytes_len = str_bytes.length(); + let mut struct_name = vector[]; + let less_than = ASCII_LESS; + loop { + let char = &str_bytes[i]; + if (char != &less_than) { + struct_name.push_back(*char); + i = i + 1; + if (i < str_bytes_len) continue + }; + break + }; + + ascii::string(struct_name) +} + /// Convert `self` into its inner String public fun into_string(self: TypeName): String { self.name diff --git a/crates/iota-protocol-config/src/lib.rs b/crates/iota-protocol-config/src/lib.rs index d7de96b5d73..7b4ae7cf88b 100644 --- a/crates/iota-protocol-config/src/lib.rs +++ b/crates/iota-protocol-config/src/lib.rs @@ -864,6 +864,11 @@ pub struct ProtocolConfig { types_is_one_time_witness_cost_base: Option, types_is_one_time_witness_type_tag_cost_per_byte: Option, types_is_one_time_witness_type_cost_per_byte: Option, + // Cost params for the Move native function `is_authenticate_one_time_witness(_: &T): + // bool` + types_is_authenticate_one_time_witness_cost_base: Option, + types_is_authenticate_one_time_witness_type_tag_cost_per_byte: Option, + types_is_authenticate_one_time_witness_type_cost_per_byte: Option, // Validator // Cost params for the Move native function `validate_metadata_bcs(metadata: vector)` @@ -1660,6 +1665,11 @@ impl ProtocolConfig { types_is_one_time_witness_cost_base: Some(52), types_is_one_time_witness_type_tag_cost_per_byte: Some(2), types_is_one_time_witness_type_cost_per_byte: Some(2), + // Cost params for the Move native function `is_authenticate_one_time_witness(_: &T): bool` + types_is_authenticate_one_time_witness_cost_base: None, + types_is_authenticate_one_time_witness_type_tag_cost_per_byte: None, + types_is_authenticate_one_time_witness_type_cost_per_byte: None, // `validator` module // Cost params for the Move native function `validate_metadata_bcs(metadata: @@ -2151,6 +2161,10 @@ impl ProtocolConfig { // === Native Function Costs === // `account` module cfg.check_auth_info_v1_cost_base = Some(1000); + + cfg.types_is_authenticate_one_time_witness_cost_base = Some(52); + cfg.types_is_authenticate_one_time_witness_type_tag_cost_per_byte = Some(2); + cfg.types_is_authenticate_one_time_witness_type_cost_per_byte = Some(2); } } // Use this template when making changes: diff --git a/iota-execution/latest/iota-move-natives/src/lib.rs b/iota-execution/latest/iota-move-natives/src/lib.rs index ee763f16868..af9c5f34c43 100644 --- a/iota-execution/latest/iota-move-natives/src/lib.rs +++ b/iota-execution/latest/iota-move-natives/src/lib.rs @@ -74,6 +74,7 @@ use crate::{ poseidon::PoseidonBN254CostParams, zklogin::{self, CheckZkloginIdCostParams, CheckZkloginIssuerCostParams}, }, + types::TypesIsAuthenticateOneTimeWitnessCostParams, }; mod account; @@ -132,6 +133,8 @@ pub struct NativesCostTable { // Type pub type_is_one_time_witness_cost_params: TypesIsOneTimeWitnessCostParams, + pub type_is_authenticate_one_time_witness_cost_params: + TypesIsAuthenticateOneTimeWitnessCostParams, // Validator pub validator_validate_metadata_bcs_cost_params: ValidatorValidateMetadataBcsCostParams, @@ -374,6 +377,18 @@ impl NativesCostTable { .types_is_one_time_witness_type_cost_per_byte() .into(), }, + type_is_authenticate_one_time_witness_cost_params: + TypesIsAuthenticateOneTimeWitnessCostParams { + types_is_authenticate_one_time_witness_cost_base: protocol_config + .types_is_authenticate_one_time_witness_cost_base() + .into(), + types_is_authenticate_one_time_witness_type_tag_cost_per_byte: protocol_config + .types_is_authenticate_one_time_witness_type_tag_cost_per_byte() + .into(), + types_is_authenticate_one_time_witness_type_cost_per_byte: protocol_config + .types_is_authenticate_one_time_witness_type_cost_per_byte() + .into(), + }, validator_validate_metadata_bcs_cost_params: ValidatorValidateMetadataBcsCostParams { validator_validate_metadata_cost_base: protocol_config .validator_validate_metadata_cost_base() @@ -1043,6 +1058,11 @@ pub fn all_natives(silent: bool, protocol_config: &ProtocolConfig) -> NativeFunc "is_one_time_witness", make_native!(types::is_one_time_witness), ), + ( + "types", + "is_authenticate_one_time_witness", + make_native!(types::is_authenticate_one_time_witness), + ), ("test_utils", "destroy", make_native!(test_utils::destroy)), ( "test_utils", diff --git a/iota-execution/latest/iota-move-natives/src/types.rs b/iota-execution/latest/iota-move-natives/src/types.rs index 61d3b0e48ac..e2ba3bc999d 100644 --- a/iota-execution/latest/iota-move-natives/src/types.rs +++ b/iota-execution/latest/iota-move-natives/src/types.rs @@ -18,6 +18,8 @@ use smallvec::smallvec; use crate::NativesCostTable; +pub const AOTW_PREFIX: &str = "AUTH_"; // authenticate one-time witness prefix + pub(crate) fn is_otw_struct( struct_layout: &MoveStructLayout, type_tag: &TypeTag, @@ -42,6 +44,30 @@ pub(crate) fn is_otw_struct( ) } +pub(crate) fn is_aotw_struct( + struct_layout: &MoveStructLayout, + type_tag: &TypeTag, + hardened_check: bool, +) -> bool { + let has_one_bool_field = matches!(struct_layout.0.as_slice(), [MoveTypeLayout::Bool]); + + // If a struct type starts with AOTW_PREFIX as substring of the name, and it has + // a single field of type bool, it means that it's a authenticate one-time + // witness type. The remaining properties of an authenticate one-time + // witness type are checked in the authenticate_one_time_witness_verifier pass + // in the IOTA bytecode verifier (a type with this prefix and with a single + // bool field that does not have all the remaining properties of a one-time + // witness type will cause a verifier error). + matches!( + type_tag, + TypeTag::Struct(struct_tag) if + has_one_bool_field && + struct_tag.name.to_string().get(0..AOTW_PREFIX.len()) == Some(AOTW_PREFIX) && + // hardened check ==> no generic types + (!hardened_check || struct_tag.type_params.is_empty()) + ) +} + #[derive(Clone)] pub struct TypesIsOneTimeWitnessCostParams { pub types_is_one_time_witness_cost_base: InternalGas, @@ -107,3 +133,73 @@ pub fn is_one_time_witness( Ok(NativeResult::ok(cost, smallvec![Value::bool(is_otw)])) } + +#[derive(Clone)] +pub struct TypesIsAuthenticateOneTimeWitnessCostParams { + pub types_is_authenticate_one_time_witness_cost_base: InternalGas, + pub types_is_authenticate_one_time_witness_type_tag_cost_per_byte: InternalGas, + pub types_is_authenticate_one_time_witness_type_cost_per_byte: InternalGas, +} +/// **************************************************************************** +/// ********************* native fun is_authenticate_one_time_witness +/// Implementation of the Move native function +/// `is_authenticate_one_time_witness(_: &T): bool` gas cost: +/// types_is_authenticate_one_time_witness_cost_base | base cost as this can be +/// expensive oper +/// + types_is_authenticate_one_time_witness_type_tag_cost_per_byte +/// * type_tag.size() | cost per byte of converting type +/// to type tag +/// + types_is_authenticate_one_time_witness_type_cost_per_byte * +/// ty.size() | cost per byte of converting type to type layout +/// **************************************************************************** +/// ******************* +pub fn is_authenticate_one_time_witness( + context: &mut NativeContext, + mut ty_args: Vec, + args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.len() == 1); + debug_assert!(args.len() == 0); + + let type_is_authenticate_one_time_witness_cost_params = context + .extensions_mut() + .get::() + .type_is_authenticate_one_time_witness_cost_params + .clone(); + + native_charge_gas_early_exit!( + context, + type_is_authenticate_one_time_witness_cost_params + .types_is_authenticate_one_time_witness_cost_base + ); + + // unwrap safe because the interface of native function guarantees it. + let ty = ty_args.pop().unwrap(); + + native_charge_gas_early_exit!( + context, + type_is_authenticate_one_time_witness_cost_params + .types_is_authenticate_one_time_witness_type_cost_per_byte + * u64::from(ty.size()).into() + ); + + let type_tag = context.type_to_type_tag(&ty)?; + native_charge_gas_early_exit!( + context, + type_is_authenticate_one_time_witness_cost_params + .types_is_authenticate_one_time_witness_type_tag_cost_per_byte + * u64::from(type_tag.abstract_size_for_gas_metering()).into() + ); + + let type_layout = context.type_to_type_layout(&ty)?; + + let cost = context.gas_used(); + let Some(MoveTypeLayout::Struct(struct_layout)) = type_layout else { + return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])); + }; + + let hardened_check = context.runtime_limits_config().hardened_otw_check; + let is_aotw = is_aotw_struct(&struct_layout, &type_tag, hardened_check); + + Ok(NativeResult::ok(cost, smallvec![Value::bool(is_aotw)])) +} diff --git a/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs b/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs new file mode 100644 index 00000000000..b969ce8d9c2 --- /dev/null +++ b/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs @@ -0,0 +1,131 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! A module can define a list of authenticate functions that can be used +//! to authenticate an account. This list is private to the module and +//! cannot be modified by other modules. +//! The list is defined as a constant vector of vector where each inner +//! vector is the UTF-8 bytes of the function name. +//! +//! The authenticate functions must be defined in the same module as the +//! constant. +//! The module's `init` function must call +//! `iota::account::publish_authenticate_registry` exactly once, passing the +//! constant as the argument. The `publish_authenticate_registry` function is +//! responsible for registering the authenticate functions and is defined in the +//! `iota::account`. +use iota_types::{ + Identifier, + error::ExecutionError, + move_package::{FnInfoMap, is_test_fun}, +}; +use move_binary_format::file_format::{CompiledModule, DatatypeHandle, SignatureToken}; +use move_core_types::{ident_str, identifier::IdentStr}; + +use crate::{ + account_auth_verifier::verify_authenticate_func, + one_time_witness_verifier::{verify_no_instantiations, verify_one_time_witness}, + verification_failure, +}; + +pub const AOTW_PREFIX: &str = "AUTH_"; // authenticate one-time witness prefix + +pub const ACCOUNT_MODULE: &IdentStr = ident_str!("account"); +pub const PUBLISH_AUTHENTICATE_REGISTRY_FN_NAME: &IdentStr = + ident_str!("publish_authenticate_registry"); + +/// Checks if the module conforms to the authenticate functions rules only if it +/// has a call instruction to the `0x2::account::publish_authenticate_registry` +/// function within the `init` function. +/// +/// If the module does not have such call instruction, then it is considered to +/// not use authenticate functions and thus the module is considered valid. +/// If the module does have such call instruction, then it must conform to the +/// rules. +pub fn verify_module( + module: &CompiledModule, + fn_info_map: &FnInfoMap, +) -> Result<(), ExecutionError> { + let struct_defs = &module.struct_defs; + let mut authenticate_one_time_witness_candidates = vec![]; + // find structs that can potentially represent a authenticate one-time witness + // type + for def in struct_defs { + let struct_handle = module.datatype_handle_at(def.struct_handle); + let struct_name = module.identifier_at(struct_handle.name).as_str(); + + // check if the struct name starts with the AOTW_PREFIX + if struct_name.starts_with(AOTW_PREFIX) { + if let Ok(field_count) = def.declared_field_count() { + // checks if the struct is non-native (and if it isn't then that's why unwrap + // below is safe) + if field_count == 1 && def.field(0).unwrap().signature.0 == SignatureToken::Bool { + // a single boolean field means that we found a authenticate one-time witness + // candidate - make sure that the remaining properties hold + verify_authenticate_one_time_witness(module, struct_name, struct_handle) + .map_err(verification_failure)?; + // if we reached this point, it means we have a legitimate one-time witness type + // candidate and we have to make sure that both the init function's signature + // reflects this and that this type is not instantiated in any authenticate of + // the module + + authenticate_one_time_witness_candidates.push((struct_name, def)); + } + } + } + } + // If authenticate_one_time_witness_candidates is not empty, then verify that + // there are not authenticate one-time witness type instantiations in any of + // the module's functions + if authenticate_one_time_witness_candidates.is_empty() { + // no authenticate one-time witness type candidates found - nothing more to + // verify + return Ok(()); + } + for fn_def in &module.function_defs { + let fn_handle = module.function_handle_at(fn_def.function); + let fn_name = module.identifier_at(fn_handle.name); + for &(candidate_name, def) in authenticate_one_time_witness_candidates.iter() { + // only verify lack of authenticate one-time witness type instantiations if we + // have a one-time witness type candidate and if instantiation does + // not happen in test code + if !is_test_fun(fn_name, module, fn_info_map) { + verify_no_instantiations(module, fn_def, candidate_name, def) + .map_err(verification_failure)?; + } + } + } + + Ok(()) +} + +// Verifies all required properties of a one-time witness type candidate. +// authenticate one-time witness type name must be the same as a capitalized +// authenticate function name +fn verify_authenticate_one_time_witness( + module: &CompiledModule, + candidate_full_name: &str, + candidate_handle: &DatatypeHandle, +) -> Result<(), String> { + verify_one_time_witness(module, candidate_full_name, candidate_handle) + .map_err(|e| format!("function {}", e))?; + + // check that the authenticate OTW name is the same as an authenticate function + if let Ok(candidate_func_name_ident) = + Identifier::new(&*candidate_full_name[AOTW_PREFIX.len()..].to_ascii_lowercase()) + { + verify_authenticate_func(module, candidate_func_name_ident.clone()).map_err(|e| { + format!( + "authenticate function '{}' does not conform to the rules: {}", + candidate_func_name_ident, e + ) + })? + } else { + return Err(format!( + "Expected the candidate function name to be a valid identifier: {:?}", + candidate_full_name + )); + }; + + Ok(()) +} diff --git a/iota-execution/latest/iota-verifier/src/lib.rs b/iota-execution/latest/iota-verifier/src/lib.rs index bdcaabee6f5..7a7b1d31f9f 100644 --- a/iota-execution/latest/iota-verifier/src/lib.rs +++ b/iota-execution/latest/iota-verifier/src/lib.rs @@ -5,6 +5,7 @@ pub mod verifier; pub mod account_auth_verifier; +pub mod authenticate_one_time_witness_verifier; pub mod entry_points_verifier; pub mod global_storage_access_verifier; pub mod id_leak_verifier; diff --git a/iota-execution/latest/iota-verifier/src/one_time_witness_verifier.rs b/iota-execution/latest/iota-verifier/src/one_time_witness_verifier.rs index 7af60c97072..86a04cee2cc 100644 --- a/iota-execution/latest/iota-verifier/src/one_time_witness_verifier.rs +++ b/iota-execution/latest/iota-verifier/src/one_time_witness_verifier.rs @@ -114,7 +114,7 @@ pub fn verify_module( // Verifies all required properties of a one-time witness type candidate (that // is a type whose name is the same as the name of a module but capitalized) -fn verify_one_time_witness( +pub fn verify_one_time_witness( module: &CompiledModule, candidate_name: &str, candidate_handle: &DatatypeHandle, @@ -201,7 +201,7 @@ fn verify_init_single_param( /// Checks if this module function does not contain instantiation of the /// one-time witness type -fn verify_no_instantiations( +pub fn verify_no_instantiations( module: &CompiledModule, fn_def: &FunctionDefinition, struct_name: &str, diff --git a/iota-execution/latest/iota-verifier/src/verifier.rs b/iota-execution/latest/iota-verifier/src/verifier.rs index adbb89b2b06..f75065e8871 100644 --- a/iota-execution/latest/iota-verifier/src/verifier.rs +++ b/iota-execution/latest/iota-verifier/src/verifier.rs @@ -9,8 +9,8 @@ use move_binary_format::file_format::CompiledModule; use move_bytecode_verifier_meter::{Meter, dummy::DummyMeter}; use crate::{ - entry_points_verifier, global_storage_access_verifier, id_leak_verifier, - one_time_witness_verifier, private_generics, struct_with_key_verifier, + authenticate_one_time_witness_verifier, entry_points_verifier, global_storage_access_verifier, + id_leak_verifier, one_time_witness_verifier, private_generics, struct_with_key_verifier, }; /// Helper for a "canonical" verification of a module. @@ -24,7 +24,8 @@ pub fn iota_verify_module_metered( id_leak_verifier::verify_module(module, meter)?; private_generics::verify_module(module)?; entry_points_verifier::verify_module(module, fn_info_map)?; - one_time_witness_verifier::verify_module(module, fn_info_map) + one_time_witness_verifier::verify_module(module, fn_info_map)?; + authenticate_one_time_witness_verifier::verify_module(module, fn_info_map) } /// Runs the IOTA verifier and checks if the error counts as an IOTA verifier From 7bf6749cc5df96fda5485530443056adc8d3539f Mon Sep 17 00:00:00 2001 From: miker83z Date: Tue, 23 Sep 2025 17:52:54 +0200 Subject: [PATCH 2/2] fix comments --- .../sources/account_auth_example.move | 10 +++- .../tests/account_auth_example_test.move | 7 +-- .../move-stdlib/sources/type_name.move | 2 +- .../authenticate_one_time_witness_verifier.rs | 46 +++++++++---------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/account_auth_example/sources/account_auth_example.move b/account_auth_example/sources/account_auth_example.move index 7b534f270dd..2e31606a68a 100644 --- a/account_auth_example/sources/account_auth_example.move +++ b/account_auth_example/sources/account_auth_example.move @@ -2,6 +2,12 @@ module account_auth_example::main_m; use iota::auth_context::AuthContext; -public struct AUTH_ARG_VALUE has drop {} +public struct AUTH_SOME_AUTHENTICATE_FN has drop {} -public fun arg_value(_val: u8, _auth_ctx: &AuthContext, _ctx: &TxContext) {} +// WON'T BUILD WITH THESE +// public struct AUTH_FAIL has drop {} +// public struct AUTH_NOT_AUTHENTICATE has drop {} + +public fun some_authenticate_fn(_val: u8, _auth_ctx: &AuthContext, _ctx: &TxContext) {} + +public fun not_authenticate(_val: u8, _ctx: &TxContext) {} diff --git a/account_auth_example/tests/account_auth_example_test.move b/account_auth_example/tests/account_auth_example_test.move index 942fd1a3fcb..b19f2a4de86 100644 --- a/account_auth_example/tests/account_auth_example_test.move +++ b/account_auth_example/tests/account_auth_example_test.move @@ -1,17 +1,14 @@ #[test_only] module account_auth_example::account_auth_example_tests; -use account_auth_example::main_m::AUTH_ARG_VALUE; +use account_auth_example::main_m::AUTH_SOME_AUTHENTICATE_FN; use iota::account::create_auth_info_v1_fotw; public struct NOT_AOTW has drop {} -// WON'T BUILD -// public struct AUTH_FAIL has drop {} - #[test] fun aotw_success() { - create_auth_info_v1_fotw(); + create_auth_info_v1_fotw(); } #[test, expected_failure] diff --git a/crates/iota-framework/packages/move-stdlib/sources/type_name.move b/crates/iota-framework/packages/move-stdlib/sources/type_name.move index 695244bc23b..f3b55fe8c24 100644 --- a/crates/iota-framework/packages/move-stdlib/sources/type_name.move +++ b/crates/iota-framework/packages/move-stdlib/sources/type_name.move @@ -11,7 +11,7 @@ use std::ascii::{Self, String}; /// ASCII Character code for the `:` (colon) symbol. const ASCII_COLON: u8 = 58; -/// ASCII Character code for the `:` (less-than) symbol. +/// ASCII Character code for the `<` (less-than) symbol. const ASCII_LESS: u8 = 60; /// ASCII Character code for the `v` (lowercase v) symbol. diff --git a/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs b/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs index b969ce8d9c2..99d2c829b46 100644 --- a/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs +++ b/iota-execution/latest/iota-verifier/src/authenticate_one_time_witness_verifier.rs @@ -1,26 +1,33 @@ // Copyright (c) 2025 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! A module can define a list of authenticate functions that can be used -//! to authenticate an account. This list is private to the module and -//! cannot be modified by other modules. -//! The list is defined as a constant vector of vector where each inner -//! vector is the UTF-8 bytes of the function name. +//! A module can define a several Authenticate One Time Witnesses (AOTW). An +//! AOTW is a type that is never instantiated, and this property is enforced by +//! the system. +//! We define an authenticate one-time witness type as a struct type that has +//! the name starting with a predefined prefix followed by the name of an +//! authenticate function in capital letters, and possessing certain special +//! properties specified below (please note that by convention, "regular" struct +//! type names are expressed in camel case). +//! In other words, if a module defines a struct type whose name is starting +//! with the predefined AOTW prefix and has no fields, then this type MUST +//! possess these special properties, otherwise the module definition will be +//! considered invalid and will be rejected by the validator: //! -//! The authenticate functions must be defined in the same module as the -//! constant. -//! The module's `init` function must call -//! `iota::account::publish_authenticate_registry` exactly once, passing the -//! constant as the argument. The `publish_authenticate_registry` function is -//! responsible for registering the authenticate functions and is defined in the -//! `iota::account`. +//! - it has a struct name where the prefix is followed by the name of a +//! function in capital letters: +//! - this function MUST be found in the same module; +//! - this function MUST be a valid authenticate function; +//! - it has only one ability: drop +//! - it has only one arbitrarily named field of type boolean or it is empty +//! - its definition does not involve type parameters +//! - it is never instantiated anywhere in its defining module use iota_types::{ Identifier, error::ExecutionError, move_package::{FnInfoMap, is_test_fun}, }; use move_binary_format::file_format::{CompiledModule, DatatypeHandle, SignatureToken}; -use move_core_types::{ident_str, identifier::IdentStr}; use crate::{ account_auth_verifier::verify_authenticate_func, @@ -30,18 +37,7 @@ use crate::{ pub const AOTW_PREFIX: &str = "AUTH_"; // authenticate one-time witness prefix -pub const ACCOUNT_MODULE: &IdentStr = ident_str!("account"); -pub const PUBLISH_AUTHENTICATE_REGISTRY_FN_NAME: &IdentStr = - ident_str!("publish_authenticate_registry"); - -/// Checks if the module conforms to the authenticate functions rules only if it -/// has a call instruction to the `0x2::account::publish_authenticate_registry` -/// function within the `init` function. -/// -/// If the module does not have such call instruction, then it is considered to -/// not use authenticate functions and thus the module is considered valid. -/// If the module does have such call instruction, then it must conform to the -/// rules. +/// Checks if the module conforms to the authenticate one time witness rules. pub fn verify_module( module: &CompiledModule, fn_info_map: &FnInfoMap,