Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions account_auth_example/Move.toml
Original file line number Diff line number Diff line change
@@ -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]
13 changes: 13 additions & 0 deletions account_auth_example/sources/account_auth_example.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module account_auth_example::main_m;

use iota::auth_context::AuthContext;

public struct AUTH_SOME_AUTHENTICATE_FN has drop {}

// 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) {}
17 changes: 17 additions & 0 deletions account_auth_example/tests/account_auth_example_test.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[test_only]
module account_auth_example::account_auth_example_tests;

use account_auth_example::main_m::AUTH_SOME_AUTHENTICATE_FN;
use iota::account::create_auth_info_v1_fotw;

public struct NOT_AOTW has drop {}

#[test]
fun aotw_success() {
create_auth_info_v1_fotw<AUTH_SOME_AUTHENTICATE_FN>();
}

#[test, expected_failure]
fun aotw_fail() {
create_auth_info_v1_fotw<NOT_AOTW>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = 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,
Expand Down Expand Up @@ -51,12 +63,32 @@ public fun authenticator_df_name(): vector<u8> {
AUTHENTICATOR_DF_NAME
}

public fun create_auth_info_v1_fotw<AOTW: drop>(): AuthenticatorInfoV1 {
// Verify that the type is an AOTW
assert!(types::is_authenticate_one_time_witness<AOTW>(), ENotAuthenticateOneTimeWitness);

let type_name = type_name::get_with_original_ids<AOTW>();
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(
package: address,
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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: drop>(_: &T): bool;

/// Tests if the argument type is an authenticate one-time witness.
public native fun is_authenticate_one_time_witness<T: drop>(): bool;
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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: `<addr as HEX>::`
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: `<module_name>:`
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
Expand Down
14 changes: 14 additions & 0 deletions crates/iota-protocol-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,11 @@ pub struct ProtocolConfig {
types_is_one_time_witness_cost_base: Option<u64>,
types_is_one_time_witness_type_tag_cost_per_byte: Option<u64>,
types_is_one_time_witness_type_cost_per_byte: Option<u64>,
// Cost params for the Move native function `is_authenticate_one_time_witness<T: drop>(_: &T):
// bool`
types_is_authenticate_one_time_witness_cost_base: Option<u64>,
types_is_authenticate_one_time_witness_type_tag_cost_per_byte: Option<u64>,
types_is_authenticate_one_time_witness_type_cost_per_byte: Option<u64>,

// Validator
// Cost params for the Move native function `validate_metadata_bcs(metadata: vector<u8>)`
Expand Down Expand Up @@ -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:
// drop>(_: &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:
Expand Down Expand Up @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions iota-execution/latest/iota-move-natives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ use crate::{
poseidon::PoseidonBN254CostParams,
zklogin::{self, CheckZkloginIdCostParams, CheckZkloginIssuerCostParams},
},
types::TypesIsAuthenticateOneTimeWitnessCostParams,
};

mod account;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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",
Expand Down
96 changes: 96 additions & 0 deletions iota-execution/latest/iota-move-natives/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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: drop>(_: &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<Type>,
args: VecDeque<Value>,
) -> PartialVMResult<NativeResult> {
debug_assert!(ty_args.len() == 1);
debug_assert!(args.len() == 0);

let type_is_authenticate_one_time_witness_cost_params = context
.extensions_mut()
.get::<NativesCostTable>()
.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)]))
}
Loading