internal/ed25519: add lookup tables for scalar mul.

This commit is contained in:
Henry de Valence 2019-05-07 22:56:08 -07:00
parent 2312dea95d
commit 26af03f7b3
3 changed files with 293 additions and 0 deletions

View File

@ -281,3 +281,36 @@ func (v *ProjP3) Equal(u *ProjP3) int {
return t1.Equal(&t2) & t3.Equal(&t4)
}
// Constant-time operations
// Select sets v to a if cond == 1 and to b if cond == 0.
func (v *ProjCached) Select(a, b *ProjCached, cond int) *ProjCached {
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
v.Z.Select(&a.Z, &b.Z, cond)
v.T2d.Select(&a.T2d, &b.T2d, cond)
return v
}
// Select sets v to a if cond == 1 and to b if cond == 0.
func (v *AffineCached) Select(a, b *AffineCached, cond int) *AffineCached {
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
v.T2d.Select(&a.T2d, &b.T2d, cond)
return v
}
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
func (v *ProjCached) CondNeg(cond int) *ProjCached {
radix51.CondSwap(&v.YplusX, &v.YminusX, cond)
v.T2d.CondNeg(&v.T2d, cond)
return v
}
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
func (v *AffineCached) CondNeg(cond int) *AffineCached {
radix51.CondSwap(&v.YplusX, &v.YminusX, cond)
v.T2d.CondNeg(&v.T2d, cond)
return v
}

View File

@ -0,0 +1,129 @@
// Copyright (c) 2019 Henry de Valence.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"crypto/subtle"
)
// A dynamic lookup table for variable-base, constant-time scalar muls.
type ProjLookupTable struct {
points [8]ProjCached
}
// A precomputed lookup table for fixed-base, constant-time scalar muls.
type AffineLookupTable struct {
points [8]AffineCached
}
// A dynamic lookup table for variable-base, variable-time scalar muls.
type NafLookupTable5 struct {
points [8]ProjCached
}
// A precomputed lookup table for fixed-base, variable-time scalar muls.
type NafLookupTable8 struct {
points [64]AffineCached
}
// Constructors.
// Builds a lookup table at runtime. Fast.
func (v *ProjLookupTable) FromP3(q *ProjP3) {
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
v.points[0].FromP3(q)
tmpP3 := ProjP3{}
tmpP1xP1 := ProjP1xP1{}
for i := 0; i < 7; i++ {
// Compute (i+1)*Q as Q + i*Q and convert to a ProjCached
// This is needlessly complicated because the API has explicit
// recievers instead of creating stack objects and relying on RVO
v.points[i+1].FromP3(tmpP3.FromP1xP1(tmpP1xP1.Add(q, &v.points[i])))
}
}
// This is not optimised for speed; affine tables should be precomputed.
func (v *AffineLookupTable) FromP3(q *ProjP3) {
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
v.points[0].FromP3(q)
tmpP3 := ProjP3{}
tmpP1xP1 := ProjP1xP1{}
for i := 0; i < 7; i++ {
// Compute (i+1)*Q as Q + i*Q and convert to AffineCached
v.points[i+1].FromP3(tmpP3.FromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i])))
}
}
// Builds a lookup table at runtime. Fast.
func (v *NafLookupTable5) FromP3(q *ProjP3) {
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
v.points[0].FromP3(q)
q2 := ProjP3{}
q2.Add(q, q)
tmpP3 := ProjP3{}
tmpP1xP1 := ProjP1xP1{}
for i := 0; i < 7; i++ {
v.points[i+1].FromP3(tmpP3.FromP1xP1(tmpP1xP1.Add(&q2, &v.points[i])))
}
}
// This is not optimised for speed; affine tables should be precomputed.
func (v *NafLookupTable8) FromP3(q *ProjP3) {
v.points[0].FromP3(q)
q2 := ProjP3{}
q2.Add(q, q)
tmpP3 := ProjP3{}
tmpP1xP1 := ProjP1xP1{}
for i := 0; i < 63; i++ {
v.points[i+1].FromP3(tmpP3.FromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i])))
}
}
// Selectors.
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
func (v *ProjLookupTable) SelectInto(dest *ProjCached, x int8) {
// Compute xabs = |x|
xmask := x >> 7
xabs := uint8((x + xmask) ^ xmask)
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
dest.CondNeg(int(xmask & 1))
}
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
func (v *AffineLookupTable) SelectInto(dest *AffineCached, x int8) {
// Compute xabs = |x|
xmask := x >> 7
xabs := uint8((x + xmask) ^ xmask)
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
dest.CondNeg(int(xmask & 1))
}
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
func (v *NafLookupTable5) SelectInto(dest *ProjCached, x int8) {
*dest = v.points[x/2]
}
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
func (v *NafLookupTable8) SelectInto(dest *AffineCached, x int8) {
*dest = v.points[x/2]
}

View File

