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