From 735e31ad99bc87621f68dc99806743f64c5128a6 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Fri, 23 Oct 2026 11:39:07 -0500 Subject: [PATCH] pcurves: fix addMixed for x = 0 On P-256 and P-384 (a = -3), the curve equation at x = 0 reduces to y^2 = B. B is a quadratic residue mod p on both curves, so the points (0, +/-sqrt(B)) exist, are valid, and lie in the prime-order group since the cofactor is 1. The final select only checks , so for q = (0, +/-sqrt(B)) it discards the correct sum and returns p. --- lib/std/crypto/pcurves/p256.zig | 2 +- lib/std/crypto/pcurves/p384.zig | 2 +- lib/std/crypto/pcurves/tests/p256.zig | 15 +++++++++++++++ lib/std/crypto/pcurves/tests/p384.zig | 15 +++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/std/crypto/pcurves/p256.zig b/lib/std/crypto/pcurves/p256.zig index 1b5469d8f582cb54b5fd3c8bc455840ce9e63aa1..803e0f167c73a70197613483ae70f14eab8d6563 100644 --- a/lib/std/crypto/pcurves/p256.zig +++ b/lib/std/crypto/pcurves/p256.zig @@ -215,7 +215,7 @@ pub const P256 = struct { .y = Y3, .z = Z3, }; - ret.cMov(p, @intFromBool(q.x.isZero())); + ret.cMov(p, @intFromBool(q.x.isZero() and q.y.isZero())); return ret; } diff --git a/lib/std/crypto/pcurves/p384.zig b/lib/std/crypto/pcurves/p384.zig index 7b97bf92b1b5ab138659e1348822d06885017fc5..c97d3c4a86cc93c7e10725e45971526072fd5205 100644 --- a/lib/std/crypto/pcurves/p384.zig +++ b/lib/std/crypto/pcurves/p384.zig @@ -215,7 +215,7 @@ pub const P384 = struct { .y = Y3, .z = Z3, }; - ret.cMov(p, @intFromBool(q.x.isZero())); + ret.cMov(p, @intFromBool(q.x.isZero() and q.y.isZero())); return ret; } diff --git a/lib/std/crypto/pcurves/tests/p256.zig b/lib/std/crypto/pcurves/tests/p256.zig index 15ee5c99b75740d0912ad7a5e4ccb5e7ea31ab0d..27a435ec8fc59e9d02e779774ee818cd591d959a 100644 --- a/lib/std/crypto/pcurves/tests/p256.zig +++ b/lib/std/crypto/pcurves/tests/p256.zig @@ -145,3 +145,18 @@ test "p256 scalar parity" { try std.testing.expect(P256.scalar.Scalar.one.isOdd()); try std.testing.expect(P256.scalar.Scalar.one.dbl().isOdd() == false); } + +test "p256 with x = 0 point" { + const yh = "66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f4"; + var ys: [32]u8 = undefined; + _ = try fmt.hexToBytes(&ys, yh); + const y = try P256.Fe.fromBytes(ys, .big); + + const p = P256.basePoint.dbl(); + const q = try P256.fromAffineCoordinates(.{ .x = .zero, .y = y }); + const want = p.add(q); + const got = p.addMixed(.{ .x = .zero, .y = y }); + + try std.testing.expect(!got.equivalent(p)); + try std.testing.expect(got.equivalent(want)); +} diff --git a/lib/std/crypto/pcurves/tests/p384.zig b/lib/std/crypto/pcurves/tests/p384.zig index 2cbbb96b3eb21d32328e97c86536f2f73d905c6b..034741b37e4f97516ae7aea2109ca74a3e114ad0 100644 --- a/lib/std/crypto/pcurves/tests/p384.zig +++ b/lib/std/crypto/pcurves/tests/p384.zig @@ -155,3 +155,18 @@ test "p384 scalar parity" { try std.testing.expect(P384.scalar.Scalar.one.isOdd()); try std.testing.expect(P384.scalar.Scalar.one.dbl().isOdd() == false); } + +test "p384 with x = 0 point" { + const yh = "c306610fb0ae5a159cf45c06069f22a6c5eb3641c602d42dea2c4b4f75550793406d80d2b91ad54f9048bd487af1ade1"; + var ys: [48]u8 = undefined; + _ = try fmt.hexToBytes(&ys, yh); + const y = try P384.Fe.fromBytes(ys, .big); + + const p = P384.basePoint.dbl(); + const q = try P384.fromAffineCoordinates(.{ .x = .zero, .y = y }); + const want = p.add(q); + const got = p.addMixed(.{ .x = .zero, .y = y }); + + try std.testing.expect(!got.equivalent(p)); + try std.testing.expect(got.equivalent(want)); +} -- 2.54.0