sui_move_natives_latest/crypto/
nitro_attestation.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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// Gas related structs and functions.
25#[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                    // Encapsulate as a lambda and call to allow us to capture any `Err` returns.
117                    // Could do this with `and_then` as well if desired.
118                    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}
137// Build an Option<vector<u8>> value
138fn to_option_vector_u8(value: Option<Vec<u8>>) -> PartialVMResult<Value> {
139    match value {
140        // Some(<vector<u8>>) = { vector[ <vector<u8>> ] }
141        Some(vec) => Ok(Value::struct_(Struct::pack(vec![Vector::pack(
142            VectorSpecialization::Container,
143            vec![Value::vector_u8(vec)],
144        )?]))),
145        // None = { vector[ ] }
146        None => Ok(Value::struct_(Struct::pack(vec![Vector::empty(
147            VectorSpecialization::Container,
148        )?]))),
149    }
150}
151
152// Convert a map of index -> PCR to a vector of PCREntry struct with index
153// and value where the indices are [0, 1, 2, 3, 4, 8] since AWS currently
154// supports PCR0, PCR1, PCR2, PCR3, PCR4, PCR8.
155fn 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
168// Convert a list of PCRs into a vector of PCREntry struct with index and value,
169// where the indices are [0, 1, 2, 3, 4, 8] since AWS currently supports PCR0,
170// PCR1, PCR2, PCR3, PCR4, PCR8.
171fn 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}