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