diff --git a/internal/edwards25519/scalarMul.go b/internal/edwards25519/scalarMul.go index 79a0179..050ebae 100644 --- a/internal/edwards25519/scalarMul.go +++ b/internal/edwards25519/scalarMul.go @@ -12,7 +12,49 @@ import ( // // The scalar multiplication is done in constant time. func (v *ProjP3) BasepointMul(x *scalar.Scalar) *ProjP3 { - panic("unimplemented") + // Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i ) + // as described in the Ed25519 paper + // + // Group even and odd coefficients + // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B + // + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B + // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B + // + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B) + // + // We use a lookup table for each i to get x_i*16^(2*i)*B + // and do four doublings to multiply by 16. + digits := x.SignedRadix16() + + multiple := &AffineCached{} + tmp1 := &ProjP1xP1{} + tmp2 := &ProjP2{} + + // Accumulate the odd components first + v.Zero() + for i := 1; i < 64; i += 2 { + basepointTable[i/2].SelectInto(multiple, digits[i]) + tmp1.AddAffine(v, multiple) + v.FromP1xP1(tmp1) + } + + // Multiply by 16 + tmp2.FromP3(v) // tmp2 = v in P2 coords + tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords + tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords + tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords + tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords + tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords + tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords + tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords + v.FromP1xP1(tmp1) // now v = 16*(odd components) + + // Accumulate the even components + for i := 0; i < 64; i += 2 { + basepointTable[i/2].SelectInto(multiple, digits[i]) + tmp1.AddAffine(v, multiple) + v.FromP1xP1(tmp1) + } + return v } diff --git a/internal/edwards25519/scalarMul_test.go b/internal/edwards25519/scalarMul_test.go index 68745ff..7a93437 100644 --- a/internal/edwards25519/scalarMul_test.go +++ b/internal/edwards25519/scalarMul_test.go @@ -48,6 +48,14 @@ func TestScalarMulVsDalek(t *testing.T) { } } +func TestBasepointMulVsDalek(t *testing.T) { + var p ProjP3 + p.BasepointMul(&dalekScalar) + if dalekScalarBasepoint.Equal(&p) != 1 { + t.Error("Scalar mul does not match dalek") + } +} + func TestScalarMulDistributesOverAdd(t *testing.T) { scalarMulDistributesOverAdd := func(x, y scalar.Scalar) bool { // The quickcheck generation strategy chooses a random @@ -99,3 +107,18 @@ func TestBasepointTableGeneration(t *testing.T) { } } + +func TestScalarMulMatchesBasepointMul(t *testing.T) { + scalarMulMatchesBasepointMul := func(x scalar.Scalar) bool { + // FIXME opaque scalars + x[31] &= 127 + var p, q ProjP3 + p.ScalarMul(&x, &B) + q.BasepointMul(&x) + return p.Equal(&q) == 1 + } + + if err := quick.Check(scalarMulMatchesBasepointMul, quickCheckConfig); err != nil { + t.Error(err) + } +}