1use 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 Err(_) => Ok(NativeResult::err(
78 v2_native_charge(context, cost)?,
79 INVALID_INPUT_ERROR,
80 )),
81 }
82}
83
84#[derive(Clone)]
87pub struct GroupOpsCostParams {
88 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 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 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 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 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 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 pub bls12381_g1_msm_base_cost: Option<InternalGas>,
120 pub bls12381_g2_msm_base_cost: Option<InternalGas>,
121 pub bls12381_g1_msm_base_cost_per_input: Option<InternalGas>,
123 pub bls12381_g2_msm_base_cost_per_input: Option<InternalGas>,
124 pub bls12381_msm_max_len: Option<u32>,
126 pub bls12381_pairing_cost: Option<InternalGas>,
128 pub bls12381_g1_to_uncompressed_g1_cost: Option<InternalGas>,
130 pub bls12381_uncompressed_g1_to_g1_cost: Option<InternalGas>,
131 pub bls12381_uncompressed_g1_sum_base_cost: Option<InternalGas>,
133 pub bls12381_uncompressed_g1_sum_cost_per_term: Option<InternalGas>,
134 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 pub ristretto_scalar_add_cost: Option<InternalGas>,
140 pub ristretto_point_add_cost: Option<InternalGas>,
141 pub ristretto_scalar_sub_cost: Option<InternalGas>,
143 pub ristretto_point_sub_cost: Option<InternalGas>,
144 pub ristretto_scalar_mul_cost: Option<InternalGas>,
146 pub ristretto_point_mul_cost: Option<InternalGas>,
147 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#[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
205fn 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
222fn 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
231pub 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
298pub 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
371pub 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
444pub 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
533pub 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
622pub 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
686fn 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 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
777pub 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 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
848pub 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
892pub 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
948pub 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 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 (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}