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;
91    if curve == BLS12381 {
92        result = fastcrypto_zkp::bls12381::api::prepare_pvk_bytes(&verifying_key);
93    } else if curve == BN254 {
94        result = fastcrypto_zkp::bn254::api::prepare_pvk_bytes(&verifying_key);
95    } else {
96        return Ok(NativeResult::err(cost, INVALID_CURVE));
97    }
98
99    match result {
100        Ok(pvk) => Ok(NativeResult::ok(
101            cost,
102            smallvec![Value::struct_(values::Struct::pack(vec![
103                Value::vector_u8(pvk[0].to_vec()),
104                Value::vector_u8(pvk[1].to_vec()),
105                Value::vector_u8(pvk[2].to_vec()),
106                Value::vector_u8(pvk[3].to_vec())
107            ]))],
108        )),
109        Err(_) => Ok(NativeResult::err(cost, INVALID_VERIFYING_KEY)),
110    }
111}
112
113#[derive(Clone)]
114pub struct Groth16VerifyGroth16ProofInternalCostParams {
115    pub groth16_verify_groth16_proof_internal_bls12381_cost_base: InternalGas,
116    pub groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: InternalGas,
117
118    pub groth16_verify_groth16_proof_internal_bn254_cost_base: InternalGas,
119    pub groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: InternalGas,
120
121    pub groth16_verify_groth16_proof_internal_public_input_cost_per_byte: InternalGas,
122}
123/// ****************************************************************************
124/// ********************* native fun verify_groth16_proof_internal
125/// Implementation of the Move native function
126/// `verify_groth16_proof_internal(curve: u8, vk_gamma_abc_g1_bytes:
127/// &vector<u8>,                          alpha_g1_beta_g2_bytes: &vector<u8>,
128/// gamma_g2_neg_pc_bytes: &vector<u8>, delta_g2_neg_pc_bytes: &vector<u8>,
129///                          public_proof_inputs: &vector<u8>, proof_points:
130/// &vector<u8>): bool`
131///
132/// This function has two cost modes depending on the curve being set to
133/// `BLS12381` or `BN254`. The core formula is same but constants differ.
134/// If curve = 0, we use the `bls12381` cost constants, otherwise we use the
135/// `bn254` cost constants.   gas cost: groth16_prepare_verifying_key_cost_base
136/// | covers various fixed costs in the oper
137///              + groth16_verify_groth16_proof_internal_public_input_cost_per_byte
138///                                                   * size_of(public_proof_inputs) | covers the cost of verifying each public input per byte
139///              + groth16_verify_groth16_proof_internal_cost_per_public_input
140///                                                   * num_public_inputs) |
141///                                                     covers the cost of
142///                                                     verifying each public
143///                                                     input per input
144/// Note: every other arg is fixed size, so their costs are included in the base
145/// cost. **********************************************************************
146/// *************************
147pub fn verify_groth16_proof_internal(
148    context: &mut NativeContext,
149    ty_args: Vec<Type>,
150    mut args: VecDeque<Value>,
151) -> PartialVMResult<NativeResult> {
152    debug_assert!(ty_args.is_empty());
153    debug_assert!(args.len() == 7);
154
155    // Load the cost parameters from the protocol config
156    let (groth16_verify_groth16_proof_internal_cost_params, crypto_invalid_arguments_cost) = {
157        let cost_table = &context.extensions().get::<NativesCostTable>();
158        (
159            cost_table
160                .groth16_verify_groth16_proof_internal_cost_params
161                .clone(),
162            cost_table.crypto_invalid_arguments_cost,
163        )
164    };
165    let bytes5 = pop_arg!(args, VectorRef);
166    let proof_points = bytes5.as_bytes_ref();
167
168    let bytes4 = pop_arg!(args, VectorRef);
169    let public_proof_inputs = bytes4.as_bytes_ref();
170
171    let bytes3 = pop_arg!(args, VectorRef);
172    let delta_g2_neg_pc = bytes3.as_bytes_ref();
173
174    let bytes2 = pop_arg!(args, VectorRef);
175    let gamma_g2_neg_pc = bytes2.as_bytes_ref();
176
177    let byte1 = pop_arg!(args, VectorRef);
178    let alpha_g1_beta_g2 = byte1.as_bytes_ref();
179
180    let bytes = pop_arg!(args, VectorRef);
181    let vk_gamma_abc_g1 = bytes.as_bytes_ref();
182
183    let curve = pop_arg!(args, u8);
184
185    let (base_cost, cost_per_public_input, num_public_inputs) = match curve {
186        BLS12381 => (
187            groth16_verify_groth16_proof_internal_cost_params
188                .groth16_verify_groth16_proof_internal_bls12381_cost_base,
189            groth16_verify_groth16_proof_internal_cost_params
190                .groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input,
191            public_proof_inputs
192                .len()
193                .div_ceil(fastcrypto::groups::bls12381::SCALAR_LENGTH),
194        ),
195        BN254 => (
196            groth16_verify_groth16_proof_internal_cost_params
197                .groth16_verify_groth16_proof_internal_bn254_cost_base,
198            groth16_verify_groth16_proof_internal_cost_params
199                .groth16_verify_groth16_proof_internal_bn254_cost_per_public_input,
200            public_proof_inputs
201                .len()
202                .div_ceil(fastcrypto_zkp::bn254::api::SCALAR_SIZE),
203        ),
204        _ => {
205            // Charge for failure but dont fail if we run out of gas otherwise the actual
206            // error is masked by OUT_OF_GAS error
207            context.charge_gas(crypto_invalid_arguments_cost);
208            let cost = if context
209                .extensions()
210                .get::<ObjectRuntime>()
211                .protocol_config
212                .native_charging_v2()
213            {
214                context.gas_used()
215            } else {
216                context.gas_budget()
217            };
218            return Ok(NativeResult::err(cost, INVALID_CURVE));
219        }
220    };
221    // Charge the base cost for this oper
222    native_charge_gas_early_exit!(context, base_cost);
223    // Charge the arg size dependent costs
224    native_charge_gas_early_exit!(
225        context,
226        cost_per_public_input * (num_public_inputs as u64).into()
227            + groth16_verify_groth16_proof_internal_cost_params
228                .groth16_verify_groth16_proof_internal_public_input_cost_per_byte
229                * (public_proof_inputs.len() as u64).into()
230    );
231
232    let cost = context.gas_used();
233
234    let result;
235    if curve == BLS12381 {
236        if public_proof_inputs.len()
237            > fastcrypto::groups::bls12381::SCALAR_LENGTH * MAX_PUBLIC_INPUTS
238        {
239            return Ok(NativeResult::err(cost, TOO_MANY_PUBLIC_INPUTS));
240        }
241        result = fastcrypto_zkp::bls12381::api::verify_groth16_in_bytes(
242            &vk_gamma_abc_g1,
243            &alpha_g1_beta_g2,
244            &gamma_g2_neg_pc,
245            &delta_g2_neg_pc,
246            &public_proof_inputs,
247            &proof_points,
248        );
249    } else if curve == BN254 {
250        if public_proof_inputs.len() > fastcrypto_zkp::bn254::api::SCALAR_SIZE * MAX_PUBLIC_INPUTS {
251            return Ok(NativeResult::err(cost, TOO_MANY_PUBLIC_INPUTS));
252        }
253        result = fastcrypto_zkp::bn254::api::verify_groth16_in_bytes(
254            &vk_gamma_abc_g1,
255            &alpha_g1_beta_g2,
256            &gamma_g2_neg_pc,
257            &delta_g2_neg_pc,
258            &public_proof_inputs,
259            &proof_points,
260        );
261    } else {
262        return Ok(NativeResult::err(cost, INVALID_CURVE));
263    }
264
265    Ok(NativeResult::ok(
266        cost,
267        smallvec![Value::bool(result.unwrap_or(false))],
268    ))
269}