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