sui_move_natives_latest/crypto/
group_ops.rs

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