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