sui_move_natives_latest/crypto/
nitro_attestation.rs1use move_binary_format::errors::{PartialVMError, PartialVMResult};
5use move_core_types::{gas_algebra::InternalGas, vm_status::StatusCode};
6use move_vm_runtime::native_functions::NativeContext;
7use move_vm_types::{
8 loaded_data::runtime_types::Type,
9 natives::function::NativeResult,
10 pop_arg,
11 values::{Struct, Value, Vector, VectorRef, VectorSpecialization},
12};
13use std::collections::{BTreeMap, VecDeque};
14use sui_types::nitro_attestation::{parse_nitro_attestation, verify_nitro_attestation};
15
16use crate::{NativesCostTable, get_extension, object_runtime::ObjectRuntime};
17use move_vm_runtime::native_charge_gas_early_exit;
18
19pub const NOT_SUPPORTED_ERROR: u64 = 0;
20pub const PARSE_ERROR: u64 = 1;
21pub const VERIFY_ERROR: u64 = 2;
22pub const INVALID_PCRS_ERROR: u64 = 3;
23
24#[derive(Clone)]
26pub struct NitroAttestationCostParams {
27 pub parse_base_cost: Option<InternalGas>,
28 pub parse_cost_per_byte: Option<InternalGas>,
29 pub verify_base_cost: Option<InternalGas>,
30 pub verify_cost_per_cert: Option<InternalGas>,
31}
32
33macro_rules! native_charge_gas_early_exit_option {
34 ($native_context:ident, $cost:expr) => {{
35 use move_binary_format::errors::PartialVMError;
36 use move_core_types::vm_status::StatusCode;
37 native_charge_gas_early_exit!(
38 $native_context,
39 $cost.ok_or_else(|| {
40 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
41 .with_message("Gas cost for nitro attestation is missing".to_string())
42 })?
43 );
44 }};
45}
46
47fn is_supported(context: &NativeContext) -> PartialVMResult<bool> {
48 Ok(get_extension!(context, ObjectRuntime)?
49 .protocol_config
50 .enable_nitro_attestation())
51}
52
53fn is_upgraded(context: &NativeContext) -> PartialVMResult<bool> {
54 Ok(get_extension!(context, ObjectRuntime)?
55 .protocol_config
56 .enable_nitro_attestation_upgraded_parsing())
57}
58
59fn is_all_nonzero_pcrs_included(context: &NativeContext) -> PartialVMResult<bool> {
60 Ok(get_extension!(context, ObjectRuntime)?
61 .protocol_config
62 .enable_nitro_attestation_all_nonzero_pcrs_parsing())
63}
64
65pub fn load_nitro_attestation_internal(
66 context: &mut NativeContext,
67 ty_args: Vec<Type>,
68 mut args: VecDeque<Value>,
69) -> PartialVMResult<NativeResult> {
70 debug_assert!(ty_args.is_empty());
71 debug_assert!(args.len() == 2);
72
73 let cost = context.gas_used();
74 if !is_supported(context)? {
75 return Ok(NativeResult::err(cost, NOT_SUPPORTED_ERROR));
76 }
77
78 let current_timestamp = pop_arg!(args, u64);
79 let attestation_ref = pop_arg!(args, VectorRef);
80 let attestation_bytes = attestation_ref.as_bytes_ref();
81
82 let cost_params = get_extension!(context, NativesCostTable)?
83 .nitro_attestation_cost_params
84 .clone();
85
86 native_charge_gas_early_exit_option!(
87 context,
88 cost_params.parse_base_cost.and_then(|base_cost| cost_params
89 .parse_cost_per_byte
90 .map(|per_byte| base_cost + per_byte * (attestation_bytes.len() as u64).into()))
91 );
92
93 match parse_nitro_attestation(
94 &attestation_bytes,
95 is_upgraded(context)?,
96 is_all_nonzero_pcrs_included(context)?,
97 ) {
98 Ok((signature, signed_message, payload)) => {
99 let cert_chain_length = payload.get_cert_chain_length();
100 native_charge_gas_early_exit_option!(
101 context,
102 cost_params
103 .verify_base_cost
104 .and_then(|base_cost| cost_params
105 .verify_cost_per_cert
106 .map(|per_cert| base_cost + per_cert * (cert_chain_length as u64).into()))
107 );
108 match verify_nitro_attestation(&signature, &signed_message, &payload, current_timestamp)
109 {
110 Ok(()) => {
111 let pcrs = if is_upgraded(context)? {
112 to_indexed_struct(payload.pcr_map)?
113 } else {
114 to_indexed_struct_legacy(payload.pcr_vec)?
115 };
116 let result = || {
119 Ok(Value::struct_(Struct::pack(vec![
120 Value::vector_u8(payload.module_id.as_bytes().to_vec()),
121 Value::u64(payload.timestamp),
122 Value::vector_u8(payload.digest.as_bytes().to_vec()),
123 pcrs,
124 to_option_vector_u8(payload.public_key)?,
125 to_option_vector_u8(payload.user_data)?,
126 to_option_vector_u8(payload.nonce)?,
127 ])))
128 };
129 NativeResult::map_partial_vm_result_one(context.gas_used(), result())
130 }
131 Err(_) => Ok(NativeResult::err(context.gas_used(), VERIFY_ERROR)),
132 }
133 }
134 Err(_) => Ok(NativeResult::err(context.gas_used(), PARSE_ERROR)),
135 }
136}
137fn to_option_vector_u8(value: Option<Vec<u8>>) -> PartialVMResult<Value> {
139 match value {
140 Some(vec) => Ok(Value::struct_(Struct::pack(vec![Vector::pack(
142 VectorSpecialization::Container,
143 vec![Value::vector_u8(vec)],
144 )?]))),
145 None => Ok(Value::struct_(Struct::pack(vec![Vector::empty(
147 VectorSpecialization::Container,
148 )?]))),
149 }
150}
151
152fn to_indexed_struct(pcrs: BTreeMap<u8, Vec<u8>>) -> PartialVMResult<Value> {
156 let mut sorted = pcrs.iter().collect::<Vec<_>>();
157 sorted.sort_by_key(|(key, _)| *key);
158 let mut indexed_struct = vec![];
159 for (index, pcr) in sorted.into_iter() {
160 indexed_struct.push(Value::struct_(Struct::pack(vec![
161 Value::u8(*index),
162 Value::vector_u8(pcr.to_vec()),
163 ])));
164 }
165 Vector::pack(VectorSpecialization::Container, indexed_struct)
166}
167
168fn to_indexed_struct_legacy(pcrs: Vec<Vec<u8>>) -> PartialVMResult<Value> {
172 let indices = [0, 1, 2, 3, 4, 8];
173 if pcrs.len() != indices.len() {
174 return Err(PartialVMError::new(StatusCode::ABORTED).with_sub_status(INVALID_PCRS_ERROR));
175 };
176 let mut indexed_struct = vec![];
177 for (index, pcr) in pcrs.iter().enumerate() {
178 indexed_struct.push(Value::struct_(Struct::pack(vec![
179 Value::u8(indices[index]),
180 Value::vector_u8(pcr.to_vec()),
181 ])));
182 }
183 Vector::pack(VectorSpecialization::Container, indexed_struct)
184}