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