From 26af03f7b3dbe963576dc35dbddddbe5a1b39b3e Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 7 May 2019 22:56:08 -0700 Subject: [PATCH] internal/ed25519: add lookup tables for scalar mul. --- internal/edwards25519/edwards25519.go | 33 +++++++ internal/edwards25519/tables.go | 129 +++++++++++++++++++++++++ internal/edwards25519/tables_test.go | 131 ++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 internal/edwards25519/tables.go create mode 100644 internal/edwards25519/tables_test.go diff --git a/internal/edwards25519/edwards25519.go b/internal/edwards25519/edwards25519.go index 831391e..f202662 100644 --- a/internal/edwards25519/edwards25519.go +++ b/internal/edwards25519/edwards25519.go @@ -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 +} diff --git a/internal/edwards25519/tables.go b/internal/edwards25519/tables.go new file mode 100644 index 0000000..de6ced8 --- /dev/null +++ b/internal/edwards25519/tables.go @@ -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] +} diff --git a/internal/edwards25519/tables_test.go b/internal/edwards25519/tables_test.go new file mode 100644 index 0000000..0a61e80 --- /dev/null +++ b/internal/edwards25519/tables_test.go @@ -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") + } +}