What changed, and why it matters
This commit refactors how Monero converts byte strings into elliptic-curve field elements and adds a helper function to 'reduce' oversized field elements back into a safe range. The change is defensive: it separates the low-level byte-to-field conversion from the point-decompression logic and introduces a normalization routine so that outputs of addition/subtraction can safely be reused as inputs to further additions/subtractions. There is no direct evidence in the commit message or diff of an exploitable vulnerability, but the new helper addresses a known class of subtle correctness issues in Curve25519-style field arithmetic.
Treat as a hardening/correctness improvement rather than an urgent security patch. Review callers of fe_reduce_vartime to ensure it is only used on public data, since it is variable-time. Verify that the bound-reduction semantics match the intended use in downstream commits. If this commit is part of a larger series, evaluate the combined effect on signature/verification correctness.
Security signals we found
New field-element normalization helper added to fix precondition bound mismatch between fe_add/fe_sub outputs and inputs
Refactor of ge_frombytes_vartime to use a separate fe_frombytes_vartime routine with an error return path
Vartime implementation suggests intended use on public/non-secret data only
No explicit bug fix, CVE, or security advisory referenced in commit message
Evidence from the diff
The patch extracts the fe_frombytes logic from ge_frombytes_vartime into a standalone fe_frombytes_vartime(fe, const unsigned char *), and adds fe_reduce_vartime(fe reduced_f, const fe f). The latter serializes a field element via fe_tobytes and re-parses it via fe_frombytes_vartime to bring coefficients back into the tighter bounds required by fe_add/fe_sub preconditions. This is a vartime (variable-time) reduction helper intended for non-secret data paths. The refactor also makes ge_frombytes_vartime call the new helper and return -1 on failure.
Changed components
src/crypto/crypto-ops.csrc/crypto/crypto-ops.hCurve25519/Ed25519 field-element arithmetic (fe_* routines)Point decompression (ge_frombytes_vartime)Inspect captured patch +56 / −20
diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c
index eca5c47..7592afa 100644
--- a/src/crypto/crypto-ops.c
+++ b/src/crypto/crypto-ops.c
@@ -1328,16 +1328,9 @@ void ge_double_scalarmult_base_vartime_p3(ge_p3 *r3, const unsigned char *a, con
}
}
-/* From ge_frombytes.c, modified */
-
-int ge_frombytes_vartime(ge_p3 *h, const unsigned char *s) {
- fe u;
- fe v;
- fe vxx;
- fe check;
-
- /* From fe_frombytes.c */
+/* From fe_frombytes.c */
+int fe_frombytes_vartime(fe y, const unsigned char *s) {
int64_t h0 = load_4(s);
int64_t h1 = load_3(s + 4) << 6;
int64_t h2 = load_3(s + 7) << 5;
@@ -1378,18 +1371,31 @@ int ge_frombytes_vartime(ge_p3 *h, const unsigned char *s) {
carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
- h->Y[0] = h0;
- h->Y[1] = h1;
- h->Y[2] = h2;
- h->Y[3] = h3;
- h->Y[4] = h4;
- h->Y[5] = h5;
- h->Y[6] = h6;
- h->Y[7] = h7;
- h->Y[8] = h8;
- h->Y[9] = h9;
+ y[0] = h0;
+ y[1] = h1;
+ y[2] = h2;
+ y[3] = h3;
+ y[4] = h4;
+ y[5] = h5;
+ y[6] = h6;
+ y[7] = h7;
+ y[8] = h8;
+ y[9] = h9;
- /* End fe_frombytes.c */
+ return 0;
+}
+
+/* From ge_frombytes.c, modified */
+
+int ge_frombytes_vartime(ge_p3 *h, const unsigned char *s) {
+ fe u;
+ fe v;
+ fe vxx;
+ fe check;
+
+ if (fe_frombytes_vartime(h->Y, s) != 0) {
+ return -1;
+ }
fe_1(h->Z);
fe_sq(u, h->Y);
@@ -3903,3 +3909,30 @@ int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) {
// Y/Z = 0/0
return 0;
}
+
+/*
+Preconditions:
+ |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+
+Since fe_add and fe_sub enforce the following conditions:
+
+fe_add & fe_sub preconditions:
+ |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+
+fe_add & fe_sub postconditions:
+ |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+
+We sometimes need to "reduce" field elems when they are in the postcondition's
+larger domain to match the precondition domain. This way we can take the output
+of fe_add or fe_sub and use it as input to another call to fe_add or fe_sub.
+
+We reduce by converting the field elem to its byte repr, then re-deriving the
+field elem from the byte repr.
+*/
+int fe_reduce_vartime(fe reduced_f, const fe f)
+{
+ unsigned char f_bytes[32];
+ fe_tobytes(f_bytes, f);
+ return fe_frombytes_vartime(reduced_f, f_bytes);
+}
diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h
index dcb335d..a4886ce 100644
--- a/src/crypto/crypto-ops.h
+++ b/src/crypto/crypto-ops.h
@@ -88,6 +88,7 @@ void ge_double_scalarmult_base_vartime_p3(ge_p3 *, const unsigned char *, const
extern const fe fe_sqrtm1;
extern const fe fe_d;
+int fe_frombytes_vartime(fe, const unsigned char *);
int ge_frombytes_vartime(ge_p3 *, const unsigned char *);
/* From ge_p1p1_to_p2.c */
@@ -168,3 +169,5 @@ void fe_mul(fe out, const fe, const fe);
void fe_0(fe h);
int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p);
+
+int fe_reduce_vartime(fe reduced_f, const fe f);
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.