sui_move_natives_latest/crypto/
nitro_attestation.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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// Gas related structs and functions.
27#[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                    // Encapsulate as a lambda and call to allow us to capture any `Err` returns.
126                    // Could do this with `and_then` as well if desired.
127                    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}
146// Build an Option<vector<u8>> value
147fn to_option_vector_u8(value: Option<Vec<u8>>) -> PartialVMResult<Value> {
148    match value {
149        // Some(<vector<u8>>) = { vector[ <vector<u8>> ] }
150        Some(vec) => Ok(Value::struct_(Struct::pack(vec![Vector::pack(
151            VectorSpecialization::Container,
152            vec![Value::vector_u8(vec)],
153        )?]))),
154        // None = { vector[ ] }
155        None => Ok(Value::struct_(Struct::pack(vec![Vector::empty(
156            VectorSpecialization::Container,
157        )?]))),
158    }
159}
160
161// Convert a map of index -> PCR to a vector of PCREntry struct with index
162// and value where the indices are [0, 1, 2, 3, 4, 8] since AWS currently
163// supports PCR0, PCR1, PCR2, PCR3, PCR4, PCR8.
164fn 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
177// Convert a list of PCRs into a vector of PCREntry struct with index and value,
178// where the indices are [0, 1, 2, 3, 4, 8] since AWS currently supports PCR0,
179// PCR1, PCR2, PCR3, PCR4, PCR8.
180fn 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}