Skip to main content

iota_move_natives_latest/crypto/
groth16.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 move_binary_format::errors::PartialVMResult;
8use move_core_types::gas_algebra::InternalGas;
9use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
10use move_vm_types::{
11    loaded_data::runtime_types::Type,
12    natives::function::NativeResult,
13    pop_arg,
14    values::{self, Value, VectorRef},
15};
16use smallvec::smallvec;
17
18use crate::{NativesCostTable, object_runtime::ObjectRuntime};
19
20pub const INVALID_VERIFYING_KEY: u64 = 0;
21pub const INVALID_CURVE: u64 = 1;
22pub const TOO_MANY_PUBLIC_INPUTS: u64 = 2;
23
24// These must match the corresponding values in iota::groth16::Curve.
25pub const BLS12381: u8 = 0;
26pub const BN254: u8 = 1;
27
28// We need to set an upper bound on the number of public inputs to avoid a DoS
29// attack
30pub const MAX_PUBLIC_INPUTS: usize = 8;
31
32#[derive(Clone)]
33pub struct Groth16PrepareVerifyingKeyCostParams {
34    pub groth16_prepare_verifying_key_bls12381_cost_base: InternalGas,
35    pub groth16_prepare_verifying_key_bn254_cost_base: InternalGas,
36}
37/// ****************************************************************************
38/// ********************* native fun prepare_verifying_key_internal
39/// Implementation of the Move native function
40/// `prepare_verifying_key_internal(curve: u8, verifying_key: &vector<u8>):
41/// PreparedVerifyingKey` This function has two cost modes depending on the
42/// curve being set to `BLS12381` or `BN254`. The core formula is same but
43/// constants differ. If curve = 0, we use the `bls12381` cost constants,
44/// otherwise we use the `bn254` cost constants.   gas cost:
45/// groth16_prepare_verifying_key_cost_base                    | covers various
46/// fixed costs in the oper Note: `curve` and `verifying_key` are fixed size, so
47/// their costs are included in the base cost. *********************************
48/// **************************************************************
49pub fn prepare_verifying_key_internal(
50    context: &mut NativeContext,
51    ty_args: Vec<Type>,
52    mut args: VecDeque<Value>,
53) -> PartialVMResult<NativeResult> {
54    debug_assert!(ty_args.is_empty());
55    debug_assert!(args.len() == 2);
56
57    // Load the cost parameters from the protocol config
58    let (groth16_prepare_verifying_key_cost_params, crypto_invalid_arguments_cost) = {
59        let cost_table = &context.extensions().get::<NativesCostTable>()?;
60        (
61            cost_table.groth16_prepare_verifying_key_cost_params.clone(),
62            cost_table.crypto_invalid_arguments_cost,
63        )
64    };
65    let bytes = pop_arg!(args, VectorRef);
66    let verifying_key = bytes.as_bytes_ref();
67
68    let curve = pop_arg!(args, u8);
69
70    // Load the cost parameters from the protocol config
71    let base_cost = match curve {
72        BLS12381 => {
73            groth16_prepare_verifying_key_cost_params
74                .groth16_prepare_verifying_key_bls12381_cost_base
75        }
76        BN254 => {
77            groth16_prepare_verifying_key_cost_params.groth16_prepare_verifying_key_bn254_cost_base
78        }
79        _ => {
80            // Charge for failure but dont fail if we run out of gas otherwise the actual
81            // error is masked by OUT_OF_GAS error
82            context.charge_gas(crypto_invalid_arguments_cost);
83            return Ok(NativeResult::err(context.gas_used(), INVALID_CURVE));
84        }
85    };
86    // Charge the base cost for this oper
87    native_charge_gas_early_exit!(context, base_cost);
88    let cost = context.gas_used();
89
90    let result = if curve == BLS12381 {
91        fastcrypto_zkp::bls12381::api::prepare_pvk_bytes(&verifying_key)
92    } else if curve == BN254 {
93        fastcrypto_zkp::bn254::api::prepare_pvk_bytes(&verifying_key)
94    } else {
95        return Ok(NativeResult::err(cost, INVALID_CURVE));
96    };
97
98    match result {
99        Ok(pvk) => Ok(NativeResult::ok(
100            cost,
101            smallvec![Value::struct_(values::Struct::pack(vec![
102                Value::vector_u8(pvk[0].to_vec()),
103                Value::vector_u8(pvk[1].to_vec()),
104                Value::vector_u8(pvk[2].to_vec()),
105                Value::vector_u8(pvk[3].to_vec())
106            ]))],
107        )),
108        Err(_) => Ok(NativeResult::err(cost, INVALID_VERIFYING_KEY)),
109    }
110}
111
112#[derive(Clone)]
113pub struct Groth16VerifyGroth16ProofInternalCostParams {
114    pub groth16_verify_groth16_proof_internal_bls12381_cost_base: InternalGas,
115    pub groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: InternalGas,
116
117    pub groth16_verify_groth16_proof_internal_bn254_cost_base: InternalGas,
118    pub groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: InternalGas,
119
120    pub groth16_verify_groth16_proof_internal_public_input_cost_per_byte: InternalGas,
121}
122/// ****************************************************************************
123/// ********************* native fun verify_groth16_proof_internal
124/// Implementation of the Move native function
125/// `verify_groth16_proof_internal(curve: u8, vk_gamma_abc_g1_bytes:
126/// &vector<u8>,                          alpha_g1_beta_g2_bytes: &vector<u8>,
127/// gamma_g2_neg_pc_bytes: &vector<u8>, delta_g2_neg_pc_bytes: &vector<u8>,
128///                          public_proof_inputs: &vector<u8>, proof_points:
129/// &vector<u8>): bool`
130///
131/// This function has two cost modes depending on the curve being set to
132/// `BLS12381` or `BN254`. The core formula is same but constants differ.
133/// If curve = 0, we use the `bls12381` cost constants, otherwise we use the
134/// `bn254` cost constants.   gas cost: groth16_prepare_verifying_key_cost_base
135/// | covers various fixed costs in the oper
136///              + groth16_verify_groth16_proof_internal_public_input_cost_per_byte
137///                                                   * size_of(public_proof_inputs) | covers the cost of verifying each public input per byte
138///              + groth16_verify_groth16_proof_internal_cost_per_public_input
139///                                                   * num_public_inputs) |
140///                                                     covers the cost of
141///                                                     verifying each public
142///                                                     input per input
143/// Note: every other arg is fixed size, so their costs are included in the base
144/// cost. **********************************************************************
145/// *************************
146pub fn verify_groth16_proof_internal(
147    context: &mut NativeContext,
148    ty_args: Vec<Type>,
149    mut args: VecDeque<Value>,
150) -> PartialVMResult<NativeResult> {
151    debug_assert!(ty_args.is_empty());
152    debug_assert!(args.len() == 7);
153
154    // Load the cost parameters from the protocol config
155    let (groth16_verify_groth16_proof_internal_cost_params, crypto_invalid_arguments_cost) = {
156        let cost_table = &context.extensions().get::<NativesCostTable>()?;
157        (
158            cost_table
159                .groth16_verify_groth16_proof_internal_cost_params
160                .clone(),
161            cost_table.crypto_invalid_arguments_cost,
162        )
163    };
164    let bytes5 = pop_arg!(args, VectorRef);
165    let proof_points = bytes5.as_bytes_ref();
166
167    let bytes4 = pop_arg!(args, VectorRef);
168    let public_proof_inputs = bytes4.as_bytes_ref();
169
170    let bytes3 = pop_arg!(args, VectorRef);
171    let delta_g2_neg_pc = bytes3.as_bytes_ref();
172
173    let bytes2 = pop_arg!(args, VectorRef);
174    let gamma_g2_neg_pc = bytes2.as_bytes_ref();
175
176    let byte1 = pop_arg!(args, VectorRef);
177    let alpha_g1_beta_g2 = byte1.as_bytes_ref();
178
179    let bytes = pop_arg!(args, VectorRef);
180    let vk_gamma_abc_g1 = bytes.as_bytes_ref();
181
182    let curve = pop_arg!(args, u8);
183
184    let (base_cost, cost_per_public_input, num_public_inputs) = match curve {
185        BLS12381 => (
186            groth16_verify_groth16_proof_internal_cost_params
187                .groth16_verify_groth16_proof_internal_bls12381_cost_base,
188            groth16_verify_groth16_proof_internal_cost_params
189                .groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input,
190            public_proof_inputs
191                .len()
192                .div_ceil(fastcrypto::groups::bls12381::SCALAR_LENGTH),
193        ),
194        BN254 => (
195            groth16_verify_groth16_proof_internal_cost_params
196                .groth16_verify_groth16_proof_internal_bn254_cost_base,
197            groth16_verify_groth16_proof_internal_cost_params
198                .groth16_verify_groth16_proof_internal_bn254_cost_per_public_input,
199            public_proof_inputs
200                .len()
201                .div_ceil(fastcrypto_zkp::bn254::api::SCALAR_SIZE),
202        ),
203        _ => {
204            // Charge for failure but dont fail if we run out of gas otherwise the actual
205            // error is masked by OUT_OF_GAS error
206            context.charge_gas(crypto_invalid_arguments_cost);
207            let cost = if context
208                .extensions()
209                .get::<ObjectRuntime>()?
210                .protocol_config
211                .native_charging_v2()
212            {
213                context.gas_used()
214            } else {
215                context.gas_budget()
216            };
217            return Ok(NativeResult::err(cost, INVALID_CURVE));
218        }
219    };
220    // Charge the base cost for this oper
221    native_charge_gas_early_exit!(context, base_cost);
222    // Charge the arg size dependent costs
223    native_charge_gas_early_exit!(
224        context,
225        cost_per_public_input * (num_public_inputs as u64).into()
226            + groth16_verify_groth16_proof_internal_cost_params
227                .groth16_verify_groth16_proof_internal_public_input_cost_per_byte
228                * (public_proof_inputs.len() as u64).into()
229    );
230
231    let cost = context.gas_used();
232
233    let result = if curve == BLS12381 {
234        if public_proof_inputs.len()
235            > fastcrypto::groups::bls12381::SCALAR_LENGTH * MAX_PUBLIC_INPUTS
236        {
237            return Ok(NativeResult::err(cost, TOO_MANY_PUBLIC_INPUTS));
238        }
239        fastcrypto_zkp::bls12381::api::verify_groth16_in_bytes(
240            &vk_gamma_abc_g1,
241            &alpha_g1_beta_g2,
242            &gamma_g2_neg_pc,
243            &delta_g2_neg_pc,
244            &public_proof_inputs,
245            &proof_points,
246        )
247    } else if curve == BN254 {
248        if public_proof_inputs.len() > fastcrypto_zkp::bn254::api::SCALAR_SIZE * MAX_PUBLIC_INPUTS {
249            return Ok(NativeResult::err(cost, TOO_MANY_PUBLIC_INPUTS));
250        }
251        fastcrypto_zkp::bn254::api::verify_groth16_in_bytes(
252            &vk_gamma_abc_g1,
253            &alpha_g1_beta_g2,
254            &gamma_g2_neg_pc,
255            &delta_g2_neg_pc,
256            &public_proof_inputs,
257            &proof_points,
258        )
259    } else {
260        return Ok(NativeResult::err(cost, INVALID_CURVE));
261    };
262
263    Ok(NativeResult::ok(
264        cost,
265        smallvec![Value::bool(result.unwrap_or(false))],
266    ))
267}