This addresses part of #1001.
The magnitude and normalization of the output of secp256k1_fe_cmov
should not depend on the runtime value of flag
.
3192+ CHECK(!x.normalized);
3193+ CHECK((x.magnitude == q.magnitude) || (x.magnitude == z.magnitude));
3194+ CHECK((x.magnitude >= q.magnitude) && (x.magnitude >= z.magnitude));
3195 #endif
3196+ x = q;
3197 secp256k1_fe_cmov(&x, &x, 1);
ACK 31b4bbe.
haha, yes. surprised to learn that magnitude doesn’t need to be the actual magnitude and is just an overflow protection mechanism.
@stratospher Right! They are constraints on the (representation of) the value stored in the field element. They don’t (or at least, shouldn’t) depend on the values themselves. They exist to allow us to reason about what operations are allowed. This is all a consequence of having non-canonical represents in the first place; if we’d always normalize after every operation, they wouldn’t be needed. But we don’t, as a performance optimization, and as a consequence we want to be able to bound how “denormalized” field elements are allowed to be.
If this were C++ or Rust or some other language with more complex type reasoning, we’d put the magnitude/normalization properties in the type itself, and have their propagation checked by the compiler (e.g. adding a fieldelem of magnitude 3 to a fieldelem of magnitude 4 always yields one of magnitude 7, regardless of the values in it). The explicit runtime tracking of magnitude in VERIFY mode is a best-effort approximation of that which we can do in C.
But to the extent possible, we want the magnitude/normalization properties to be fixed at compile-time (even if not known), because that guarantees that any possible code path sticks to field elements which satisfy the constraints.