iota_move_natives_latest/crypto/
group_ops.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::{
8    error::{FastCryptoError, FastCryptoResult},
9    groups::{
10        FromTrustedByteArray, GroupElement, HashToGroupElement, MultiScalarMul, Pairing,
11        bls12381 as bls,
12    },
13    serde_helpers::ToFromByteArray,
14};
15use move_binary_format::errors::{PartialVMError, PartialVMResult};
16use move_core_types::{gas_algebra::InternalGas, vm_status::StatusCode};
17use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
18use move_vm_types::{
19    loaded_data::runtime_types::Type,
20    natives::function::NativeResult,
21    pop_arg,
22    values::{Value, VectorRef},
23};
24use smallvec::smallvec;
25
26use crate::{NativesCostTable, object_runtime::ObjectRuntime};
27
28pub const NOT_SUPPORTED_ERROR: u64 = 0;
29pub const INVALID_INPUT_ERROR: u64 = 1;
30pub const INPUT_TOO_LONG_ERROR: u64 = 2;
31
32fn is_msm_supported(context: &NativeContext) -> PartialVMResult<bool> {
33    Ok(context
34        .extensions()
35        .get::<ObjectRuntime>()?
36        .protocol_config
37        .enable_group_ops_native_function_msm())
38}
39
40fn v2_native_charge(context: &NativeContext, cost: InternalGas) -> PartialVMResult<InternalGas> {
41    Ok(
42        if context
43            .extensions()
44            .get::<ObjectRuntime>()?
45            .protocol_config
46            .native_charging_v2()
47        {
48            context.gas_used()
49        } else {
50            cost
51        },
52    )
53}
54
55fn map_op_result(
56    context: &NativeContext,
57    cost: InternalGas,
58    result: FastCryptoResult<Vec<u8>>,
59) -> PartialVMResult<NativeResult> {
60    match result {
61        Ok(bytes) => Ok(NativeResult::ok(
62            v2_native_charge(context, cost)?,
63            smallvec![Value::vector_u8(bytes)],
64        )),
65        // Since all Element<G> are validated on construction, this error should never happen unless
66        // the requested type is wrong or inputs are invalid.
67        Err(_) => Ok(NativeResult::err(
68            v2_native_charge(context, cost)?,
69            INVALID_INPUT_ERROR,
70        )),
71    }
72}
73
74fn is_uncompressed_g1_supported(context: &NativeContext) -> PartialVMResult<bool> {
75    Ok(context
76        .extensions()
77        .get::<ObjectRuntime>()?
78        .protocol_config
79        .uncompressed_g1_group_elements())
80}
81
82// Gas related structs and functions.
83
84#[derive(Clone)]
85pub struct GroupOpsCostParams {
86    // costs for decode and validate
87    pub bls12381_decode_scalar_cost: Option<InternalGas>,
88    pub bls12381_decode_g1_cost: Option<InternalGas>,
89    pub bls12381_decode_g2_cost: Option<InternalGas>,
90    pub bls12381_decode_gt_cost: Option<InternalGas>,
91    // costs for decode, add, and encode output
92    pub bls12381_scalar_add_cost: Option<InternalGas>,
93    pub bls12381_g1_add_cost: Option<InternalGas>,
94    pub bls12381_g2_add_cost: Option<InternalGas>,
95    pub bls12381_gt_add_cost: Option<InternalGas>,
96    // costs for decode, sub, and encode output
97    pub bls12381_scalar_sub_cost: Option<InternalGas>,
98    pub bls12381_g1_sub_cost: Option<InternalGas>,
99    pub bls12381_g2_sub_cost: Option<InternalGas>,
100    pub bls12381_gt_sub_cost: Option<InternalGas>,
101    // costs for decode, mul, and encode output
102    pub bls12381_scalar_mul_cost: Option<InternalGas>,
103    pub bls12381_g1_mul_cost: Option<InternalGas>,
104    pub bls12381_g2_mul_cost: Option<InternalGas>,
105    pub bls12381_gt_mul_cost: Option<InternalGas>,
106    // costs for decode, div, and encode output
107    pub bls12381_scalar_div_cost: Option<InternalGas>,
108    pub bls12381_g1_div_cost: Option<InternalGas>,
109    pub bls12381_g2_div_cost: Option<InternalGas>,
110    pub bls12381_gt_div_cost: Option<InternalGas>,
111    // costs for hashing
112    pub bls12381_g1_hash_to_base_cost: Option<InternalGas>,
113    pub bls12381_g2_hash_to_base_cost: Option<InternalGas>,
114    pub bls12381_g1_hash_to_cost_per_byte: Option<InternalGas>,
115    pub bls12381_g2_hash_to_cost_per_byte: Option<InternalGas>,
116    // costs for encoding the output + base cost for MSM (the |q| doublings) but not decoding
117    pub bls12381_g1_msm_base_cost: Option<InternalGas>,
118    pub bls12381_g2_msm_base_cost: Option<InternalGas>,
119    // cost that is multiplied with the approximated number of additions
120    pub bls12381_g1_msm_base_cost_per_input: Option<InternalGas>,
121    pub bls12381_g2_msm_base_cost_per_input: Option<InternalGas>,
122    // limit the length of the input vectors for MSM
123    pub bls12381_msm_max_len: Option<u32>,
124    // costs for decode, pairing, and encode output
125    pub bls12381_pairing_cost: Option<InternalGas>,
126    // costs for conversion to and from uncompressed form
127    pub bls12381_g1_to_uncompressed_g1_cost: Option<InternalGas>,
128    pub bls12381_uncompressed_g1_to_g1_cost: Option<InternalGas>,
129    // costs for sum of elements uncompressed form
130    pub bls12381_uncompressed_g1_sum_base_cost: Option<InternalGas>,
131    pub bls12381_uncompressed_g1_sum_cost_per_term: Option<InternalGas>,
132    // limit the number of terms in a sum
133    pub bls12381_uncompressed_g1_sum_max_terms: Option<u64>,
134}
135
136macro_rules! native_charge_gas_early_exit_option {
137    ($native_context:ident, $cost:expr) => {{
138        use move_binary_format::errors::PartialVMError;
139        use move_core_types::vm_status::StatusCode;
140        native_charge_gas_early_exit!(
141            $native_context,
142            $cost.ok_or_else(|| {
143                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
144                    .with_message("Gas cost for group ops is missing".to_string())
145            })?
146        );
147    }};
148}
149
150// Next should be aligned with the related Move modules.
151#[repr(u8)]
152enum Groups {
153    BLS12381Scalar = 0,
154    BLS12381G1 = 1,
155    BLS12381G2 = 2,
156    BLS12381GT = 3,
157    BLS12381UncompressedG1 = 4,
158}
159
160impl Groups {
161    fn from_u8(value: u8) -> Option<Self> {
162        match value {
163            0 => Some(Groups::BLS12381Scalar),
164            1 => Some(Groups::BLS12381G1),
165            2 => Some(Groups::BLS12381G2),
166            3 => Some(Groups::BLS12381GT),
167            4 => Some(Groups::BLS12381UncompressedG1),
168            _ => None,
169        }
170    }
171}
172
173fn parse_untrusted<G: ToFromByteArray<S> + FromTrustedByteArray<S>, const S: usize>(
174    e: &[u8],
175) -> FastCryptoResult<G> {
176    G::from_byte_array(e.try_into().map_err(|_| FastCryptoError::InvalidInput)?)
177}
178
179fn parse_trusted<G: ToFromByteArray<S> + FromTrustedByteArray<S>, const S: usize>(
180    e: &[u8],
181) -> FastCryptoResult<G> {
182    G::from_trusted_byte_array(e.try_into().map_err(|_| FastCryptoError::InvalidInput)?)
183}
184
185// Binary operations with 2 different types.
186fn binary_op_diff<
187    G1: ToFromByteArray<S1> + FromTrustedByteArray<S1>,
188    G2: ToFromByteArray<S2> + FromTrustedByteArray<S2>,
189    const S1: usize,
190    const S2: usize,
191>(
192    op: impl Fn(G1, G2) -> FastCryptoResult<G2>,
193    a1: &[u8],
194    a2: &[u8],
195) -> FastCryptoResult<Vec<u8>> {
196    let e1 = parse_trusted::<G1, S1>(a1)?;
197    let e2 = parse_trusted::<G2, S2>(a2)?;
198    let result = op(e1, e2)?;
199    Ok(result.to_byte_array().to_vec())
200}
201
202// Binary operations with the same type.
203fn binary_op<G: ToFromByteArray<S> + FromTrustedByteArray<S>, const S: usize>(
204    op: impl Fn(G, G) -> FastCryptoResult<G>,
205    a1: &[u8],
206    a2: &[u8],
207) -> FastCryptoResult<Vec<u8>> {
208    binary_op_diff::<G, G, S, S>(op, a1, a2)
209}
210
211// TODO: Since in many cases more than one group operation will be performed in
212// a single transaction, it might be worth caching the affine representation of
213// the group elements and use them to save conversions.
214
215/// ****************************************************************************
216/// ********************* native fun internal_validate
217/// Implementation of the Move native function `internal_validate(type: u8,
218/// bytes: &vector<u8>): bool`   gas cost: group_ops_decode_bls12381_X_cost
219/// where X is the requested type **********************************************
220/// *************************************************
221pub fn internal_validate(
222    context: &mut NativeContext,
223    ty_args: Vec<Type>,
224    mut args: VecDeque<Value>,
225) -> PartialVMResult<NativeResult> {
226    debug_assert!(ty_args.is_empty());
227    debug_assert!(args.len() == 2);
228
229    let cost = context.gas_used();
230
231    let bytes_ref = pop_arg!(args, VectorRef);
232    let bytes = bytes_ref.as_bytes_ref();
233    let group_type = pop_arg!(args, u8);
234
235    let cost_params = &context
236        .extensions()
237        .get::<NativesCostTable>()?
238        .group_ops_cost_params
239        .clone();
240
241    let result = match Groups::from_u8(group_type) {
242        Some(Groups::BLS12381Scalar) => {
243            native_charge_gas_early_exit_option!(context, cost_params.bls12381_decode_scalar_cost);
244            parse_untrusted::<bls::Scalar, { bls::Scalar::BYTE_LENGTH }>(&bytes).is_ok()
245        }
246        Some(Groups::BLS12381G1) => {
247            native_charge_gas_early_exit_option!(context, cost_params.bls12381_decode_g1_cost);
248            parse_untrusted::<bls::G1Element, { bls::G1Element::BYTE_LENGTH }>(&bytes).is_ok()
249        }
250        Some(Groups::BLS12381G2) => {
251            native_charge_gas_early_exit_option!(context, cost_params.bls12381_decode_g2_cost);
252            parse_untrusted::<bls::G2Element, { bls::G2Element::BYTE_LENGTH }>(&bytes).is_ok()
253        }
254        _ => false,
255    };
256
257    Ok(NativeResult::ok(
258        v2_native_charge(context, cost)?,
259        smallvec![Value::bool(result)],
260    ))
261}
262
263/// ****************************************************************************
264/// ********************* native fun internal_add
265/// Implementation of the Move native function `internal_add(type: u8, e1:
266/// &vector<u8>, e2: &vector<u8>): vector<u8>`   gas cost:
267/// group_ops_bls12381_X_add_cost where X is the requested type ****************
268/// ****************************************************************************
269/// ***
270pub fn internal_add(
271    context: &mut NativeContext,
272    ty_args: Vec<Type>,
273    mut args: VecDeque<Value>,
274) -> PartialVMResult<NativeResult> {
275    debug_assert!(ty_args.is_empty());
276    debug_assert!(args.len() == 3);
277
278    let cost = context.gas_used();
279
280    let e2_ref = pop_arg!(args, VectorRef);
281    let e2 = e2_ref.as_bytes_ref();
282    let e1_ref = pop_arg!(args, VectorRef);
283    let e1 = e1_ref.as_bytes_ref();
284    let group_type = pop_arg!(args, u8);
285
286    let cost_params = &context
287        .extensions()
288        .get::<NativesCostTable>()?
289        .group_ops_cost_params
290        .clone();
291
292    let result = match Groups::from_u8(group_type) {
293        Some(Groups::BLS12381Scalar) => {
294            native_charge_gas_early_exit_option!(context, cost_params.bls12381_scalar_add_cost);
295            binary_op::<bls::Scalar, { bls::Scalar::BYTE_LENGTH }>(|a, b| Ok(a + b), &e1, &e2)
296        }
297        Some(Groups::BLS12381G1) => {
298            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g1_add_cost);
299            binary_op::<bls::G1Element, { bls::G1Element::BYTE_LENGTH }>(|a, b| Ok(a + b), &e1, &e2)
300        }
301        Some(Groups::BLS12381G2) => {
302            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g2_add_cost);
303            binary_op::<bls::G2Element, { bls::G2Element::BYTE_LENGTH }>(|a, b| Ok(a + b), &e1, &e2)
304        }
305        Some(Groups::BLS12381GT) => {
306            native_charge_gas_early_exit_option!(context, cost_params.bls12381_gt_add_cost);
307            binary_op::<bls::GTElement, { bls::GTElement::BYTE_LENGTH }>(|a, b| Ok(a + b), &e1, &e2)
308        }
309        _ => Err(FastCryptoError::InvalidInput),
310    };
311
312    map_op_result(context, cost, result)
313}
314
315/// ****************************************************************************
316/// ********************* native fun internal_sub
317/// Implementation of the Move native function `internal_sub(type: u8, e1:
318/// &vector<u8>, e2: &vector<u8>): vector<u8>`   gas cost:
319/// group_ops_bls12381_X_sub_cost where X is the requested type ****************
320/// ****************************************************************************
321/// ***
322pub fn internal_sub(
323    context: &mut NativeContext,
324    ty_args: Vec<Type>,
325    mut args: VecDeque<Value>,
326) -> PartialVMResult<NativeResult> {
327    debug_assert!(ty_args.is_empty());
328    debug_assert!(args.len() == 3);
329
330    let cost = context.gas_used();
331
332    let e2_ref = pop_arg!(args, VectorRef);
333    let e2 = e2_ref.as_bytes_ref();
334    let e1_ref = pop_arg!(args, VectorRef);
335    let e1 = e1_ref.as_bytes_ref();
336    let group_type = pop_arg!(args, u8);
337
338    let cost_params = &context
339        .extensions()
340        .get::<NativesCostTable>()?
341        .group_ops_cost_params
342        .clone();
343
344    let result = match Groups::from_u8(group_type) {
345        Some(Groups::BLS12381Scalar) => {
346            native_charge_gas_early_exit_option!(context, cost_params.bls12381_scalar_sub_cost);
347            binary_op::<bls::Scalar, { bls::Scalar::BYTE_LENGTH }>(|a, b| Ok(a - b), &e1, &e2)
348        }
349        Some(Groups::BLS12381G1) => {
350            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g1_sub_cost);
351            binary_op::<bls::G1Element, { bls::G1Element::BYTE_LENGTH }>(|a, b| Ok(a - b), &e1, &e2)
352        }
353        Some(Groups::BLS12381G2) => {
354            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g2_sub_cost);
355            binary_op::<bls::G2Element, { bls::G2Element::BYTE_LENGTH }>(|a, b| Ok(a - b), &e1, &e2)
356        }
357        Some(Groups::BLS12381GT) => {
358            native_charge_gas_early_exit_option!(context, cost_params.bls12381_gt_sub_cost);
359            binary_op::<bls::GTElement, { bls::GTElement::BYTE_LENGTH }>(|a, b| Ok(a - b), &e1, &e2)
360        }
361        _ => Err(FastCryptoError::InvalidInput),
362    };
363
364    map_op_result(context, cost, result)
365}
366
367/// ****************************************************************************
368/// ********************* native fun internal_mul
369/// Implementation of the Move native function `internal_mul(type: u8, e1:
370/// &vector<u8>, e2: &vector<u8>): vector<u8>`   gas cost:
371/// group_ops_bls12381_X_mul_cost where X is the requested type ****************
372/// ****************************************************************************
373/// ***
374pub fn internal_mul(
375    context: &mut NativeContext,
376    ty_args: Vec<Type>,
377    mut args: VecDeque<Value>,
378) -> PartialVMResult<NativeResult> {
379    debug_assert!(ty_args.is_empty());
380    debug_assert!(args.len() == 3);
381
382    let cost = context.gas_used();
383
384    let e2_ref = pop_arg!(args, VectorRef);
385    let e2 = e2_ref.as_bytes_ref();
386    let e1_ref = pop_arg!(args, VectorRef);
387    let e1 = e1_ref.as_bytes_ref();
388    let group_type = pop_arg!(args, u8);
389
390    let cost_params = &context
391        .extensions()
392        .get::<NativesCostTable>()?
393        .group_ops_cost_params
394        .clone();
395
396    let result = match Groups::from_u8(group_type) {
397        Some(Groups::BLS12381Scalar) => {
398            native_charge_gas_early_exit_option!(context, cost_params.bls12381_scalar_mul_cost);
399            binary_op::<bls::Scalar, { bls::Scalar::BYTE_LENGTH }>(|a, b| Ok(b * a), &e1, &e2)
400        }
401        Some(Groups::BLS12381G1) => {
402            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g1_mul_cost);
403            binary_op_diff::<
404                bls::Scalar,
405                bls::G1Element,
406                { bls::Scalar::BYTE_LENGTH },
407                { bls::G1Element::BYTE_LENGTH },
408            >(|a, b| Ok(b * a), &e1, &e2)
409        }
410        Some(Groups::BLS12381G2) => {
411            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g2_mul_cost);
412            binary_op_diff::<
413                bls::Scalar,
414                bls::G2Element,
415                { bls::Scalar::BYTE_LENGTH },
416                { bls::G2Element::BYTE_LENGTH },
417            >(|a, b| Ok(b * a), &e1, &e2)
418        }
419        Some(Groups::BLS12381GT) => {
420            native_charge_gas_early_exit_option!(context, cost_params.bls12381_gt_mul_cost);
421            binary_op_diff::<
422                bls::Scalar,
423                bls::GTElement,
424                { bls::Scalar::BYTE_LENGTH },
425                { bls::GTElement::BYTE_LENGTH },
426            >(|a, b| Ok(b * a), &e1, &e2)
427        }
428        _ => Err(FastCryptoError::InvalidInput),
429    };
430
431    map_op_result(context, cost, result)
432}
433
434/// ****************************************************************************
435/// ********************* native fun internal_div
436/// Implementation of the Move native function `internal_div(type: u8, e1:
437/// &vector<u8>, e2: &vector<u8>): vector<u8>`   gas cost:
438/// group_ops_bls12381_X_div_cost where X is the requested type ****************
439/// ****************************************************************************
440/// ***
441pub fn internal_div(
442    context: &mut NativeContext,
443    ty_args: Vec<Type>,
444    mut args: VecDeque<Value>,
445) -> PartialVMResult<NativeResult> {
446    debug_assert!(ty_args.is_empty());
447    debug_assert!(args.len() == 3);
448
449    let cost = context.gas_used();
450
451    let e2_ref = pop_arg!(args, VectorRef);
452    let e2 = e2_ref.as_bytes_ref();
453    let e1_ref = pop_arg!(args, VectorRef);
454    let e1 = e1_ref.as_bytes_ref();
455    let group_type = pop_arg!(args, u8);
456
457    let cost_params = &context
458        .extensions()
459        .get::<NativesCostTable>()?
460        .group_ops_cost_params
461        .clone();
462
463    let result = match Groups::from_u8(group_type) {
464        Some(Groups::BLS12381Scalar) => {
465            native_charge_gas_early_exit_option!(context, cost_params.bls12381_scalar_div_cost);
466            binary_op::<bls::Scalar, { bls::Scalar::BYTE_LENGTH }>(|a, b| b / a, &e1, &e2)
467        }
468        Some(Groups::BLS12381G1) => {
469            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g1_div_cost);
470            binary_op_diff::<
471                bls::Scalar,
472                bls::G1Element,
473                { bls::Scalar::BYTE_LENGTH },
474                { bls::G1Element::BYTE_LENGTH },
475            >(|a, b| b / a, &e1, &e2)
476        }
477        Some(Groups::BLS12381G2) => {
478            native_charge_gas_early_exit_option!(context, cost_params.bls12381_g2_div_cost);
479            binary_op_diff::<
480                bls::Scalar,
481                bls::G2Element,
482                { bls::Scalar::BYTE_LENGTH },
483                { bls::G2Element::BYTE_LENGTH },
484            >(|a, b| b / a, &e1, &e2)
485        }
486        Some(Groups::BLS12381GT) => {
487            native_charge_gas_early_exit_option!(context, cost_params.bls12381_gt_div_cost);
488            binary_op_diff::<
489                bls::Scalar,
490                bls::GTElement,
491                { bls::Scalar::BYTE_LENGTH },
492                { bls::GTElement::BYTE_LENGTH },
493            >(|a, b| b / a, &e1, &e2)
494        }
495        _ => Err(FastCryptoError::InvalidInput),
496    };
497
498    map_op_result(context, cost, result)
499}
500
501#[rustfmt::skip]
502// native fun internal_hash_to
503// Implementation of the Move native function `internal_hash_to(type: u8, m: &vector<u8>): vector<u8>`
504//   gas cost: group_ops_bls12381_X_hash_to_base_cost + group_ops_bls12381_X_hash_to_cost_per_byte * |input|
505//             where X is the requested type
506pub fn internal_hash_to(
507    context: &mut NativeContext,
508    ty_args: Vec<Type>,
509    mut args: VecDeque<Value>,
510) -> PartialVMResult<NativeResult> {
511    debug_assert!(ty_args.is_empty());
512    debug_assert!(args.len() == 2);
513
514    let cost = context.gas_used();
515
516    let m_ref = pop_arg!(args, VectorRef);
517    let m = m_ref.as_bytes_ref();
518    let group_type = pop_arg!(args, u8);
519
520    if m.is_empty() {
521        return Ok(NativeResult::err(cost, INVALID_INPUT_ERROR));
522    }
523
524    let cost_params = &context
525        .extensions()
526        .get::<NativesCostTable>()?
527        .group_ops_cost_params
528        .clone();
529
530    let result = match Groups::from_u8(group_type) {
531        Some(Groups::BLS12381G1) => {
532            native_charge_gas_early_exit_option!(
533                context,
534                cost_params
535                    .bls12381_g1_hash_to_base_cost
536                    .and_then(|base_cost| cost_params
537                        .bls12381_g1_hash_to_cost_per_byte
538                        .map(|per_byte| base_cost + per_byte * (m.len() as u64).into()))
539            );
540            Ok(bls::G1Element::hash_to_group_element(&m)
541                .to_byte_array()
542                .to_vec())
543        }
544        Some(Groups::BLS12381G2) => {
545            native_charge_gas_early_exit_option!(
546                context,
547                cost_params
548                    .bls12381_g2_hash_to_base_cost
549                    .and_then(|base_cost| cost_params
550                        .bls12381_g2_hash_to_cost_per_byte
551                        .map(|per_byte| base_cost + per_byte * (m.len() as u64).into()))
552            );
553            Ok(bls::G2Element::hash_to_group_element(&m)
554                .to_byte_array()
555                .to_vec())
556        }
557        _ => Err(FastCryptoError::InvalidInput),
558    };
559
560    map_op_result(context, cost, result)
561}
562
563// Based on calculation from https://github.com/supranational/blst/blob/master/src/multi_scalar.c#L270
564fn msm_num_of_additions(n: u64) -> u64 {
565    debug_assert!(n > 0);
566    let wbits = (64 - n.leading_zeros() - 1) as u64;
567    let window_size = match wbits {
568        0 => 1,
569        1..=4 => 2,
570        5..=12 => wbits - 2,
571        _ => wbits - 3,
572    };
573    let num_of_windows = 255 / window_size + if 255 % window_size == 0 { 0 } else { 1 };
574    num_of_windows * (n + (1 << window_size) + 1)
575}
576
577#[test]
578fn test_msm_factor() {
579    assert_eq!(msm_num_of_additions(1), 1020);
580    assert_eq!(msm_num_of_additions(2), 896);
581    assert_eq!(msm_num_of_additions(3), 1024);
582    assert_eq!(msm_num_of_additions(4), 1152);
583    assert_eq!(msm_num_of_additions(32), 3485);
584}
585
586fn multi_scalar_mul<G, const SCALAR_SIZE: usize, const POINT_SIZE: usize>(
587    context: &mut NativeContext,
588    scalar_decode_cost: Option<InternalGas>,
589    point_decode_cost: Option<InternalGas>,
590    base_cost: Option<InternalGas>,
591    base_cost_per_addition: Option<InternalGas>,
592    max_len: u32,
593    scalars: &[u8],
594    points: &[u8],
595) -> PartialVMResult<NativeResult>
596where
597    G: GroupElement
598        + ToFromByteArray<POINT_SIZE>
599        + FromTrustedByteArray<POINT_SIZE>
600        + MultiScalarMul,
601    G::ScalarType: ToFromByteArray<SCALAR_SIZE> + FromTrustedByteArray<SCALAR_SIZE>,
602{
603    if points.is_empty()
604        || scalars.is_empty()
605        || !scalars.len().is_multiple_of(SCALAR_SIZE)
606        || !points.len().is_multiple_of(POINT_SIZE)
607        || points.len() / POINT_SIZE != scalars.len() / SCALAR_SIZE
608    {
609        return Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR));
610    }
611
612    if points.len() / POINT_SIZE > max_len as usize {
613        return Ok(NativeResult::err(context.gas_used(), INPUT_TOO_LONG_ERROR));
614    }
615
616    native_charge_gas_early_exit_option!(
617        context,
618        scalar_decode_cost.map(|cost| cost * ((scalars.len() / SCALAR_SIZE) as u64).into())
619    );
620    let scalars = scalars
621        .chunks(SCALAR_SIZE)
622        .map(parse_trusted::<G::ScalarType, { SCALAR_SIZE }>)
623        .collect::<Result<Vec<_>, _>>();
624
625    native_charge_gas_early_exit_option!(
626        context,
627        point_decode_cost.map(|cost| cost * ((points.len() / POINT_SIZE) as u64).into())
628    );
629    let points = points
630        .chunks(POINT_SIZE)
631        .map(parse_trusted::<G, { POINT_SIZE }>)
632        .collect::<Result<Vec<_>, _>>();
633
634    if let (Ok(scalars), Ok(points)) = (scalars, points) {
635        // Checked above that len()>0
636        let num_of_additions = msm_num_of_additions(scalars.len() as u64);
637        native_charge_gas_early_exit_option!(
638            context,
639            base_cost.and_then(|base| base_cost_per_addition
640                .map(|per_addition| base + per_addition * num_of_additions.into()))
641        );
642
643        let r = G::multi_scalar_mul(&scalars, &points)
644            .expect("Already checked the lengths of the vectors");
645        Ok(NativeResult::ok(
646            context.gas_used(),
647            smallvec![Value::vector_u8(r.to_byte_array().to_vec())],
648        ))
649    } else {
650        Ok(NativeResult::err(context.gas_used(), INVALID_INPUT_ERROR))
651    }
652}
653
654/// ****************************************************************************
655/// ********************* native fun internal_multi_scalar_mul
656/// Implementation of the Move native function `internal_multi_scalar_mul(type:
657/// u8, scalars: &vector<u8>, elements: &vector<u8>): vector<u8>`   gas cost:
658/// (bls12381_decode_scalar_cost + bls12381_decode_X_cost) * N +
659/// bls12381_X_msm_base_cost +             bls12381_X_msm_base_cost_per_input *
660/// num_of_additions(N) ********************************************************
661/// ***************************************
662pub fn internal_multi_scalar_mul(
663    context: &mut NativeContext,
664    ty_args: Vec<Type>,
665    mut args: VecDeque<Value>,
666) -> PartialVMResult<NativeResult> {
667    debug_assert!(ty_args.is_empty());
668    debug_assert!(args.len() == 3);
669
670    let cost = context.gas_used();
671    if !is_msm_supported(context)? {
672        return Ok(NativeResult::err(cost, NOT_SUPPORTED_ERROR));
673    }
674
675    let elements_ref = pop_arg!(args, VectorRef);
676    let elements = elements_ref.as_bytes_ref();
677    let scalars_ref = pop_arg!(args, VectorRef);
678    let scalars = scalars_ref.as_bytes_ref();
679    let group_type = pop_arg!(args, u8);
680
681    let cost_params = &context
682        .extensions()
683        .get::<NativesCostTable>()?
684        .group_ops_cost_params
685        .clone();
686
687    let max_len = cost_params.bls12381_msm_max_len.ok_or_else(|| {
688        PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
689            .with_message("Max len for MSM is not set".to_string())
690    })?;
691
692    // TODO: can potentially improve performance when some of the points are the
693    // generator.
694    match Groups::from_u8(group_type) {
695        Some(Groups::BLS12381G1) => multi_scalar_mul::<
696            bls::G1Element,
697            { bls::Scalar::BYTE_LENGTH },
698            { bls::G1Element::BYTE_LENGTH },
699        >(
700            context,
701            cost_params.bls12381_decode_scalar_cost,
702            cost_params.bls12381_decode_g1_cost,
703            cost_params.bls12381_g1_msm_base_cost,
704            cost_params.bls12381_g1_msm_base_cost_per_input,
705            max_len,
706            scalars.as_ref(),
707            elements.as_ref(),
708        ),
709        Some(Groups::BLS12381G2) => multi_scalar_mul::<
710            bls::G2Element,
711            { bls::Scalar::BYTE_LENGTH },
712            { bls::G2Element::BYTE_LENGTH },
713        >(
714            context,
715            cost_params.bls12381_decode_scalar_cost,
716            cost_params.bls12381_decode_g2_cost,
717            cost_params.bls12381_g2_msm_base_cost,
718            cost_params.bls12381_g2_msm_base_cost_per_input,
719            max_len,
720            scalars.as_ref(),
721            elements.as_ref(),
722        ),
723        _ => Ok(NativeResult::err(
724            v2_native_charge(context, cost)?,
725            INVALID_INPUT_ERROR,
726        )),
727    }
728}
729
730/// ****************************************************************************
731/// ********************* native fun internal_pairing
732/// Implementation of the Move native function `internal_pairing(type:u8, e1:
733/// &vector<u8>, e2: &vector<u8>): vector<u8>`   gas cost:
734/// group_ops_bls12381_pairing_cost ********************************************
735/// ***************************************************
736pub fn internal_pairing(
737    context: &mut NativeContext,
738    ty_args: Vec<Type>,
739    mut args: VecDeque<Value>,
740) -> PartialVMResult<NativeResult> {
741    debug_assert!(ty_args.is_empty());
742    debug_assert!(args.len() == 3);
743
744    let cost = context.gas_used();
745
746    let e2_ref = pop_arg!(args, VectorRef);
747    let e2 = e2_ref.as_bytes_ref();
748    let e1_ref = pop_arg!(args, VectorRef);
749    let e1 = e1_ref.as_bytes_ref();
750    let group_type = pop_arg!(args, u8);
751
752    let cost_params = &context
753        .extensions()
754        .get::<NativesCostTable>()?
755        .group_ops_cost_params
756        .clone();
757
758    let result = match Groups::from_u8(group_type) {
759        Some(Groups::BLS12381G1) => {
760            native_charge_gas_early_exit_option!(context, cost_params.bls12381_pairing_cost);
761            parse_trusted::<bls::G1Element, { bls::G1Element::BYTE_LENGTH }>(&e1).and_then(|e1| {
762                parse_trusted::<bls::G2Element, { bls::G2Element::BYTE_LENGTH }>(&e2).map(|e2| {
763                    let e3 = e1.pairing(&e2);
764                    e3.to_byte_array().to_vec()
765                })
766            })
767        }
768        _ => Err(FastCryptoError::InvalidInput),
769    };
770
771    map_op_result(context, cost, result)
772}
773
774/// ****************************************************************************
775/// ********************* native fun internal_convert
776/// Implementation of the Move native function `internal_convert(from_type:u8,
777/// to_type: u8, e: &vector<u8>): vector<u8>`   gas cost:
778/// group_ops_bls12381_g1_from_uncompressed_cost /
779/// group_ops_bls12381_g1_from_compressed_cost *********************************
780/// **************************************************************
781pub fn internal_convert(
782    context: &mut NativeContext,
783    ty_args: Vec<Type>,
784    mut args: VecDeque<Value>,
785) -> PartialVMResult<NativeResult> {
786    debug_assert!(ty_args.is_empty());
787    debug_assert!(args.len() == 3);
788
789    let cost = context.gas_used();
790
791    if !(is_uncompressed_g1_supported(context))? {
792        return Ok(NativeResult::err(cost, NOT_SUPPORTED_ERROR));
793    }
794
795    let e_ref = pop_arg!(args, VectorRef);
796    let e = e_ref.as_bytes_ref();
797    let to_type = pop_arg!(args, u8);
798    let from_type = pop_arg!(args, u8);
799
800    let cost_params = &context
801        .extensions()
802        .get::<NativesCostTable>()?
803        .group_ops_cost_params
804        .clone();
805
806    let result = match (Groups::from_u8(from_type), Groups::from_u8(to_type)) {
807        (Some(Groups::BLS12381UncompressedG1), Some(Groups::BLS12381G1)) => {
808            native_charge_gas_early_exit_option!(
809                context,
810                cost_params.bls12381_uncompressed_g1_to_g1_cost
811            );
812            e.to_vec()
813                .try_into()
814                .map_err(|_| FastCryptoError::InvalidInput)
815                .map(bls::G1ElementUncompressed::from_trusted_byte_array)
816                .and_then(|e| bls::G1Element::try_from(&e))
817                .map(|e| e.to_byte_array().to_vec())
818        }
819        (Some(Groups::BLS12381G1), Some(Groups::BLS12381UncompressedG1)) => {
820            native_charge_gas_early_exit_option!(
821                context,
822                cost_params.bls12381_g1_to_uncompressed_g1_cost
823            );
824            parse_trusted::<bls::G1Element, { bls::G1Element::BYTE_LENGTH }>(&e)
825                .map(|e| bls::G1ElementUncompressed::from(&e))
826                .map(|e| e.into_byte_array().to_vec())
827        }
828        _ => Err(FastCryptoError::InvalidInput),
829    };
830
831    map_op_result(context, cost, result)
832}
833
834/// ****************************************************************************
835/// ********************* native fun internal_sum
836/// Implementation of the Move native function `internal_sum(type:u8, terms:
837/// &vector<vector<u8>>): vector<u8>`   gas cost:
838/// group_ops_bls12381_g1_sum_of_uncompressed_base_cost + len(terms) *
839/// group_ops_bls12381_g1_sum_of_uncompressed_cost_per_term ********************
840/// ***************************************************************************
841pub fn internal_sum(
842    context: &mut NativeContext,
843    ty_args: Vec<Type>,
844    mut args: VecDeque<Value>,
845) -> PartialVMResult<NativeResult> {
846    debug_assert!(ty_args.is_empty());
847    debug_assert!(args.len() == 2);
848
849    let cost = context.gas_used();
850
851    if !(is_uncompressed_g1_supported(context))? {
852        return Ok(NativeResult::err(cost, NOT_SUPPORTED_ERROR));
853    }
854
855    let cost_params = &context
856        .extensions()
857        .get::<NativesCostTable>()?
858        .group_ops_cost_params
859        .clone();
860
861    // The input is a reference to a vector of vector<u8>'s
862    let inputs = pop_arg!(args, VectorRef);
863    let group_type = pop_arg!(args, u8);
864
865    let length = inputs
866        .len(&Type::Vector(Box::new(Type::U8)))?
867        .value_as::<u64>()?;
868
869    let result = match Groups::from_u8(group_type) {
870        Some(Groups::BLS12381UncompressedG1) => {
871            let max_terms = cost_params
872                .bls12381_uncompressed_g1_sum_max_terms
873                .ok_or_else(|| {
874                    PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
875                        .with_message("Max number of terms is not set".to_string())
876                })?;
877
878            if length > max_terms {
879                return Ok(NativeResult::err(cost, INPUT_TOO_LONG_ERROR));
880            }
881
882            native_charge_gas_early_exit_option!(
883                context,
884                cost_params
885                    .bls12381_uncompressed_g1_sum_base_cost
886                    .and_then(|base| cost_params
887                        .bls12381_uncompressed_g1_sum_cost_per_term
888                        .map(|per_term| base + per_term * length.into()))
889            );
890
891            // Read the input vector
892            (0..length)
893                .map(|i| {
894                    inputs
895                        .borrow_elem(i as usize, &Type::Vector(Box::new(Type::U8)))
896                        .and_then(Value::value_as::<VectorRef>)
897                        .map_err(|_| FastCryptoError::InvalidInput)
898                        .and_then(|v| {
899                            v.as_bytes_ref()
900                                .to_vec()
901                                .try_into()
902                                .map_err(|_| FastCryptoError::InvalidInput)
903                        })
904                        .map(bls::G1ElementUncompressed::from_trusted_byte_array)
905                })
906                .collect::<FastCryptoResult<Vec<_>>>()
907                .and_then(|e| bls::G1ElementUncompressed::sum(&e))
908                .map(|e| bls::G1ElementUncompressed::from(&e))
909                .map(|e| e.into_byte_array().to_vec())
910        }
911        _ => Err(FastCryptoError::InvalidInput),
912    };
913
914    map_op_result(context, cost, result)
915}