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