1mod glv;
40mod secp256k1;
41mod short_weierstrass;
42
43use alloc::vec::Vec;
44
45use miden_core::{
46 Felt, ZERO,
47 deferred::{
48 DeferredContext, DeferredError, Digest, Node, NodeType, Payload, Precompile,
49 PrecompileError, TRUE_DIGEST, Tag, precompile_id,
50 },
51};
52
53use self::secp256k1::Secp256k1;
54pub use self::{
55 glv::{SECP256K1_BETA, SECP256K1_LAMBDA, glv_decompose, phi_generator, scalar_mul_mod_n},
56 secp256k1::{SECP256K1_GENERATOR_X, SECP256K1_GENERATOR_Y, SECP256K1_ID},
57};
58use crate::math::uint::{Limbs, UintDomain, UintPrecompile, UintSpec};
59
60pub const K1_A_PTR: u32 = 8;
62pub const K1_B_PTR: u32 = 9;
64pub const K1_BETA_PTR: u32 = 10;
67pub const K1_LAMBDA_PTR: u32 = 11;
70
71pub const K1_GROUP_PTR: u32 = 1;
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct CurveCoefficient {
77 pub ptr: u32,
79 pub bound_ptr: u32,
81 pub value: Limbs,
83}
84
85pub fn curve_coefficients() -> [CurveCoefficient; 2] {
89 [
90 CurveCoefficient {
91 ptr: CurveId::Secp256k1.a_ptr(),
92 bound_ptr: CurveId::Secp256k1.base_domain().bound_ptr(),
93 value: <Secp256k1 as ShortWeierstrassSpec>::A,
94 },
95 CurveCoefficient {
96 ptr: CurveId::Secp256k1.b_ptr(),
97 bound_ptr: CurveId::Secp256k1.base_domain().bound_ptr(),
98 value: <Secp256k1 as ShortWeierstrassSpec>::B,
99 },
100 ]
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct Endomorphism {
109 pub beta_ptr: u32,
111 pub beta: Limbs,
113 pub lambda_ptr: u32,
115 pub lambda: Limbs,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum CurvePoint {
122 Identity,
124 Affine { x: Limbs, y: Limbs },
127}
128
129pub trait CurveSpec: Sized + 'static {
146 const ID: Felt;
151
152 type BaseField: UintSpec;
154
155 type ScalarField: UintSpec;
157
158 const GENERATOR_X: Limbs;
160
161 const GENERATOR_Y: Limbs;
163
164 fn generator() -> CurvePoint {
166 Self::point_from_affine(Self::GENERATOR_X, Self::GENERATOR_Y)
167 .expect("curve generator coordinates must be valid")
168 }
169
170 fn point_from_affine(x: Limbs, y: Limbs) -> Result<CurvePoint, PrecompileError>;
178
179 fn canonical_point(point: CurvePoint) -> Result<CurvePoint, PrecompileError> {
185 match point {
186 CurvePoint::Identity => Ok(CurvePoint::Identity),
187 CurvePoint::Affine { x, y } => Self::point_from_affine(x, y),
188 }
189 }
190
191 fn is_on_curve(point: &CurvePoint) -> bool {
193 Self::canonical_point(*point).is_ok()
194 }
195
196 fn add(lhs: CurvePoint, rhs: CurvePoint) -> Result<CurvePoint, PrecompileError>;
204
205 fn neg(point: CurvePoint) -> Result<CurvePoint, PrecompileError>;
213
214 fn sub(lhs: CurvePoint, rhs: CurvePoint) -> Result<CurvePoint, PrecompileError> {
221 let rhs = Self::neg(rhs)?;
222 Self::add(lhs, rhs)
223 }
224
225 fn mul_scalar(point: CurvePoint, scalar: Limbs) -> Result<CurvePoint, PrecompileError> {
232 debug_assert!(Self::is_on_curve(&point));
233 debug_assert!(Self::ScalarField::is_canonical(&scalar));
234
235 let Some(highest_limb) = scalar.iter().rposition(|&limb| limb != 0) else {
236 return Ok(CurvePoint::Identity);
237 };
238 let highest_bit =
239 highest_limb * 32 + (u32::BITS - 1 - scalar[highest_limb].leading_zeros()) as usize;
240
241 let mut acc = CurvePoint::Identity;
242 let mut base = point;
243
244 for bit_index in 0..=highest_bit {
245 let limb = scalar[bit_index / 32];
246 if ((limb >> (bit_index % 32)) & 1) == 1 {
247 acc = Self::add(acc, base)?;
248 }
249 if bit_index != highest_bit {
250 base = Self::add(base, base)?;
251 }
252 }
253
254 Ok(acc)
255 }
256}
257
258pub trait ShortWeierstrassSpec: CurveSpec {
260 const A: Limbs;
262
263 const B: Limbs;
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum CurveId {
270 Secp256k1,
271}
272
273impl CurveId {
274 pub const ALL: [Self; 1] = [Self::Secp256k1];
276
277 pub fn from_id(id: Felt) -> Option<Self> {
279 match id {
280 id if id == <Secp256k1 as CurveSpec>::ID => Some(Self::Secp256k1),
281 _ => None,
282 }
283 }
284
285 pub fn id(self) -> Felt {
287 match self {
288 Self::Secp256k1 => SECP256K1_ID,
289 }
290 }
291
292 pub const fn group_ptr(self) -> u32 {
294 match self {
295 Self::Secp256k1 => K1_GROUP_PTR,
296 }
297 }
298
299 pub const fn from_group_ptr(ptr: u32) -> Option<Self> {
301 match ptr {
302 K1_GROUP_PTR => Some(Self::Secp256k1),
303 _ => None,
304 }
305 }
306
307 pub const fn a_ptr(self) -> u32 {
309 match self {
310 Self::Secp256k1 => K1_A_PTR,
311 }
312 }
313
314 pub const fn b_ptr(self) -> u32 {
316 match self {
317 Self::Secp256k1 => K1_B_PTR,
318 }
319 }
320
321 pub const fn base_domain(self) -> UintDomain {
323 match self {
324 Self::Secp256k1 => UintDomain::K1Base,
325 }
326 }
327
328 pub fn a_value(self) -> Limbs {
330 match self {
331 Self::Secp256k1 => <Secp256k1 as ShortWeierstrassSpec>::A,
332 }
333 }
334
335 pub fn b_value(self) -> Limbs {
337 match self {
338 Self::Secp256k1 => <Secp256k1 as ShortWeierstrassSpec>::B,
339 }
340 }
341
342 pub const fn scalar_domain(self) -> UintDomain {
344 match self {
345 Self::Secp256k1 => UintDomain::K1Scalar,
346 }
347 }
348
349 pub fn generator(self) -> CurvePoint {
351 match self {
352 Self::Secp256k1 => Secp256k1::generator(),
353 }
354 }
355
356 pub fn endomorphism(self) -> Option<Endomorphism> {
358 match self {
359 Self::Secp256k1 => Some(Endomorphism {
360 beta_ptr: K1_BETA_PTR,
361 beta: SECP256K1_BETA,
362 lambda_ptr: K1_LAMBDA_PTR,
363 lambda: SECP256K1_LAMBDA,
364 }),
365 }
366 }
367
368 pub fn point_from_affine(self, x: Limbs, y: Limbs) -> Result<CurvePoint, PrecompileError> {
371 match self {
372 Self::Secp256k1 => Secp256k1::point_from_affine(x, y),
373 }
374 }
375
376 pub fn is_on_curve(self, point: &CurvePoint) -> bool {
378 match self {
379 Self::Secp256k1 => Secp256k1::is_on_curve(point),
380 }
381 }
382
383 pub fn add(self, lhs: CurvePoint, rhs: CurvePoint) -> Result<CurvePoint, PrecompileError> {
385 match self {
386 Self::Secp256k1 => Secp256k1::add(lhs, rhs),
387 }
388 }
389
390 pub fn neg(self, point: CurvePoint) -> Result<CurvePoint, PrecompileError> {
392 match self {
393 Self::Secp256k1 => Secp256k1::neg(point),
394 }
395 }
396
397 pub fn sub(self, lhs: CurvePoint, rhs: CurvePoint) -> Result<CurvePoint, PrecompileError> {
399 match self {
400 Self::Secp256k1 => Secp256k1::sub(lhs, rhs),
401 }
402 }
403
404 pub fn mul_scalar(
407 self,
408 point: CurvePoint,
409 scalar: Limbs,
410 ) -> Result<CurvePoint, PrecompileError> {
411 match self {
412 Self::Secp256k1 => Secp256k1::mul_scalar(point, scalar),
413 }
414 }
415}
416
417#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum CurveNodeRef {
423 Value { curve: CurveId, x: Digest, y: Digest },
425 Add { lhs: Digest, rhs: Digest },
427 Sub { lhs: Digest, rhs: Digest },
429 Eq { lhs: Digest, rhs: Digest },
431 Msm { pairs: Vec<(Digest, Digest)> },
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437enum CurveBinaryOp {
438 Add,
439 Sub,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
443enum CurveOp {
444 Value(CurveId),
445 Binary(CurveBinaryOp),
446 Eq,
447 Msm,
448}
449
450impl CurveOp {
451 fn decode(args: [Felt; 3]) -> Option<Self> {
452 match args[0].as_canonical_u64() {
453 CurvePrecompile::VALUE_OP_ID if args[2] == ZERO => {
454 let group_ptr = u32::try_from(args[1].as_canonical_u64()).ok()?;
455 let curve = CurveId::from_group_ptr(group_ptr)?;
456 Some(Self::Value(curve))
457 },
458 CurvePrecompile::ADD_OP_ID if args[1] == ZERO && args[2] == ZERO => {
459 Some(Self::Binary(CurveBinaryOp::Add))
460 },
461 CurvePrecompile::SUB_OP_ID if args[1] == ZERO && args[2] == ZERO => {
462 Some(Self::Binary(CurveBinaryOp::Sub))
463 },
464 CurvePrecompile::EQ_OP_ID if args[1] == ZERO && args[2] == ZERO => Some(Self::Eq),
465 CurvePrecompile::MSM_OP_ID if args[1] == ZERO && args[2] == ZERO => Some(Self::Msm),
466 _ => None,
467 }
468 }
469
470 fn node_type(self) -> NodeType {
471 match self {
472 Self::Value(_) | Self::Binary(_) | Self::Eq => NodeType::Join,
473 Self::Msm => NodeType::PairList,
474 }
475 }
476}
477
478enum CurveNode {
479 Value {
480 curve: CurveId,
481 lhs: Digest,
482 rhs: Digest,
483 },
484 BinaryOp {
485 op: CurveBinaryOp,
486 lhs: Digest,
487 rhs: Digest,
488 },
489 Eq {
490 lhs: Digest,
491 rhs: Digest,
492 },
493 Msm {
494 pairs: Vec<(Digest, Digest)>,
495 },
496}
497
498impl CurveNode {
499 fn parse(op: CurveOp, payload: &Payload) -> Result<Self, PrecompileError> {
500 Ok(match op {
501 CurveOp::Value(curve) => {
502 let (lhs, rhs) = payload.as_join()?;
503 Self::Value { curve, lhs, rhs }
504 },
505 CurveOp::Binary(op) => {
506 let (lhs, rhs) = payload.as_join()?;
507 Self::BinaryOp { op, lhs, rhs }
508 },
509 CurveOp::Eq => {
510 let (lhs, rhs) = payload.as_join()?;
511 Self::Eq { lhs, rhs }
512 },
513 CurveOp::Msm => {
514 let pairs = payload.as_pair_list()?;
515 Self::Msm { pairs }
516 },
517 })
518 }
519}
520
521#[derive(Clone, Copy, Debug, Default)]
523pub struct CurvePrecompile;
524
525impl CurvePrecompile {
526 pub const NAME: &'static str = "curve";
528
529 pub const VALUE_OP_ID: u64 = 0;
531 pub const ADD_OP_ID: u64 = 1;
532 pub const SUB_OP_ID: u64 = 2;
533 pub const EQ_OP_ID: u64 = 3;
534 pub const MSM_OP_ID: u64 = 4;
535
536 pub fn id() -> Felt {
538 precompile_id(Self::NAME)
539 }
540
541 pub fn value_tag(curve: CurveId) -> Tag {
543 let op_id = Felt::new(Self::VALUE_OP_ID).expect("curve VALUE op id must fit in a felt");
544 Tag::precompile(Self::id(), [op_id, Felt::from(curve.group_ptr()), ZERO])
545 .expect("curve precompile id is not framework-reserved")
546 }
547
548 pub fn op_tag(op_id: u64) -> Tag {
553 let op_id = Felt::new(op_id).expect("curve op id must fit in a felt");
554 Tag::precompile(Self::id(), [op_id, ZERO, ZERO])
555 .expect("curve precompile id is not framework-reserved")
556 }
557
558 pub fn msm_tag() -> Tag {
560 Self::op_tag(Self::MSM_OP_ID)
561 }
562
563 pub fn value_node(curve: CurveId, point: CurvePoint) -> Node {
568 match point {
569 CurvePoint::Identity => Self::identity_node(curve),
570 CurvePoint::Affine { x, y } => Self::affine_node_from_digests(
571 curve,
572 UintPrecompile::value_node(curve.base_domain(), x).digest(),
573 UintPrecompile::value_node(curve.base_domain(), y).digest(),
574 ),
575 }
576 }
577
578 pub fn identity_node(curve: CurveId) -> Node {
580 Node::join(Self::value_tag(curve), TRUE_DIGEST, TRUE_DIGEST)
581 .expect("curve value tag is precompile-owned")
582 }
583
584 pub fn generator_node(curve: CurveId) -> Node {
586 Self::value_node(curve, curve.generator())
587 }
588
589 pub fn affine_node_from_digests(curve: CurveId, x: Digest, y: Digest) -> Node {
591 Node::join(Self::value_tag(curve), x, y).expect("curve value tag is precompile-owned")
592 }
593
594 pub fn decode_node(node: &Node) -> Result<Option<CurveNodeRef>, PrecompileError> {
599 if node.tag().id() != Self::id() {
600 return Ok(None);
601 }
602
603 let op = CurveOp::decode(node.tag().args()).ok_or(PrecompileError::InvalidNode)?;
604 let parsed = CurveNode::parse(op, node.payload())?;
605 Ok(Some(match parsed {
606 CurveNode::Value { curve, lhs: x, rhs: y } => CurveNodeRef::Value { curve, x, y },
607 CurveNode::BinaryOp { op: CurveBinaryOp::Add, lhs, rhs } => {
608 CurveNodeRef::Add { lhs, rhs }
609 },
610 CurveNode::BinaryOp { op: CurveBinaryOp::Sub, lhs, rhs } => {
611 CurveNodeRef::Sub { lhs, rhs }
612 },
613 CurveNode::Eq { lhs, rhs } => CurveNodeRef::Eq { lhs, rhs },
614 CurveNode::Msm { pairs } => CurveNodeRef::Msm { pairs },
615 }))
616 }
617
618 fn extend_init_nodes_with_point(nodes: &mut Vec<Node>, curve: CurveId, point: CurvePoint) {
619 if let CurvePoint::Affine { x, y } = point {
620 let x = UintPrecompile::value_node(curve.base_domain(), x);
621 let y = UintPrecompile::value_node(curve.base_domain(), y);
622 nodes.push(x.clone());
623 nodes.push(y.clone());
624 nodes.push(Self::affine_node_from_digests(curve, x.digest(), y.digest()));
625 }
626 }
627
628 fn canonical_value_node(
634 curve: CurveId,
635 point: CurvePoint,
636 context: &mut DeferredContext<'_>,
637 ) -> Result<Node, PrecompileError> {
638 match point {
639 CurvePoint::Identity => Ok(Self::identity_node(curve)),
640 CurvePoint::Affine { x, y } => {
641 let x = context.register(UintPrecompile::value_node(curve.base_domain(), x))?;
642 let y = context.register(UintPrecompile::value_node(curve.base_domain(), y))?;
643 Ok(Self::affine_node_from_digests(curve, x, y))
644 },
645 }
646 }
647
648 fn evaluate_msm_term(
649 expected_curve: Option<CurveId>,
650 point: Digest,
651 scalar: Digest,
652 context: &mut DeferredContext<'_>,
653 ) -> Result<(CurveId, CurvePoint, Limbs), PrecompileError> {
654 let (point_digest, scalar_digest) = context.evaluate_digest_pair(point, scalar)?;
655 let point_node = context.get_node(&point_digest).ok_or(PrecompileError::MissingNode)?;
656 let scalar_node = context.get_node(&scalar_digest).ok_or(PrecompileError::MissingNode)?;
657
658 let (point_curve, point) = Self::point_from_value_node(point_node, context)?;
659 if let Some(expected_curve) = expected_curve
660 && expected_curve != point_curve
661 {
662 return Err(DeferredError::InvalidPayload.into());
663 }
664 let scalar =
665 UintPrecompile::limbs_from_value_node(scalar_node, point_curve.scalar_domain())?;
666 Ok((point_curve, point, scalar))
667 }
668
669 fn evaluate_msm(
670 pairs: &[(Digest, Digest)],
671 context: &mut DeferredContext<'_>,
672 ) -> Result<(CurveId, CurvePoint), PrecompileError> {
673 let Some((&(point, scalar), rest)) = pairs.split_first() else {
674 return Err(DeferredError::InvalidPayload.into());
675 };
676
677 let (curve, point, scalar) = Self::evaluate_msm_term(None, point, scalar, context)?;
678 if scalar == [0; 8] || point == CurvePoint::Identity {
679 return Err(DeferredError::InvalidPayload.into());
680 }
681 let mut acc = curve.mul_scalar(point, scalar)?;
682 let mut points = Vec::with_capacity(pairs.len());
683 points.push(point);
684 for &(point, scalar) in rest {
685 let (_, point, scalar) = Self::evaluate_msm_term(Some(curve), point, scalar, context)?;
686 if scalar == [0; 8] || point == CurvePoint::Identity || points.contains(&point) {
687 return Err(DeferredError::InvalidPayload.into());
688 }
689 points.push(point);
690 let term = curve.mul_scalar(point, scalar)?;
691 acc = curve.add(acc, term)?;
692 }
693 Ok((curve, acc))
694 }
695
696 fn evaluate_point_pair(
697 context: &mut DeferredContext<'_>,
698 lhs: Digest,
699 rhs: Digest,
700 ) -> Result<(CurveId, CurvePoint, CurvePoint), PrecompileError> {
701 let (lhs, rhs) = context.evaluate_digest_pair(lhs, rhs)?;
702 let (lhs_curve, lhs) = {
703 let lhs = context.get_node(&lhs).ok_or(PrecompileError::MissingNode)?;
704 Self::point_from_value_node(lhs, context)?
705 };
706 let (rhs_curve, rhs) = {
707 let rhs = context.get_node(&rhs).ok_or(PrecompileError::MissingNode)?;
708 Self::point_from_value_node(rhs, context)?
709 };
710
711 if lhs_curve != rhs_curve {
712 return Err(DeferredError::InvalidPayload.into());
713 }
714
715 Ok((lhs_curve, lhs, rhs))
716 }
717
718 fn point_from_value_node(
720 node: &Node,
721 context: &DeferredContext<'_>,
722 ) -> Result<(CurveId, CurvePoint), PrecompileError> {
723 let Some(CurveOp::Value(curve)) = CurveOp::decode(node.tag().args()) else {
724 return Err(DeferredError::InvalidPayload.into());
725 };
726 let point = Self::point_of_canonical_node(curve, node, context)?;
727 Ok((curve, point))
728 }
729
730 fn point_of_canonical_node(
736 curve: CurveId,
737 node: &Node,
738 context: &DeferredContext<'_>,
739 ) -> Result<CurvePoint, PrecompileError> {
740 let payload = node.payload_for_tag(Self::value_tag(curve))?;
741 let (x_digest, y_digest) = payload.as_join()?;
742 Self::point_from_canonical_value_payload(curve, x_digest, y_digest, context)
743 }
744
745 fn point_from_checked_value_payload(
752 curve: CurveId,
753 x_digest: Digest,
754 y_digest: Digest,
755 context: &DeferredContext<'_>,
756 ) -> Result<CurvePoint, PrecompileError> {
757 match (x_digest == TRUE_DIGEST, y_digest == TRUE_DIGEST) {
758 (true, true) => Ok(CurvePoint::Identity),
759 (true, false) | (false, true) => Err(DeferredError::InvalidPayload.into()),
760 (false, false) => {
761 let x_node = context.get_node(&x_digest).ok_or(PrecompileError::MissingNode)?;
762 let y_node = context.get_node(&y_digest).ok_or(PrecompileError::MissingNode)?;
763 let x = UintPrecompile::limbs_from_value_node(x_node, curve.base_domain())?;
764 let y = UintPrecompile::limbs_from_value_node(y_node, curve.base_domain())?;
765 curve.point_from_affine(x, y)
766 },
767 }
768 }
769
770 fn point_from_canonical_value_payload(
778 curve: CurveId,
779 x_digest: Digest,
780 y_digest: Digest,
781 context: &DeferredContext<'_>,
782 ) -> Result<CurvePoint, PrecompileError> {
783 match (x_digest == TRUE_DIGEST, y_digest == TRUE_DIGEST) {
784 (true, true) => Ok(CurvePoint::Identity),
785 (true, false) | (false, true) => Err(DeferredError::InvalidPayload.into()),
786 (false, false) => {
787 let x_node = context.get_node(&x_digest).ok_or(PrecompileError::MissingNode)?;
788 let y_node = context.get_node(&y_digest).ok_or(PrecompileError::MissingNode)?;
789 let x = UintPrecompile::limbs_from_value_node(x_node, curve.base_domain())?;
790 let y = UintPrecompile::limbs_from_value_node(y_node, curve.base_domain())?;
791 let point = CurvePoint::Affine { x, y };
792 debug_assert!(curve.is_on_curve(&point));
793 Ok(point)
794 },
795 }
796 }
797}
798
799impl Precompile for CurvePrecompile {
800 fn name(&self) -> &'static str {
801 Self::NAME
802 }
803
804 fn id(&self) -> Felt {
805 Self::id()
806 }
807
808 fn init(&self) -> Vec<Node> {
809 let mut nodes = Vec::with_capacity(CurveId::ALL.len() * 2);
810 for curve in CurveId::ALL {
811 nodes.push(Self::identity_node(curve));
812 Self::extend_init_nodes_with_point(&mut nodes, curve, curve.generator());
813 }
814 nodes
815 }
816
817 fn decode(&self, args: [Felt; 3]) -> Option<NodeType> {
818 let op = CurveOp::decode(args)?;
819 Some(op.node_type())
820 }
821
822 fn evaluate(
823 &self,
824 args: [Felt; 3],
825 payload: &Payload,
826 context: &mut DeferredContext<'_>,
827 ) -> Result<Node, PrecompileError> {
828 let op = CurveOp::decode(args).ok_or(PrecompileError::InvalidNode)?;
829
830 match CurveNode::parse(op, payload)? {
831 CurveNode::Value { curve, lhs, rhs } => {
832 match (lhs == TRUE_DIGEST, rhs == TRUE_DIGEST) {
833 (true, true) => Ok(Self::identity_node(curve)),
834 (true, false) | (false, true) => Err(DeferredError::InvalidPayload.into()),
835 (false, false) => {
836 let (x_digest, y_digest) = context.evaluate_digest_pair(lhs, rhs)?;
837 let point = Self::point_from_checked_value_payload(
838 curve, x_digest, y_digest, context,
839 )?;
840 Self::canonical_value_node(curve, point, context)
841 },
842 }
843 },
844 CurveNode::BinaryOp { op, lhs, rhs } => {
845 let (curve, lhs, rhs) = Self::evaluate_point_pair(context, lhs, rhs)?;
846 let value = match op {
847 CurveBinaryOp::Add => curve.add(lhs, rhs)?,
848 CurveBinaryOp::Sub => curve.sub(lhs, rhs)?,
849 };
850 Self::canonical_value_node(curve, value, context)
851 },
852 CurveNode::Eq { lhs, rhs } => {
853 let (_, lhs, rhs) = Self::evaluate_point_pair(context, lhs, rhs)?;
854 if lhs == rhs {
855 Ok(Node::TRUE)
856 } else {
857 Err(PrecompileError::AssertionFailed)
858 }
859 },
860 CurveNode::Msm { pairs } => {
861 let (curve, value) = Self::evaluate_msm(&pairs, context)?;
862 Self::canonical_value_node(curve, value, context)
863 },
864 }
865 }
866}
867
868#[cfg(test)]
869mod tests {
870 use alloc::{sync::Arc, vec};
871
872 use miden_core::deferred::DeferredState;
873
874 use super::*;
875 use crate::math::{
876 k1_scalar::K1Scalar,
877 uint::{UintPrecompile, ZERO_LIMBS},
878 };
879
880 fn state() -> DeferredState {
881 DeferredState::new(Arc::new(crate::registry())).expect("precompile init must succeed")
882 }
883
884 fn evaluate(state: &mut DeferredState, node: Node) -> Result<Node, PrecompileError> {
885 let digest = state.register(node)?;
886 state.require_canonical_node(digest).map(|(_, node)| node.clone())
887 }
888
889 fn assert_invalid_payload<T>(result: Result<T, PrecompileError>) {
890 let Err(error) = result else {
891 panic!("expected invalid payload");
892 };
893 assert!(
894 matches!(error.root(), PrecompileError::Other(DeferredError::InvalidPayload)),
895 "expected invalid payload, got {error:?}",
896 );
897 }
898
899 fn affine_limbs(point: CurvePoint) -> (Limbs, Limbs) {
900 match point {
901 CurvePoint::Affine { x, y } => (x, y),
902 CurvePoint::Identity => panic!("expected affine point"),
903 }
904 }
905
906 fn register_affine_point(state: &mut DeferredState, curve: CurveId, point: CurvePoint) -> Node {
907 let (x, y) = affine_limbs(point);
908 let x = UintPrecompile::value_node(curve.base_domain(), x);
909 let y = UintPrecompile::value_node(curve.base_domain(), y);
910 state.register(x.clone()).expect("x coordinate must register");
911 state.register(y.clone()).expect("y coordinate must register");
912 let point = CurvePrecompile::affine_node_from_digests(curve, x.digest(), y.digest());
913 state.register(point.clone()).expect("point must register");
914 point
915 }
916
917 #[test]
918 fn decode_curve_value_tags() {
919 let precompile = CurvePrecompile;
920 let curve = CurveId::Secp256k1;
921
922 assert_eq!(
923 CurvePrecompile::value_tag(curve).as_word(),
924 [
925 CurvePrecompile::id(),
926 Felt::from_u32(CurvePrecompile::VALUE_OP_ID as u32),
927 Felt::from(curve.group_ptr()),
928 ZERO,
929 ],
930 );
931 assert_eq!(
932 precompile.decode(CurvePrecompile::value_tag(curve).args()),
933 Some(NodeType::Join)
934 );
935 assert_eq!(
936 precompile.decode([
937 Felt::from_u32(CurvePrecompile::VALUE_OP_ID as u32),
938 Felt::from(curve.group_ptr()),
939 Felt::from_u32(1),
940 ]),
941 None
942 );
943 assert_eq!(
944 precompile.decode([
945 Felt::from_u32(CurvePrecompile::VALUE_OP_ID as u32),
946 Felt::new_unchecked(99),
947 ZERO,
948 ]),
949 None
950 );
951 }
952
953 #[test]
954 fn decode_curve_operation_tags() {
955 let precompile = CurvePrecompile;
956 let curve = CurveId::Secp256k1;
957
958 assert_eq!(
959 precompile.decode(CurvePrecompile::op_tag(CurvePrecompile::ADD_OP_ID).args()),
960 Some(NodeType::Join)
961 );
962
963 let mut add_with_curve = CurvePrecompile::op_tag(CurvePrecompile::ADD_OP_ID).args();
964 add_with_curve[1] = Felt::from(curve.group_ptr());
965 assert_eq!(precompile.decode(add_with_curve), None);
966 assert_eq!(precompile.decode(CurvePrecompile::op_tag(5).args()), None);
967 }
968
969 #[test]
970 fn decode_curve_msm_tags() {
971 let precompile = CurvePrecompile;
972 let curve = CurveId::Secp256k1;
973
974 assert_eq!(
975 CurvePrecompile::msm_tag().as_word(),
976 [
977 CurvePrecompile::id(),
978 Felt::from_u32(CurvePrecompile::MSM_OP_ID as u32),
979 ZERO,
980 ZERO,
981 ],
982 );
983 assert_eq!(precompile.decode(CurvePrecompile::msm_tag().args()), Some(NodeType::PairList));
984
985 let mut msm_with_curve = CurvePrecompile::msm_tag().args();
986 msm_with_curve[1] = Felt::from(curve.group_ptr());
987 assert_eq!(precompile.decode(msm_with_curve), None);
988 assert_eq!(
989 precompile.decode([
990 Felt::from_u32(CurvePrecompile::MSM_OP_ID as u32),
991 Felt::from(curve.group_ptr()),
992 Felt::from_u32(1),
993 ]),
994 None
995 );
996 }
997
998 #[test]
999 fn same_curve_add_succeeds() {
1000 let mut state = state();
1001 let curve = CurveId::Secp256k1;
1002 let generator = CurvePrecompile::generator_node(curve);
1003 let identity = CurvePrecompile::identity_node(curve);
1004 let node = Node::join(
1005 CurvePrecompile::op_tag(CurvePrecompile::ADD_OP_ID),
1006 generator.digest(),
1007 identity.digest(),
1008 )
1009 .expect("tag is curve-owned");
1010
1011 assert_eq!(evaluate(&mut state, node).unwrap(), generator);
1012 }
1013
1014 #[test]
1015 fn msm_one_pair_evaluates_point_and_scalar_operands() {
1016 let mut state = state();
1017 let curve = CurveId::Secp256k1;
1018 let generator = CurvePrecompile::generator_node(curve);
1019 let scalar = UintPrecompile::value_node(curve.scalar_domain(), [2, 0, 0, 0, 0, 0, 0, 0]);
1020 state.register(scalar.clone()).expect("scalar must register");
1021 let node = Node::try_pair_list(
1022 CurvePrecompile::msm_tag(),
1023 vec![(generator.digest(), scalar.digest())],
1024 )
1025 .expect("tag is curve-owned");
1026 let expected = CurvePrecompile::value_node(
1027 curve,
1028 curve
1029 .mul_scalar(curve.generator(), [2, 0, 0, 0, 0, 0, 0, 0])
1030 .expect("valid mul_scalar"),
1031 );
1032
1033 assert_eq!(evaluate(&mut state, node).unwrap(), expected);
1034 }
1035
1036 #[test]
1037 fn msm_accumulates_multiple_pairs() {
1038 let mut state = state();
1039 let curve = CurveId::Secp256k1;
1040 let generator = CurvePrecompile::generator_node(curve);
1041 let two_g = curve
1042 .mul_scalar(curve.generator(), [2, 0, 0, 0, 0, 0, 0, 0])
1043 .expect("valid scalar multiplication");
1044 let two_g_node = register_affine_point(&mut state, curve, two_g);
1045 let scalar_2 = UintPrecompile::value_node(curve.scalar_domain(), [2, 0, 0, 0, 0, 0, 0, 0]);
1046 let scalar_3 = UintPrecompile::value_node(curve.scalar_domain(), [3, 0, 0, 0, 0, 0, 0, 0]);
1047 state.register(scalar_2.clone()).expect("scalar must register");
1048 state.register(scalar_3.clone()).expect("scalar must register");
1049 let node = Node::try_pair_list(
1050 CurvePrecompile::msm_tag(),
1051 vec![
1052 (generator.digest(), scalar_2.digest()),
1053 (two_g_node.digest(), scalar_3.digest()),
1054 ],
1055 )
1056 .expect("tag is curve-owned");
1057 let two_g_scaled = curve
1058 .mul_scalar(curve.generator(), [2, 0, 0, 0, 0, 0, 0, 0])
1059 .expect("valid scalar multiplication");
1060 let six_g = curve
1061 .mul_scalar(two_g, [3, 0, 0, 0, 0, 0, 0, 0])
1062 .expect("valid scalar multiplication");
1063 let expected = CurvePrecompile::value_node(
1064 curve,
1065 curve.add(two_g_scaled, six_g).expect("valid point addition"),
1066 );
1067
1068 assert_eq!(evaluate(&mut state, node).unwrap(), expected);
1069 }
1070
1071 #[test]
1072 fn msm_rejects_duplicate_canonical_points() {
1073 let mut state = state();
1074 let curve = CurveId::Secp256k1;
1075 let generator = CurvePrecompile::generator_node(curve);
1076 let scalar_2 = UintPrecompile::value_node(curve.scalar_domain(), [2, 0, 0, 0, 0, 0, 0, 0]);
1077 let scalar_3 = UintPrecompile::value_node(curve.scalar_domain(), [3, 0, 0, 0, 0, 0, 0, 0]);
1078 state.register(scalar_2.clone()).expect("scalar must register");
1079 state.register(scalar_3.clone()).expect("scalar must register");
1080 let node = Node::try_pair_list(
1081 CurvePrecompile::msm_tag(),
1082 vec![(generator.digest(), scalar_2.digest()), (generator.digest(), scalar_3.digest())],
1083 )
1084 .expect("tag is curve-owned");
1085
1086 assert_invalid_payload(evaluate(&mut state, node));
1087 }
1088
1089 #[test]
1090 fn msm_rejects_zero_scalar_terms() {
1091 let mut state = state();
1092 let curve = CurveId::Secp256k1;
1093 let generator = CurvePrecompile::generator_node(curve);
1094 let zero = UintPrecompile::value_node(curve.scalar_domain(), [0; 8]);
1095 state.register(zero.clone()).expect("scalar must register");
1096 let node = Node::try_pair_list(
1097 CurvePrecompile::msm_tag(),
1098 vec![(generator.digest(), zero.digest())],
1099 )
1100 .expect("tag is curve-owned");
1101
1102 assert_invalid_payload(evaluate(&mut state, node));
1103 }
1104
1105 #[test]
1106 fn msm_rejects_identity_base_terms() {
1107 let mut state = state();
1108 let curve = CurveId::Secp256k1;
1109 let identity = CurvePrecompile::identity_node(curve);
1110 let scalar = UintPrecompile::value_node(curve.scalar_domain(), [2, 0, 0, 0, 0, 0, 0, 0]);
1111 state.register(identity.clone()).expect("identity must register");
1112 state.register(scalar.clone()).expect("scalar must register");
1113 let node = Node::try_pair_list(
1114 CurvePrecompile::msm_tag(),
1115 vec![(identity.digest(), scalar.digest())],
1116 )
1117 .expect("tag is curve-owned");
1118
1119 assert_invalid_payload(evaluate(&mut state, node));
1120 }
1121
1122 #[test]
1123 fn msm_rejects_wrong_scalar_domain() {
1124 let mut state = state();
1125 let curve = CurveId::Secp256k1;
1126 let generator = CurvePrecompile::generator_node(curve);
1127 let scalar = UintPrecompile::value_node(curve.base_domain(), [2, 0, 0, 0, 0, 0, 0, 0]);
1128 state.register(scalar.clone()).expect("scalar must register");
1129 let node = Node::try_pair_list(
1130 CurvePrecompile::msm_tag(),
1131 vec![(generator.digest(), scalar.digest())],
1132 )
1133 .expect("tag is curve-owned");
1134
1135 assert_invalid_payload(evaluate(&mut state, node));
1136 }
1137
1138 #[test]
1139 fn msm_rejects_non_curve_point_node() {
1140 let mut state = state();
1141 let curve = CurveId::Secp256k1;
1142 let point = UintPrecompile::value_node(curve.base_domain(), [1, 0, 0, 0, 0, 0, 0, 0]);
1143 let scalar = UintPrecompile::value_node(curve.scalar_domain(), [2, 0, 0, 0, 0, 0, 0, 0]);
1144 state.register(point.clone()).expect("point placeholder must register");
1145 state.register(scalar.clone()).expect("scalar must register");
1146 let node = Node::try_pair_list(
1147 CurvePrecompile::msm_tag(),
1148 vec![(point.digest(), scalar.digest())],
1149 )
1150 .expect("tag is curve-owned");
1151
1152 assert_invalid_payload(evaluate(&mut state, node));
1153 }
1154
1155 #[test]
1156 fn mixed_true_payload_is_invalid() {
1157 let identity = CurvePrecompile::identity_node(CurveId::Secp256k1);
1158 let node = CurvePrecompile::affine_node_from_digests(
1159 CurveId::Secp256k1,
1160 TRUE_DIGEST,
1161 identity.digest(),
1162 );
1163 let mut state = state();
1164
1165 assert_invalid_payload(evaluate(&mut state, node));
1166 }
1167
1168 #[test]
1169 fn affine_value_rejects_scalar_field_coordinate_nodes() {
1170 let curve = CurveId::Secp256k1;
1171 let x = UintPrecompile::value_node(UintDomain::K1Scalar, Secp256k1::GENERATOR_X);
1172 let y = UintPrecompile::value_node(UintDomain::K1Scalar, Secp256k1::GENERATOR_Y);
1173 let point = CurvePrecompile::affine_node_from_digests(curve, x.digest(), y.digest());
1174 let mut state = state();
1175 state.register(x).expect("x coordinate must register");
1176 state.register(y).expect("y coordinate must register");
1177
1178 assert_invalid_payload(evaluate(&mut state, point));
1179 }
1180
1181 #[test]
1182 fn off_curve_affine_value_fails_at_registration() {
1183 let curve = CurveId::Secp256k1;
1184 let x = UintPrecompile::value_node(curve.base_domain(), [1, 0, 0, 0, 0, 0, 0, 0]);
1185 let y = UintPrecompile::value_node(curve.base_domain(), [1, 0, 0, 0, 0, 0, 0, 0]);
1186 let point = CurvePrecompile::affine_node_from_digests(curve, x.digest(), y.digest());
1187 let mut state = state();
1188 state.register(x).expect("x coordinate must register");
1189 state.register(y).expect("y coordinate must register");
1190
1191 assert_invalid_payload(state.register(point));
1192 }
1193
1194 #[test]
1195 fn mul_scalar_two_generator_matches_hardcoded_known_answers() {
1196 const K1_2G_X: Limbs = [
1197 0x5c70_9ee5,
1198 0xabac_09b9,
1199 0x8cef_3ca7,
1200 0x5c77_8e4b,
1201 0x95c0_7cd8,
1202 0x3045_406e,
1203 0x41ed_7d6d,
1204 0xc604_7f94,
1205 ];
1206 const K1_2G_Y: Limbs = [
1207 0x50cf_e52a,
1208 0x2364_31a9,
1209 0x3266_d0e1,
1210 0xf7f6_3265,
1211 0x466c_eaee,
1212 0xa3c5_8419,
1213 0xa63d_c339,
1214 0x1ae1_68fe,
1215 ];
1216 let curve = CurveId::Secp256k1;
1217 let (x, y) =
1218 affine_limbs(curve.mul_scalar(curve.generator(), [2, 0, 0, 0, 0, 0, 0, 0]).unwrap());
1219 assert_eq!((x, y), (K1_2G_X, K1_2G_Y));
1220 }
1221
1222 #[test]
1223 fn fixed_curve_public_pointers_and_generators_validate() {
1224 for curve in CurveId::ALL {
1225 assert_eq!(CurveId::from_group_ptr(curve.group_ptr()), Some(curve));
1226 assert!(curve.is_on_curve(&curve.generator()));
1227 assert!(curve.scalar_domain().is_prime_field());
1228 }
1229 assert_eq!(CurveId::from_group_ptr(99), None);
1230 assert!(!K1Scalar::is_canonical(&K1Scalar::MODULUS));
1231
1232 assert_eq!(
1233 curve_coefficients(),
1234 [
1235 CurveCoefficient {
1236 ptr: CurveId::Secp256k1.a_ptr(),
1237 bound_ptr: CurveId::Secp256k1.base_domain().bound_ptr(),
1238 value: CurveId::Secp256k1.a_value(),
1239 },
1240 CurveCoefficient {
1241 ptr: CurveId::Secp256k1.b_ptr(),
1242 bound_ptr: CurveId::Secp256k1.base_domain().bound_ptr(),
1243 value: CurveId::Secp256k1.b_value(),
1244 },
1245 ],
1246 );
1247 }
1248
1249 #[test]
1250 fn mul_scalar_matches_affine_double_and_add_reference() {
1251 fn affine_mul_scalar(curve: CurveId, point: CurvePoint, scalar: Limbs) -> CurvePoint {
1254 let Some(highest_limb) = scalar.iter().rposition(|&limb| limb != 0) else {
1255 return CurvePoint::Identity;
1256 };
1257 let highest_bit =
1258 highest_limb * 32 + (u32::BITS - 1 - scalar[highest_limb].leading_zeros()) as usize;
1259
1260 let mut acc = CurvePoint::Identity;
1261 let mut base = point;
1262 for bit_index in 0..=highest_bit {
1263 if ((scalar[bit_index / 32] >> (bit_index % 32)) & 1) == 1 {
1264 acc = curve.add(acc, base).expect("affine add");
1265 }
1266 if bit_index != highest_bit {
1267 base = curve.add(base, base).expect("affine double");
1268 }
1269 }
1270 acc
1271 }
1272
1273 fn next_u32(state: &mut u64) -> u32 {
1274 *state = state
1275 .wrapping_mul(6_364_136_223_846_793_005)
1276 .wrapping_add(1_442_695_040_888_963_407);
1277 (*state >> 32) as u32
1278 }
1279
1280 let mut state: u64 = 0xdead_beef_cafe_f00d;
1281 for curve in CurveId::ALL {
1282 let modulus = match curve {
1283 CurveId::Secp256k1 => K1Scalar::MODULUS,
1284 };
1285 let minus_one = match curve {
1286 CurveId::Secp256k1 => K1Scalar::minus_one(),
1287 };
1288
1289 let generator = curve.generator();
1293 let generator_128 = affine_mul_scalar(curve, generator, [0, 0, 0, 0, 1, 0, 0, 0]);
1294 let sum = curve.add(generator, generator_128).expect("valid point");
1295 let points = [generator, generator_128, sum];
1296
1297 let edges = [ZERO_LIMBS, [1, 0, 0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0, 0, 0], minus_one];
1299
1300 for point in points {
1301 for scalar in edges {
1302 assert_eq!(
1303 curve.mul_scalar(point, scalar).expect("projective mul_scalar"),
1304 affine_mul_scalar(curve, point, scalar),
1305 "{curve:?} mul_scalar edge mismatch for scalar {scalar:?}",
1306 );
1307 }
1308
1309 for _ in 0..40 {
1310 let mut scalar = [0u32; 8];
1311 for limb in scalar.iter_mut() {
1312 *limb = next_u32(&mut state);
1313 }
1314 scalar[7] %= modulus[7];
1317 assert!(curve.scalar_domain().is_canonical(&scalar));
1318
1319 assert_eq!(
1320 curve.mul_scalar(point, scalar).expect("projective mul_scalar"),
1321 affine_mul_scalar(curve, point, scalar),
1322 "{curve:?} mul_scalar random mismatch for scalar {scalar:?}",
1323 );
1324 }
1325 }
1326 }
1327 }
1328}