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