internal/radix51: make reduction an invariant and unexport Reduce

Now every operation returns a light-reduced value, so the reduction is
an invariant, and there's no need to ever explicitly call Reduce.

Safety!
This commit is contained in:
Filippo Valsorda 2019-03-30 22:01:30 -04:00 committed by George Tankersley
parent ce6d218ef3
commit 620415daa4
6 changed files with 56 additions and 107 deletions

View File

@ -74,7 +74,6 @@ func (curve ed25519Curve) IsOnCurve(x, y *big.Int) bool {
lh.Neg(&lh) // -x^2
lh.Add(&lh, &y2) // -x^2 + y^2
lh.Sub(&lh, &rh) // -x^2 + y^2 - 1 - dx^2y^2
lh.Reduce(&lh) // mod p
return lh.Equal(radix51.Zero) == 1
}

View File

@ -84,7 +84,7 @@ func TestAliasing(t *testing.T) {
{name: "Abs", oneArgF: (*FieldElement).Abs},
{name: "Invert", oneArgF: (*FieldElement).Invert},
{name: "Neg", oneArgF: (*FieldElement).Neg},
{name: "Reduce", oneArgF: (*FieldElement).Reduce},
{name: "reduce", oneArgF: (*FieldElement).reduce},
{name: "Set", oneArgF: (*FieldElement).Set},
{name: "Square", oneArgF: (*FieldElement).Square},
{

View File

@ -35,51 +35,53 @@ var (
// Zero sets v = 0 and returns v.
func (v *FieldElement) Zero() *FieldElement {
v[0] = 0
v[1] = 0
v[2] = 0
v[3] = 0
v[4] = 0
*v = *Zero
return v
}
// One sets v = 1 and returns v.
func (v *FieldElement) One() *FieldElement {
v[0] = 1
v[1] = 0
v[2] = 0
v[3] = 0
v[4] = 0
*v = *One
return v
}
// Reduce reduces v modulo 2^255 - 19 and returns it.
func (v *FieldElement) Reduce(u *FieldElement) *FieldElement {
v.Set(u)
// Lev v = v[0] + v[1]*2^51 + v[2]*2^102 + v[3]*2^153 + v[4]*2^204
// Reduce each limb below 2^51, propagating carries.
// lightReduce brings the limbs below 52, 51, 51, 51, 51 bits. It is split in
// two because the inliner works actively against us. The two functions MUST be
// called one after the other.
func (v *FieldElement) lightReduce1() *FieldElement {
v[1] += v[0] >> 51
v[0] = v[0] & maskLow51Bits
v[2] += v[1] >> 51
v[1] = v[1] & maskLow51Bits
v[3] += v[2] >> 51
return v
}
func (v *FieldElement) lightReduce2() *FieldElement {
v[2] = v[2] & maskLow51Bits
v[4] += v[3] >> 51
v[3] = v[3] & maskLow51Bits
v[0] += (v[4] >> 51) * 19
v[4] = v[4] & maskLow51Bits
return v
}
// We now hate a field element v < 2^255, but need v <= 2^255-19
// TODO Document why this works. It's the elaborate comment about r = h-pq etc etc.
// reduce reduces v modulo 2^255 - 19 and returns it.
func (v *FieldElement) reduce(u *FieldElement) *FieldElement {
v.Set(u).lightReduce1().lightReduce2()
// Get the carry bit
// After the light reduction we now have a field element representation
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
c := (v[0] + 19) >> 51
c = (v[1] + c) >> 51
c = (v[2] + c) >> 51
c = (v[3] + c) >> 51
c = (v[4] + c) >> 51
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
// effectively applying the reduction identity to the carry.
v[0] += 19 * c
v[1] += v[0] >> 51
@ -107,35 +109,19 @@ func (v *FieldElement) Add(a, b *FieldElement) *FieldElement {
v[2] = a[2] + b[2]
v[3] = a[3] + b[3]
v[4] = a[4] + b[4]
return v
return v.lightReduce1().lightReduce2()
}
// Sub sets v = a - b and returns v.
func (v *FieldElement) Sub(a, b *FieldElement) *FieldElement {
t := *b
// Reduce each limb below 2^51, propagating carries. Ensures that results
// fit within the limbs. This would not be required for reduced input.
t[1] += t[0] >> 51
t[0] = t[0] & maskLow51Bits
t[2] += t[1] >> 51
t[1] = t[1] & maskLow51Bits
t[3] += t[2] >> 51
t[2] = t[2] & maskLow51Bits
t[4] += t[3] >> 51
t[3] = t[3] & maskLow51Bits
t[0] += (t[4] >> 51) * 19
t[4] = t[4] & maskLow51Bits
// This is slightly more complicated. Because we use unsigned coefficients, we
// first add a multiple of p and then subtract.
v[0] = (a[0] + 0xFFFFFFFFFFFDA) - t[0]
v[1] = (a[1] + 0xFFFFFFFFFFFFE) - t[1]
v[2] = (a[2] + 0xFFFFFFFFFFFFE) - t[2]
v[3] = (a[3] + 0xFFFFFFFFFFFFE) - t[3]
v[4] = (a[4] + 0xFFFFFFFFFFFFE) - t[4]
return v
// We first add 2 * p, to guarantee the subtraction won't underflow, and
// then subtract b (which can be up to 2^255 + 2^13 * 19).
v[0] = (a[0] + 0xFFFFFFFFFFFDA) - b[0]
v[1] = (a[1] + 0xFFFFFFFFFFFFE) - b[1]
v[2] = (a[2] + 0xFFFFFFFFFFFFE) - b[2]
v[3] = (a[3] + 0xFFFFFFFFFFFFE) - b[3]
v[4] = (a[4] + 0xFFFFFFFFFFFFE) - b[4]
return v.lightReduce1().lightReduce2()
}
// Neg sets v = -a and returns v.
@ -241,7 +227,7 @@ func (v *FieldElement) FromBytes(x []byte) *FieldElement {
// Bytes appends a 32 bytes little-endian encoding of v to b.
func (v *FieldElement) Bytes(b []byte) []byte {
t := new(FieldElement).Reduce(v)
t := new(FieldElement).reduce(v)
res, out := sliceForAppend(b, 32)
for i := range out {

View File

@ -8,20 +8,17 @@ package radix51
// Mul sets v = x * y and returns v.
func (v *FieldElement) Mul(x, y *FieldElement) *FieldElement {
var x0, x1, x2, x3, x4 uint64
var y0, y1, y2, y3, y4 uint64
x0 := x[0]
x1 := x[1]
x2 := x[2]
x3 := x[3]
x4 := x[4]
x0 = x[0]
x1 = x[1]
x2 = x[2]
x3 = x[3]
x4 = x[4]
y0 = y[0]
y1 = y[1]
y2 = y[2]
y3 = y[3]
y4 = y[4]
y0 := y[0]
y1 := y[1]
y2 := y[2]
y3 := y[3]
y4 := y[4]
// Reduction can be carried out simultaneously to multiplication. For
// example, we do not compute a coefficient r_5 . Whenever the result of a
@ -106,22 +103,6 @@ func (v *FieldElement) Mul(x, y *FieldElement) *FieldElement {
// r_0 to r_1 , from r_1 to r_2 , from r_2 to r_3 , from r_3 to r_4 , and
// finally from r_4 to r_0 . Each of these carries is done as one copy, one
// right shift by 51, one logical and with 2^51 1, and one addition.
r10 += r00 >> 51
r00 &= maskLow51Bits
r20 += r10 >> 51
r10 &= maskLow51Bits
r30 += r20 >> 51
r20 &= maskLow51Bits
r40 += r30 >> 51
r30 &= maskLow51Bits
r00 += (r40 >> 51) * 19
r40 &= maskLow51Bits
v[0] = r00
v[1] = r10
v[2] = r20
v[3] = r30
v[4] = r40
return v
*v = FieldElement{r00, r10, r20, r30, r40}
return v.lightReduce1().lightReduce2()
}

View File

@ -12,13 +12,11 @@ func (v *FieldElement) Square(x *FieldElement) *FieldElement {
// this is combined with multiplication by 19 where possible. The coefficient
// reduction after squaring is the same as for multiplication.
var x0, x1, x2, x3, x4 uint64
x0 = x[0]
x1 = x[1]
x2 = x[2]
x3 = x[3]
x4 = x[4]
x0 := x[0]
x1 := x[1]
x2 := x[2]
x3 := x[3]
x4 := x[4]
x0_2 := x0 << 1
x1_2 := x1 << 1
@ -79,21 +77,6 @@ func (v *FieldElement) Square(x *FieldElement) *FieldElement {
r41 *= 19
r00 += r41
r10 += r00 >> 51
r00 &= maskLow51Bits
r20 += r10 >> 51
r10 &= maskLow51Bits
r30 += r20 >> 51
r20 &= maskLow51Bits
r40 += r30 >> 51
r30 &= maskLow51Bits
r00 += (r40 >> 51) * 19
r40 &= maskLow51Bits
v[0] = r00
v[1] = r10
v[2] = r20
v[3] = r30
v[4] = r40
return v
*v = FieldElement{r00, r10, r20, r30, r40}
return v.lightReduce1().lightReduce2()
}

View File

@ -145,10 +145,10 @@ func TestFromBytesRoundTrip(t *testing.T) {
r.FromBytes(out[:])
// Intentionally not using Equal not to go through Bytes again.
// Calling Reduce because both Generate and FromBytes can produce
// Calling reduce because both Generate and FromBytes can produce
// non-canonical representations.
fe.Reduce(&fe)
r.Reduce(&r)
fe.reduce(&fe)
r.reduce(&r)
return fe == r
}
if err := quick.Check(f2, nil); err != nil {
@ -287,7 +287,7 @@ func TestFeInvert(t *testing.T) {
xinv.Invert(&x)
r.Mul(&x, &xinv)
r.Reduce(&r)
r.reduce(&r)
if !vartimeEqual(one, r) {
t.Errorf("inversion identity failed, got: %x", r)
@ -303,7 +303,7 @@ func TestFeInvert(t *testing.T) {
xinv.Invert(&x)
r.Mul(&x, &xinv)
r.Reduce(&r)
r.reduce(&r)
if !vartimeEqual(one, r) {
t.Errorf("random inversion identity failed, got: %x for field element %x", r, x)