iota_move_natives_latest/crypto/
vdf.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::VecDeque;
6
7use fastcrypto_vdf::{
8    class_group::{QuadraticForm, discriminant::DISCRIMINANT_3072},
9    vdf::{VDF, wesolowski::DefaultVDF},
10};
11use move_binary_format::errors::PartialVMResult;
12use move_core_types::{gas_algebra::InternalGas, vm_status::StatusCode};
13use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
14use move_vm_types::{
15    loaded_data::runtime_types::Type,
16    natives::function::{NativeResult, PartialVMError},
17    pop_arg,
18    values::{Value, VectorRef},
19};
20use smallvec::smallvec;
21
22use crate::{NativesCostTable, object_runtime::ObjectRuntime};
23
24pub const INVALID_INPUT_ERROR: u64 = 0;
25pub const NOT_SUPPORTED_ERROR: u64 = 1;
26
27fn is_supported(context: &NativeContext) -> bool {
28    context
29        .extensions()
30        .get::<ObjectRuntime>()
31        .protocol_config
32        .enable_vdf()
33}
34
35#[derive(Clone)]
36pub struct VDFCostParams {
37    pub vdf_verify_cost: Option<InternalGas>,
38    pub hash_to_input_cost: Option<InternalGas>,
39}
40
41/// ****************************************************************************
42/// ********************* native fun vdf_verify_internal
43///
44/// Implementation of the Move native function `vdf::verify_vdf_internal(
45///      input: &vector<u8>,
46///      output: &vector<u8>,
47///      proof: &vector<u8>,
48///      iterations: u64): bool`
49///
50/// Gas cost: verify_vdf_cost
51/// ****************************************************************************
52/// *******************
53pub fn vdf_verify_internal(
54    context: &mut NativeContext,
55    ty_args: Vec<Type>,
56    mut args: VecDeque<Value>,
57) -> PartialVMResult<NativeResult> {
58    let cost = context.gas_used();
59    if !is_supported(context) {
60        return Ok(NativeResult::err(cost, NOT_SUPPORTED_ERROR));
61    }
62
63    // Load the cost parameters from the protocol config
64    let cost_params = &context
65        .extensions()
66        .get::<NativesCostTable>()
67        .vdf_cost_params
68        .clone();
69
70    // Charge the base cost for this operation
71    native_charge_gas_early_exit!(
72        context,
73        cost_params
74            .vdf_verify_cost
75            .ok_or_else(
76                || PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
77                    .with_message("Gas cost for vdf_verify not available".to_string())
78            )?
79    );
80
81    debug_assert!(ty_args.is_empty());
82    debug_assert!(args.len() == 4);
83
84    // The input is a reference to a vector of vector<u8>'s
85    let iterations = pop_arg!(args, u64);
86    let proof_bytes = pop_arg!(args, VectorRef);
87    let output_bytes = pop_arg!(args, VectorRef);
88    let input_bytes = pop_arg!(args, VectorRef);
89
90    let input = match bcs::from_bytes::<QuadraticForm>(&input_bytes.as_bytes_ref()) {
91        Ok(input) => input,
92        Err(_) => return Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR)),
93    };
94
95    let proof = match bcs::from_bytes::<QuadraticForm>(&proof_bytes.as_bytes_ref()) {
96        Ok(proof) => proof,
97        Err(_) => return Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR)),
98    };
99
100    let output = match bcs::from_bytes::<QuadraticForm>(&output_bytes.as_bytes_ref()) {
101        Ok(output) => output,
102        Err(_) => return Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR)),
103    };
104
105    // We use the default VDF construction: Wesolowski's construction using a strong
106    // Fiat-Shamir construction and a windowed scalar multiplier to speed up the
107    // proof verification.
108    let vdf = DefaultVDF::new(DISCRIMINANT_3072.clone(), iterations);
109    let verified = vdf.verify(&input, &output, &proof).is_ok();
110
111    Ok(NativeResult::ok(
112        context.gas_used(),
113        smallvec![Value::bool(verified)],
114    ))
115}
116
117/// ****************************************************************************
118/// ********************* native fun hash_to_input_internal
119///
120/// Implementation of the Move native function
121/// `vdf::hash_to_input_internal(message: &vector<u8>): vector<u8>`
122///
123/// Gas cost: hash_to_input_cost
124/// ****************************************************************************
125/// *******************
126pub fn hash_to_input_internal(
127    context: &mut NativeContext,
128    ty_args: Vec<Type>,
129    mut args: VecDeque<Value>,
130) -> PartialVMResult<NativeResult> {
131    let cost = context.gas_used();
132    if !is_supported(context) {
133        return Ok(NativeResult::err(cost, NOT_SUPPORTED_ERROR));
134    }
135
136    // Load the cost parameters from the protocol config
137    let cost_params = &context
138        .extensions()
139        .get::<NativesCostTable>()
140        .vdf_cost_params
141        .clone();
142
143    // Charge the base cost for this operation
144    native_charge_gas_early_exit!(
145        context,
146        cost_params
147            .hash_to_input_cost
148            .ok_or_else(
149                || PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
150                    .with_message("Gas cost for hash_to_input not available".to_string())
151            )?
152    );
153
154    debug_assert!(ty_args.is_empty());
155    debug_assert!(args.len() == 1);
156
157    let message = pop_arg!(args, VectorRef);
158
159    let output = match QuadraticForm::hash_to_group_with_default_parameters(
160        &message.as_bytes_ref(),
161        &DISCRIMINANT_3072,
162    ) {
163        Ok(output) => output,
164        Err(_) => return Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR)),
165    };
166
167    let output_bytes = match bcs::to_bytes(&output) {
168        Ok(bytes) => bytes,
169        // This should only fail on extremely large inputs, so we treat it as an invalid input error
170        Err(_) => return Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR)),
171    };
172
173    Ok(NativeResult::ok(
174        context.gas_used(),
175        smallvec![Value::vector_u8(output_bytes)],
176    ))
177}