sui_verifier_latest/
one_time_witness_verifier.rs

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