From 3aa304664057732806f8f7f16105e4e96ff2e741 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Mon, 12 Oct 2026 14:40:06 +0300 Subject: [PATCH 1/4] fix: non-canonical field-element encodings --- simf/lib/secp256k1/operations.simf | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/simf/lib/secp256k1/operations.simf b/simf/lib/secp256k1/operations.simf index b4a2fcd..5316eea 100644 --- a/simf/lib/secp256k1/operations.simf +++ b/simf/lib/secp256k1/operations.simf @@ -1,5 +1,5 @@ use crate::lib::binary::and; -use crate::lib::asserts::{assert_eq_1, assert_eq_256}; +use crate::lib::asserts::assert_eq_1; /// Compress an affine point to `(parity, x)`, where `parity = 1` iff `y` is odd. pub fn ge_to_point(p: Ge) -> Point { @@ -53,11 +53,11 @@ pub fn ge_eq(a: Ge, b: Ge) -> bool { and(fe_eq(ax, bx), fe_eq(ay, by)) } -/// Check if two points are equal. +/// Compressed point equality, with `x` compared modulo `p`. pub fn point_point_eq(a: Point, b: Point) -> bool { - let (ap, ax): (u1, u256) = a; - let (bp, bx): (u1, u256) = b; - and(jet::eq_256(ax, bx), jet::eq_1(ap, bp)) + let (ap, ax): (u1, Fe) = a; + let (bp, bx): (u1, Fe) = b; + and(fe_eq(ax, bx), jet::eq_1(ap, bp)) } /// Check if two points represent the same point @@ -84,12 +84,12 @@ pub fn assert_ge_eq(a: Ge, b: Ge) { assert_fe_eq(ay, by); } -/// Check if two points are equal. +/// Compressed point equality, with `x` compared modulo `p`. pub fn assert_point_eq(a: Point, b: Point) { - let (ap, ax): (u1, u256) = a; - let (bp, bx): (u1, u256) = b; + let (ap, ax): (u1, Fe) = a; + let (bp, bx): (u1, Fe) = b; - assert_eq_256(ax, bx); + assert_fe_eq(ax, bx); assert_eq_1(ap, bp); } From 19438962ce3451e841c0b0855e8a1fb78ea42dfe Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Mon, 12 Oct 2026 14:55:07 +0300 Subject: [PATCH 2/4] fix: gej_point_eq aborts instead of returning false for an invalid compressed point --- simf/lib/secp256k1/operations.simf | 14 ++++++++++++-- tests/stdlib/secp256k1/operations.rs | 9 +++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/simf/lib/secp256k1/operations.simf b/simf/lib/secp256k1/operations.simf index 5316eea..31e55c0 100644 --- a/simf/lib/secp256k1/operations.simf +++ b/simf/lib/secp256k1/operations.simf @@ -12,11 +12,17 @@ pub fn ge_to_point(p: Ge) -> Point { } /// Decompress a `Point` into a Jacobian point with `z = 1`. +/// +/// ## Panics +/// - if `x` is not the x-coordinate of a curve point pub fn point_to_gej(p: Point) -> Gej { (unwrap(jet::decompress(p)), 1) } /// Convert the point into affine coordinates. +/// +/// ## Panics +/// - if `p` is the point at infinity pub fn safe_gej_normalize(p: Gej) -> Ge { unwrap(jet::gej_normalize(p)) } @@ -60,9 +66,13 @@ pub fn point_point_eq(a: Point, b: Point) -> bool { and(fe_eq(ax, bx), jet::eq_1(ap, bp)) } -/// Check if two points represent the same point +/// Check if two points represent the same point. +/// Returns `false` if `p` does not decompress to a curve point. pub fn gej_point_eq(a: Gej, p: Point) -> bool { - jet::gej_ge_equiv(a, unwrap(jet::decompress(p))) + match jet::decompress(p) { + Some(ge: Ge) => jet::gej_ge_equiv(a, ge), + None => false, + } } /// Field equality modulo `p`. diff --git a/tests/stdlib/secp256k1/operations.rs b/tests/stdlib/secp256k1/operations.rs index 673dedf..cfbc4ef 100644 --- a/tests/stdlib/secp256k1/operations.rs +++ b/tests/stdlib/secp256k1/operations.rs @@ -399,6 +399,15 @@ fn gej_point_eq_rejects_negation(context: simplex::TestContext) -> anyhow::Resul .expecting(&context, Expect::AssertFailed) } +#[simplex::test] +fn gej_point_eq_off_curve_is_false(context: simplex::TestContext) -> anyhow::Result<()> { + // x = 0 is off-curve. + case(GejPointEq) + .gej(ge_to_gej(random_ge_bytes())) + .point((0, [0u8; 32])) + .expecting(&context, Expect::AssertFailed) +} + // 9. safe_gej_normalize #[simplex::test] fn safe_gej_normalize_roundtrip(context: simplex::TestContext) -> anyhow::Result<()> { From dc92cf9312e9fdbd8bcadaccaed4a3436ecd2cbf Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Mon, 12 Oct 2026 15:33:41 +0300 Subject: [PATCH 3/4] fix: Jacobian point equality accepts off-curve witness points --- simf/lib/secp256k1/operations.simf | 6 ++++++ tests/stdlib/secp256k1/operations.rs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/simf/lib/secp256k1/operations.simf b/simf/lib/secp256k1/operations.simf index 31e55c0..da693b1 100644 --- a/simf/lib/secp256k1/operations.simf +++ b/simf/lib/secp256k1/operations.simf @@ -109,11 +109,17 @@ pub fn assert_gej_point_eq(a: Gej, p: Point) { } /// Assert two points represent the same curve point. +/// Both points must be on the curve: `gej_equiv` only rejects `y = -b.y` for a matching `x`. pub fn assert_gej_eq(a: Gej, b: Gej) { + assert!(jet::gej_is_on_curve(a)); + assert!(jet::gej_is_on_curve(b)); assert!(jet::gej_equiv(a, b)); } /// Assert point equal an affine point (no normalize required). +/// Both points must be on the curve: `gej_ge_equiv` only rejects `y = -b.y` for a matching `x`. pub fn assert_gej_ge_eq(a: Gej, b: Ge) { + assert!(jet::gej_is_on_curve(a)); + assert!(jet::ge_is_on_curve(b)); assert!(jet::gej_ge_equiv(a, b)); } diff --git a/tests/stdlib/secp256k1/operations.rs b/tests/stdlib/secp256k1/operations.rs index cfbc4ef..3bc6798 100644 --- a/tests/stdlib/secp256k1/operations.rs +++ b/tests/stdlib/secp256k1/operations.rs @@ -408,6 +408,20 @@ fn gej_point_eq_off_curve_is_false(context: simplex::TestContext) -> anyhow::Res .expecting(&context, Expect::AssertFailed) } +#[simplex::test] +fn gej_point_eq_rejects_off_curve_gej(context: simplex::TestContext) -> anyhow::Result<()> { + // (x, 1, 1) shares x with P but is off-curve. The equivalence jet only + // rejects y = -P.y, so without an on-curve check this would pass. + let ge = random_ge_bytes(); + let mut one = [0u8; 32]; + one[31] = 1; + + case(GejPointEq) + .gej(((ge.0, one), one)) + .point(compress(ge)) + .expecting(&context, Expect::AssertFailed) +} + // 9. safe_gej_normalize #[simplex::test] fn safe_gej_normalize_roundtrip(context: simplex::TestContext) -> anyhow::Result<()> { From 406894c94e3e3abe744ade5cb625694ac33fcfdf Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Mon, 12 Oct 2026 16:45:04 +0300 Subject: [PATCH 4/4] feat: optimize get_point_eq function --- simf/lib/secp256k1/operations.simf | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/simf/lib/secp256k1/operations.simf b/simf/lib/secp256k1/operations.simf index da693b1..4a1fe85 100644 --- a/simf/lib/secp256k1/operations.simf +++ b/simf/lib/secp256k1/operations.simf @@ -67,12 +67,17 @@ pub fn point_point_eq(a: Point, b: Point) -> bool { } /// Check if two points represent the same point. -/// Returns `false` if `p` does not decompress to a curve point. +/// Returns `false` if `a` is off-curve or `p` does not decompress to a curve point. pub fn gej_point_eq(a: Gej, p: Point) -> bool { - match jet::decompress(p) { - Some(ge: Ge) => jet::gej_ge_equiv(a, ge), - None => false, - } + let (parity, x): (u1, Fe) = p; + + // Cheaper than `decompress`: an on-curve `a` with matching `x` and `y` parity is exactly `p`. + let parity_eq: bool = match jet::gej_y_is_odd(a) { + true => jet::eq_1(parity, 1), + false => jet::eq_1(parity, 0), + }; + + and(and(jet::gej_is_on_curve(a), jet::gej_x_equiv(x, a)), parity_eq) } /// Field equality modulo `p`.