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