@ -0,0 +1,131 @@
package edwards25519
import (
"github.com/gtank/ristretto255/internal/radix51"
"testing"
)
var (
B = ProjP3{
X: radix51.FieldElement([5]uint64{426475514619346, 2063872706840040, 14628272888959, 107677749330612, 288339085807592}),
Y: radix51.FieldElement([5]uint64{1934594822876571, 2049809580636559, 1991994783322914, 1758681962032007, 380046701118659}),
Z: radix51.FieldElement([5]uint64{1, 0, 0, 0, 0}),
T: radix51.FieldElement([5]uint64{410445769351754, 2235400917701188, 1495825632738689, 1351628537510093, 430502003771208}),
}
)
func TestProjLookupTable(t *testing.T) {
var table ProjLookupTable
table.FromP3(&B)
var tmp1, tmp2, tmp3 ProjCached
table.SelectInto(&tmp1, 6)
table.SelectInto(&tmp2, -2)
table.SelectInto(&tmp3, -4)
// Expect T1 + T2 + T3 = identity
var accP1xP1 ProjP1xP1
var accP3, check ProjP3
accP3.Zero()
check.Zero()
accP1xP1.Add(&accP3, &tmp1)
accP3.FromP1xP1(&accP1xP1)
accP1xP1.Add(&accP3, &tmp2)
accP3.FromP1xP1(&accP1xP1)
accP1xP1.Add(&accP3, &tmp3)
accP3.FromP1xP1(&accP1xP1)
if accP3.Equal(&check) != 1 {
t.Errorf("Sanity check on ProjLookupTable.SelectInto failed! %x %x %x", tmp1, tmp2, tmp3)
}
}
func TestAffineLookupTable(t *testing.T) {
var table AffineLookupTable
table.FromP3(&B)
var tmp1, tmp2, tmp3 AffineCached
table.SelectInto(&tmp1, 3)
table.SelectInto(&tmp2, -7)
table.SelectInto(&tmp3, 4)
// Expect T1 + T2 + T3 = identity
var accP1xP1 ProjP1xP1
var accP3, check ProjP3
accP3.Zero()
check.Zero()
accP1xP1.AddAffine(&accP3, &tmp1)
accP3.FromP1xP1(&accP1xP1)
accP1xP1.AddAffine(&accP3, &tmp2)
accP3.FromP1xP1(&accP1xP1)
accP1xP1.AddAffine(&accP3, &tmp3)
accP3.FromP1xP1(&accP1xP1)
if accP3.Equal(&check) != 1 {
t.Errorf("Sanity check on ProjLookupTable.SelectInto failed! %x %x %x", tmp1, tmp2, tmp3)
}
}
func TestNafLookupTable5(t *testing.T) {
var table NafLookupTable5
table.FromP3(&B)
var tmp1, tmp2, tmp3, tmp4 ProjCached
table.SelectInto(&tmp1, 9)
table.SelectInto(&tmp2, 11)
table.SelectInto(&tmp3, 7)
table.SelectInto(&tmp4, 13)
// Expect T1 + T2 = T3 + T4
var accP1xP1 ProjP1xP1
var lhs, rhs ProjP3
lhs.Zero()
rhs.Zero()
accP1xP1.Add(&lhs, &tmp1)
lhs.FromP1xP1(&accP1xP1)
accP1xP1.Add(&lhs, &tmp2)
lhs.FromP1xP1(&accP1xP1)
accP1xP1.Add(&rhs, &tmp3)
rhs.FromP1xP1(&accP1xP1)
accP1xP1.Add(&rhs, &tmp4)
rhs.FromP1xP1(&accP1xP1)
if lhs.Equal(&rhs) != 1 {
t.Errorf("Sanity check on NafLookupTable5 failed")
}
}
func TestNafLookupTable8(t *testing.T) {
var table NafLookupTable8
table.FromP3(&B)
var tmp1, tmp2, tmp3, tmp4 AffineCached
table.SelectInto(&tmp1, 49)
table.SelectInto(&tmp2, 11)
table.SelectInto(&tmp3, 35)
table.SelectInto(&tmp4, 25)
// Expect T1 + T2 = T3 + T4
var accP1xP1 ProjP1xP1
var lhs, rhs ProjP3
lhs.Zero()
rhs.Zero()
accP1xP1.AddAffine(&lhs, &tmp1)
lhs.FromP1xP1(&accP1xP1)
accP1xP1.AddAffine(&lhs, &tmp2)
lhs.FromP1xP1(&accP1xP1)
accP1xP1.AddAffine(&rhs, &tmp3)
rhs.FromP1xP1(&accP1xP1)
accP1xP1.AddAffine(&rhs, &tmp4)
rhs.FromP1xP1(&accP1xP1)
if lhs.Equal(&rhs) != 1 {
t.Errorf("Sanity check on NafLookupTable8 failed")
}
}