Skip to main content

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::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// Gas related structs and functions.
28#[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        /* is_upgraded */ true,
93        /* include_all_nonzero_pcrs */ true,
94        /* always_include_required_pcrs */ 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                    // Encapsulate as a lambda and call to allow us to capture any `Err` returns.
113                    // Could do this with `and_then` as well if desired.
114                    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}
133// Build an Option<vector<u8>> value
134fn to_option_vector_u8(value: Option<Vec<u8>>) -> PartialVMResult<Value> {
135    match value {
136        // Some(<vector<u8>>) = { vector[ <vector<u8>> ] }
137        Some(vec) => Ok(Value::struct_(Struct::pack(vec![Vector::pack(
138            VectorSpecialization::Container,
139            vec![Value::vector_u8(vec)],
140        )?]))),
141        // None = { vector[ ] }
142        None => Ok(Value::struct_(Struct::pack(vec![Vector::empty(
143            VectorSpecialization::Container,
144        )?]))),
145    }
146}
147
148// Convert a map of index -> PCR to a vector of PCREntry struct with index
149// and value where the indices are [0, 1, 2, 3, 4, 8] since AWS currently
150// supports PCR0, PCR1, PCR2, PCR3, PCR4, PCR8.
151fn 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}