From 0aee554caa3c0d2f7e31e11c66209da92116c6d1 Mon Sep 17 00:00:00 2001 From: Ihor Antonov Date: Fri, 25 Sep 2026 13:49:23 -0700 Subject: [PATCH] core/crypto/_fiat/field_p256r1: fix fe_sqrt addition chain base Step 4 (t0 = x^0xc) passed x^1 (&xx) instead of x^3 (out1), producing an incorrect exponent and rejecting valid quadratic residues. This broke compressed SEC1 P-256 point decoding via pt_set_sec_bytes. --- core/crypto/_fiat/field_p256r1/field.odin | 2 +- .../crypto/test_core_crypto_weierstrass.odin | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/core/crypto/_fiat/field_p256r1/field.odin b/core/crypto/_fiat/field_p256r1/field.odin index fcb7357a7..112810659 100644 --- a/core/crypto/_fiat/field_p256r1/field.odin +++ b/core/crypto/_fiat/field_p256r1/field.odin @@ -235,7 +235,7 @@ fe_sqrt :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) -> fe_mul(out1, &xx, out1) // Step 4: t0 = x^0xc - fe_pow2k(&t0, &xx, 2) + fe_pow2k(&t0, out1, 2) // Step 5: z = x^0xf fe_mul(out1, out1, &t0) diff --git a/tests/core/crypto/test_core_crypto_weierstrass.odin b/tests/core/crypto/test_core_crypto_weierstrass.odin index dcb17aa3d..7b2cb6ccd 100644 --- a/tests/core/crypto/test_core_crypto_weierstrass.odin +++ b/tests/core/crypto/test_core_crypto_weierstrass.odin @@ -919,6 +919,42 @@ test_p256_s11n_sec_generator :: proc(t: ^testing.T) { testing.expect(t, ec.pt_equal(&g, &p) == 1) } +@(test) +test_p256_fe_sqrt_quadratic_residue :: proc(t: ^testing.T) { + for xv in u64(1) ..= u64(1024) { + xb: [32]byte + xb[30] = byte(xv >> 8) + xb[31] = byte(xv) + + x, yy, root, check: ec.Field_Element_p256r1 + ok := ec.fe_set_bytes(&x, xb[:]) + testing.expect(t, ok) + + ec.fe_square(&yy, &x) + root_ok := ec.fe_sqrt(&root, &yy) + testing.expectf(t, root_ok == 1, "fe_sqrt failed for x = %v", xv) + + ec.fe_square(&check, &root) + testing.expectf(t, ec.fe_equal(&check, &yy) == 1, "root^2 != x^2 for x = %v", xv) + } +} + +@(test) +test_p256_s11n_sec_compressed_generator :: proc(t: ^testing.T) { + p, g: ec.Point_p256r1 + + ec.pt_generator(&g) + + b: [33]byte + ok := ec.pt_sec_bytes(b[:], &g, true) + testing.expect(t, ok) + testing.expect(t, b[0] == 0x03) + + ok = ec.pt_set_sec_bytes(&p, b[:]) + testing.expect(t, ok) + testing.expect(t, ec.pt_equal(&g, &p) == 1) +} + @(test) test_p256_sc_inv :: proc(t: ^testing.T) { if crypto.HAS_RAND_BYTES == false {