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