iota_verifier_latest/
one_time_witness_verifier.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5//! A module can define a one-time witness type, that is a type that is
6//! instantiated only once, and this property is enforced by the system. We
7//! define a one-time witness type as a struct type that has the same name as
8//! the module that defines it but with all the letters capitalized, and
9//! possessing certain special properties specified below (please note that by
10//! convention, "regular" struct type names are expressed in camel case).  In
11//! other words, if a module defines a struct type whose name is the same as the
12//! module name, this type MUST possess these special properties, otherwise the
13//! module definition will be considered invalid and will be rejected by the
14//! validator:
15//!
16//! - it has only one ability: drop
17//! - it has only one arbitrarily named field of type boolean (since Move
18//!   structs cannot be empty)
19//! - its definition does not involve type parameters
20//! - its only instance in existence is passed as an argument to the module
21//!   initializer
22//! - it is never instantiated anywhere in its defining module
23use iota_types::{
24    BRIDGE_ADDRESS, IOTA_FRAMEWORK_ADDRESS,
25    base_types::{TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME},
26    bridge::BRIDGE_SUPPORTED_ASSET,
27    error::ExecutionError,
28    move_package::{FnInfoMap, is_test_fun},
29};
30use move_binary_format::file_format::{
31    Ability, AbilitySet, Bytecode, CompiledModule, DatatypeHandle, FunctionDefinition,
32    FunctionHandle, SignatureToken, StructDefinition,
33};
34use move_core_types::{ident_str, language_storage::ModuleId};
35
36use crate::{INIT_FN_NAME, verification_failure};
37
38pub fn verify_module(
39    module: &CompiledModule,
40    fn_info_map: &FnInfoMap,
41) -> Result<(), ExecutionError> {
42    // When verifying test functions, a check preventing by-hand instantiation of
43    // one-time withess is disabled
44
45    // In IOTA's framework code there is an exception to the one-time witness type
46    // rule - we have an IOTA type in the iota module but it is instantiated
47    // outside of the module initializer (in fact, the module has no
48    // initializer). The reason for it is that the IOTA coin is only instantiated
49    // during genesis. It is easiest to simply special-case this module particularly
50    // that this is framework code and thus deemed correct.
51    let self_id = module.self_id();
52
53    if ModuleId::new(IOTA_FRAMEWORK_ADDRESS, ident_str!("iota").to_owned()) == self_id {
54        return Ok(());
55    }
56
57    if BRIDGE_SUPPORTED_ASSET
58        .iter()
59        .any(|token| ModuleId::new(BRIDGE_ADDRESS, ident_str!(token).to_owned()) == self_id)
60    {
61        return Ok(());
62    }
63
64    let mod_handle = module.module_handle_at(module.self_module_handle_idx);
65    let mod_name = module.identifier_at(mod_handle.name).as_str();
66    let struct_defs = &module.struct_defs;
67    let mut one_time_witness_candidate = None;
68    // find structs that can potentially represent a one-time witness type
69    for def in struct_defs {
70        let struct_handle = module.datatype_handle_at(def.struct_handle);
71        let struct_name = module.identifier_at(struct_handle.name).as_str();
72        if mod_name.to_ascii_uppercase() == struct_name {
73            // one-time witness candidate's type name must be the same as capitalized module
74            // name
75            if let Ok(field_count) = def.declared_field_count() {
76                // checks if the struct is non-native (and if it isn't then that's why unwrap
77                // below is safe)
78                if field_count == 1 && def.field(0).unwrap().signature.0 == SignatureToken::Bool {
79                    // a single boolean field means that we found a one-time witness candidate -
80                    // make sure that the remaining properties hold
81                    verify_one_time_witness(module, struct_name, struct_handle)
82                        .map_err(verification_failure)?;
83                    // if we reached this point, it means we have a legitimate one-time witness type
84                    // candidate and we have to make sure that both the init function's signature
85                    // reflects this and that this type is not instantiated in any function of the
86                    // module
87                    one_time_witness_candidate = Some((struct_name, struct_handle, def));
88                    break; // no reason to look any further
89                }
90            }
91        }
92    }
93    for fn_def in &module.function_defs {
94        let fn_handle = module.function_handle_at(fn_def.function);
95        let fn_name = module.identifier_at(fn_handle.name);
96        if fn_name == INIT_FN_NAME {
97            if let Some((candidate_name, candidate_handle, _)) = one_time_witness_candidate {
98                // only verify if init function conforms to one-time witness type requirements
99                // if we have a one-time witness type candidate
100                verify_init_one_time_witness(module, fn_handle, candidate_name, candidate_handle)
101                    .map_err(verification_failure)?;
102            } else {
103                // if there is no one-time witness type candidate than the init function should
104                // have only one parameter of TxContext type
105                verify_init_single_param(module, fn_handle).map_err(verification_failure)?;
106            }
107        }
108        if let Some((candidate_name, _, def)) = one_time_witness_candidate {
109            // only verify lack of one-time witness type instantiations if we have a
110            // one-time witness type candidate and if instantiation does not
111            // happen in test code
112
113            if !is_test_fun(fn_name, module, fn_info_map) {
114                verify_no_instantiations(module, fn_def, candidate_name, def)
115                    .map_err(verification_failure)?;
116            }
117        }
118    }
119
120    Ok(())
121}
122
123// Verifies all required properties of a one-time witness type candidate (that
124// is a type whose name is the same as the name of a module but capitalized)
125fn verify_one_time_witness(
126    module: &CompiledModule,
127    candidate_name: &str,
128    candidate_handle: &DatatypeHandle,
129) -> Result<(), String> {
130    // must have only one ability: drop
131    let drop_set = AbilitySet::EMPTY | Ability::Drop;
132    let abilities = candidate_handle.abilities;
133    if abilities != drop_set {
134        return Err(format!(
135            "one-time witness type candidate {}::{} must have a single ability: drop",
136            module.self_id(),
137            candidate_name,
138        ));
139    }
140
141    if !candidate_handle.type_parameters.is_empty() {
142        return Err(format!(
143            "one-time witness type candidate {}::{} cannot have type parameters",
144            module.self_id(),
145            candidate_name,
146        ));
147    }
148    Ok(())
149}
150
151/// Checks if this module's `init` function conformant with the one-time witness
152/// type
153fn verify_init_one_time_witness(
154    module: &CompiledModule,
155    fn_handle: &FunctionHandle,
156    candidate_name: &str,
157    candidate_handle: &DatatypeHandle,
158) -> Result<(), String> {
159    let fn_sig = module.signature_at(fn_handle.parameters);
160    if fn_sig.len() != 2 || !is_one_time_witness(module, &fn_sig.0[0], candidate_handle) {
161        // check only the first parameter - the other one is checked in entry_points
162        // verification pass
163        return Err(format!(
164            "init function of a module containing one-time witness type candidate must have \
165             {}::{} as the first parameter (a struct which has no fields or a single field of type \
166             bool)",
167            module.self_id(),
168            candidate_name,
169        ));
170    }
171
172    Ok(())
173}
174
175// Checks if a given SignatureToken represents a one-time witness type struct
176fn is_one_time_witness(
177    view: &CompiledModule,
178    tok: &SignatureToken,
179    candidate_handle: &DatatypeHandle,
180) -> bool {
181    matches!(tok, SignatureToken::Datatype(idx) if view.datatype_handle_at(*idx) == candidate_handle)
182}
183
184/// Checks if this module's `init` function has a single parameter of TxContext
185/// type only
186fn verify_init_single_param(
187    module: &CompiledModule,
188    fn_handle: &FunctionHandle,
189) -> Result<(), String> {
190    let fn_sig = module.signature_at(fn_handle.parameters);
191    if fn_sig.len() != 1 {
192        return Err(format!(
193            "Expected last (and at most second) parameter for {0}::{1} to be &mut {2}::{3}::{4} or \
194             &{2}::{3}::{4}; optional first parameter must be of one-time witness type whose name \
195             is the same as the capitalized module name ({5}::{6}) and which has no fields or a \
196             single field of type bool",
197            module.self_id(),
198            INIT_FN_NAME,
199            IOTA_FRAMEWORK_ADDRESS,
200            TX_CONTEXT_MODULE_NAME,
201            TX_CONTEXT_STRUCT_NAME,
202            module.self_id(),
203            module.self_id().name().as_str().to_uppercase(),
204        ));
205    }
206
207    Ok(())
208}
209
210/// Checks if this module function does not contain instantiation of the
211/// one-time witness type
212fn verify_no_instantiations(
213    module: &CompiledModule,
214    fn_def: &FunctionDefinition,
215    struct_name: &str,
216    struct_def: &StructDefinition,
217) -> Result<(), String> {
218    if fn_def.code.is_none() {
219        return Ok(());
220    }
221    for bcode in &fn_def.code.as_ref().unwrap().code {
222        let struct_def_idx = match bcode {
223            Bytecode::Pack(idx) => idx,
224            _ => continue,
225        };
226        // unwrap is safe below since we know we are getting a struct out of a module
227        // (see definition of struct_def_at)
228        if module.struct_def_at(*struct_def_idx) == struct_def {
229            let fn_handle = module.function_handle_at(fn_def.function);
230            let fn_name = module.identifier_at(fn_handle.name);
231            return Err(format!(
232                "one-time witness type {}::{} is instantiated \
233                         in the {}::{} function and must never be",
234                module.self_id(),
235                struct_name,
236                module.self_id(),
237                fn_name,
238            ));
239        }
240    }
241
242    Ok(())
243}