567 lines
14 KiB
Go
567 lines
14 KiB
Go
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package field
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"io"
|
|
"math/big"
|
|
"math/bits"
|
|
mathrand "math/rand"
|
|
"reflect"
|
|
"testing"
|
|
"testing/quick"
|
|
)
|
|
|
|
func (v Element) String() string {
|
|
return hex.EncodeToString(v.Bytes())
|
|
}
|
|
|
|
// quickCheckConfig returns a quick.Config that scales the max count by the
|
|
// given factor if the -short flag is not set.
|
|
func quickCheckConfig(slowScale int) *quick.Config {
|
|
cfg := new(quick.Config)
|
|
if !testing.Short() {
|
|
cfg.MaxCountScale = float64(slowScale)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func generateFieldElement(rand *mathrand.Rand) Element {
|
|
const maskLow52Bits = (1 << 52) - 1
|
|
return Element{
|
|
rand.Uint64() & maskLow52Bits,
|
|
rand.Uint64() & maskLow52Bits,
|
|
rand.Uint64() & maskLow52Bits,
|
|
rand.Uint64() & maskLow52Bits,
|
|
rand.Uint64() & maskLow52Bits,
|
|
}
|
|
}
|
|
|
|
// weirdLimbs can be combined to generate a range of edge-case field elements.
|
|
// 0 and -1 are intentionally more weighted, as they combine well.
|
|
var (
|
|
weirdLimbs51 = []uint64{
|
|
0, 0, 0, 0,
|
|
1,
|
|
19 - 1,
|
|
19,
|
|
0x2aaaaaaaaaaaa,
|
|
0x5555555555555,
|
|
(1 << 51) - 20,
|
|
(1 << 51) - 19,
|
|
(1 << 51) - 1, (1 << 51) - 1,
|
|
(1 << 51) - 1, (1 << 51) - 1,
|
|
}
|
|
weirdLimbs52 = []uint64{
|
|
0, 0, 0, 0, 0, 0,
|
|
1,
|
|
19 - 1,
|
|
19,
|
|
0x2aaaaaaaaaaaa,
|
|
0x5555555555555,
|
|
(1 << 51) - 20,
|
|
(1 << 51) - 19,
|
|
(1 << 51) - 1, (1 << 51) - 1,
|
|
(1 << 51) - 1, (1 << 51) - 1,
|
|
(1 << 51) - 1, (1 << 51) - 1,
|
|
1 << 51,
|
|
(1 << 51) + 1,
|
|
(1 << 52) - 19,
|
|
(1 << 52) - 1,
|
|
}
|
|
)
|
|
|
|
func generateWeirdFieldElement(rand *mathrand.Rand) Element {
|
|
return Element{
|
|
weirdLimbs52[rand.Intn(len(weirdLimbs52))],
|
|
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
|
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
|
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
|
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
|
|
}
|
|
}
|
|
|
|
func (Element) Generate(rand *mathrand.Rand, size int) reflect.Value {
|
|
if rand.Intn(2) == 0 {
|
|
return reflect.ValueOf(generateWeirdFieldElement(rand))
|
|
}
|
|
return reflect.ValueOf(generateFieldElement(rand))
|
|
}
|
|
|
|
// isInBounds returns whether the element is within the expected bit size bounds
|
|
// after a light reduction.
|
|
func isInBounds(x *Element) bool {
|
|
return bits.Len64(x.l0) <= 52 &&
|
|
bits.Len64(x.l1) <= 52 &&
|
|
bits.Len64(x.l2) <= 52 &&
|
|
bits.Len64(x.l3) <= 52 &&
|
|
bits.Len64(x.l4) <= 52
|
|
}
|
|
|
|
func TestMultiplyDistributesOverAdd(t *testing.T) {
|
|
multiplyDistributesOverAdd := func(x, y, z Element) bool {
|
|
// Compute t1 = (x+y)*z
|
|
t1 := new(Element)
|
|
t1.Add(&x, &y)
|
|
t1.Multiply(t1, &z)
|
|
|
|
// Compute t2 = x*z + y*z
|
|
t2 := new(Element)
|
|
t3 := new(Element)
|
|
t2.Multiply(&x, &z)
|
|
t3.Multiply(&y, &z)
|
|
t2.Add(t2, t3)
|
|
|
|
return t1.Equal(t2) == 1 && isInBounds(t1) && isInBounds(t2)
|
|
}
|
|
|
|
if err := quick.Check(multiplyDistributesOverAdd, quickCheckConfig(1024)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestMul64to128(t *testing.T) {
|
|
a := uint64(5)
|
|
b := uint64(5)
|
|
r := mul64(a, b)
|
|
if r.lo != 0x19 || r.hi != 0 {
|
|
t.Errorf("lo-range wide mult failed, got %d + %d*(2**64)", r.lo, r.hi)
|
|
}
|
|
|
|
a = uint64(18014398509481983) // 2^54 - 1
|
|
b = uint64(18014398509481983) // 2^54 - 1
|
|
r = mul64(a, b)
|
|
if r.lo != 0xff80000000000001 || r.hi != 0xfffffffffff {
|
|
t.Errorf("hi-range wide mult failed, got %d + %d*(2**64)", r.lo, r.hi)
|
|
}
|
|
|
|
a = uint64(1125899906842661)
|
|
b = uint64(2097155)
|
|
r = mul64(a, b)
|
|
r = addMul64(r, a, b)
|
|
r = addMul64(r, a, b)
|
|
r = addMul64(r, a, b)
|
|
r = addMul64(r, a, b)
|
|
if r.lo != 16888498990613035 || r.hi != 640 {
|
|
t.Errorf("wrong answer: %d + %d*(2**64)", r.lo, r.hi)
|
|
}
|
|
}
|
|
|
|
func TestSetBytesRoundTrip(t *testing.T) {
|
|
f1 := func(in [32]byte, fe Element) bool {
|
|
fe.SetBytes(in[:])
|
|
|
|
// Mask the most significant bit as it's ignored by SetBytes. (Now
|
|
// instead of earlier so we check the masking in SetBytes is working.)
|
|
in[len(in)-1] &= (1 << 7) - 1
|
|
|
|
return bytes.Equal(in[:], fe.Bytes()) && isInBounds(&fe)
|
|
}
|
|
if err := quick.Check(f1, nil); err != nil {
|
|
t.Errorf("failed bytes->FE->bytes round-trip: %v", err)
|
|
}
|
|
|
|
f2 := func(fe, r Element) bool {
|
|
r.SetBytes(fe.Bytes())
|
|
|
|
// Intentionally not using Equal not to go through Bytes again.
|
|
// Calling reduce because both Generate and SetBytes can produce
|
|
// non-canonical representations.
|
|
fe.reduce()
|
|
r.reduce()
|
|
return fe == r
|
|
}
|
|
if err := quick.Check(f2, nil); err != nil {
|
|
t.Errorf("failed FE->bytes->FE round-trip: %v", err)
|
|
}
|
|
|
|
// Check some fixed vectors from dalek
|
|
type feRTTest struct {
|
|
fe Element
|
|
b []byte
|
|
}
|
|
var tests = []feRTTest{
|
|
{
|
|
fe: Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676},
|
|
b: []byte{74, 209, 69, 197, 70, 70, 161, 222, 56, 226, 229, 19, 112, 60, 25, 92, 187, 74, 222, 56, 50, 153, 51, 233, 40, 74, 57, 6, 160, 185, 213, 31},
|
|
},
|
|
{
|
|
fe: Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972},
|
|
b: []byte{199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
b := tt.fe.Bytes()
|
|
fe, _ := new(Element).SetBytes(tt.b)
|
|
if !bytes.Equal(b, tt.b) || fe.Equal(&tt.fe) != 1 {
|
|
t.Errorf("Failed fixed roundtrip: %v", tt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func swapEndianness(buf []byte) []byte {
|
|
for i := 0; i < len(buf)/2; i++ {
|
|
buf[i], buf[len(buf)-i-1] = buf[len(buf)-i-1], buf[i]
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func TestBytesBigEquivalence(t *testing.T) {
|
|
f1 := func(in [32]byte, fe, fe1 Element) bool {
|
|
fe.SetBytes(in[:])
|
|
|
|
in[len(in)-1] &= (1 << 7) - 1 // mask the most significant bit
|
|
b := new(big.Int).SetBytes(swapEndianness(in[:]))
|
|
fe1.fromBig(b)
|
|
|
|
if fe != fe1 {
|
|
return false
|
|
}
|
|
|
|
buf := make([]byte, 32)
|
|
buf = swapEndianness(fe1.toBig().FillBytes(buf))
|
|
|
|
return bytes.Equal(fe.Bytes(), buf) && isInBounds(&fe) && isInBounds(&fe1)
|
|
}
|
|
if err := quick.Check(f1, nil); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
// fromBig sets v = n, and returns v. The bit length of n must not exceed 256.
|
|
func (v *Element) fromBig(n *big.Int) *Element {
|
|
if n.BitLen() > 32*8 {
|
|
panic("edwards25519: invalid field element input size")
|
|
}
|
|
|
|
buf := make([]byte, 0, 32)
|
|
for _, word := range n.Bits() {
|
|
for i := 0; i < bits.UintSize; i += 8 {
|
|
if len(buf) >= cap(buf) {
|
|
break
|
|
}
|
|
buf = append(buf, byte(word))
|
|
word >>= 8
|
|
}
|
|
}
|
|
|
|
v.SetBytes(buf[:32])
|
|
return v
|
|
}
|
|
|
|
func (v *Element) fromDecimal(s string) *Element {
|
|
n, ok := new(big.Int).SetString(s, 10)
|
|
if !ok {
|
|
panic("not a valid decimal: " + s)
|
|
}
|
|
return v.fromBig(n)
|
|
}
|
|
|
|
// toBig returns v as a big.Int.
|
|
func (v *Element) toBig() *big.Int {
|
|
buf := v.Bytes()
|
|
|
|
words := make([]big.Word, 32*8/bits.UintSize)
|
|
for n := range words {
|
|
for i := 0; i < bits.UintSize; i += 8 {
|
|
if len(buf) == 0 {
|
|
break
|
|
}
|
|
words[n] |= big.Word(buf[0]) << big.Word(i)
|
|
buf = buf[1:]
|
|
}
|
|
}
|
|
|
|
return new(big.Int).SetBits(words)
|
|
}
|
|
|
|
func TestDecimalConstants(t *testing.T) {
|
|
sqrtM1String := "19681161376707505956807079304988542015446066515923890162744021073123829784752"
|
|
if exp := new(Element).fromDecimal(sqrtM1String); sqrtM1.Equal(exp) != 1 {
|
|
t.Errorf("sqrtM1 is %v, expected %v", sqrtM1, exp)
|
|
}
|
|
// d is in the parent package, and we don't want to expose d or fromDecimal.
|
|
// dString := "37095705934669439343138083508754565189542113879843219016388785533085940283555"
|
|
// if exp := new(Element).fromDecimal(dString); d.Equal(exp) != 1 {
|
|
// t.Errorf("d is %v, expected %v", d, exp)
|
|
// }
|
|
}
|
|
|
|
func TestSetBytesRoundTripEdgeCases(t *testing.T) {
|
|
// TODO: values close to 0, close to 2^255-19, between 2^255-19 and 2^255-1,
|
|
// and between 2^255 and 2^256-1. Test both the documented SetBytes
|
|
// behavior, and that Bytes reduces them.
|
|
}
|
|
|
|
// Tests self-consistency between Multiply and Square.
|
|
func TestConsistency(t *testing.T) {
|
|
var x Element
|
|
var x2, x2sq Element
|
|
|
|
x = Element{1, 1, 1, 1, 1}
|
|
x2.Multiply(&x, &x)
|
|
x2sq.Square(&x)
|
|
|
|
if x2 != x2sq {
|
|
t.Fatalf("all ones failed\nmul: %x\nsqr: %x\n", x2, x2sq)
|
|
}
|
|
|
|
var bytes [32]byte
|
|
|
|
_, err := io.ReadFull(rand.Reader, bytes[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
x.SetBytes(bytes[:])
|
|
|
|
x2.Multiply(&x, &x)
|
|
x2sq.Square(&x)
|
|
|
|
if x2 != x2sq {
|
|
t.Fatalf("all ones failed\nmul: %x\nsqr: %x\n", x2, x2sq)
|
|
}
|
|
}
|
|
|
|
func TestEqual(t *testing.T) {
|
|
x := Element{1, 1, 1, 1, 1}
|
|
y := Element{5, 4, 3, 2, 1}
|
|
|
|
eq := x.Equal(&x)
|
|
if eq != 1 {
|
|
t.Errorf("wrong about equality")
|
|
}
|
|
|
|
eq = x.Equal(&y)
|
|
if eq != 0 {
|
|
t.Errorf("wrong about inequality")
|
|
}
|
|
}
|
|
|
|
func TestInvert(t *testing.T) {
|
|
x := Element{1, 1, 1, 1, 1}
|
|
one := Element{1, 0, 0, 0, 0}
|
|
var xinv, r Element
|
|
|
|
xinv.Invert(&x)
|
|
r.Multiply(&x, &xinv)
|
|
r.reduce()
|
|
|
|
if one != r {
|
|
t.Errorf("inversion identity failed, got: %x", r)
|
|
}
|
|
|
|
var bytes [32]byte
|
|
|
|
_, err := io.ReadFull(rand.Reader, bytes[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
x.SetBytes(bytes[:])
|
|
|
|
xinv.Invert(&x)
|
|
r.Multiply(&x, &xinv)
|
|
r.reduce()
|
|
|
|
if one != r {
|
|
t.Errorf("random inversion identity failed, got: %x for field element %x", r, x)
|
|
}
|
|
|
|
zero := Element{}
|
|
x.Set(&zero)
|
|
if xx := xinv.Invert(&x); xx != &xinv {
|
|
t.Errorf("inverting zero did not return the receiver")
|
|
} else if xinv.Equal(&zero) != 1 {
|
|
t.Errorf("inverting zero did not return zero")
|
|
}
|
|
}
|
|
|
|
func TestSelectSwap(t *testing.T) {
|
|
a := Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676}
|
|
b := Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972}
|
|
|
|
var c, d Element
|
|
|
|
c.Select(&a, &b, 1)
|
|
d.Select(&a, &b, 0)
|
|
|
|
if c.Equal(&a) != 1 || d.Equal(&b) != 1 {
|
|
t.Errorf("Select failed")
|
|
}
|
|
|
|
c.Swap(&d, 0)
|
|
|
|
if c.Equal(&a) != 1 || d.Equal(&b) != 1 {
|
|
t.Errorf("Swap failed")
|
|
}
|
|
|
|
c.Swap(&d, 1)
|
|
|
|
if c.Equal(&b) != 1 || d.Equal(&a) != 1 {
|
|
t.Errorf("Swap failed")
|
|
}
|
|
}
|
|
|
|
func TestMult32(t *testing.T) {
|
|
mult32EquivalentToMul := func(x Element, y uint32) bool {
|
|
t1 := new(Element)
|
|
for i := 0; i < 100; i++ {
|
|
t1.Mult32(&x, y)
|
|
}
|
|
|
|
ty := new(Element)
|
|
ty.l0 = uint64(y)
|
|
|
|
t2 := new(Element)
|
|
for i := 0; i < 100; i++ {
|
|
t2.Multiply(&x, ty)
|
|
}
|
|
|
|
return t1.Equal(t2) == 1 && isInBounds(t1) && isInBounds(t2)
|
|
}
|
|
|
|
if err := quick.Check(mult32EquivalentToMul, quickCheckConfig(1024)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestSqrtRatio(t *testing.T) {
|
|
// From draft-irtf-cfrg-ristretto255-decaf448-00, Appendix A.4.
|
|
type test struct {
|
|
u, v string
|
|
wasSquare int
|
|
r string
|
|
}
|
|
var tests = []test{
|
|
// If u is 0, the function is defined to return (0, TRUE), even if v
|
|
// is zero. Note that where used in this package, the denominator v
|
|
// is never zero.
|
|
{
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
1, "0000000000000000000000000000000000000000000000000000000000000000",
|
|
},
|
|
// 0/1 == 0²
|
|
{
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
1, "0000000000000000000000000000000000000000000000000000000000000000",
|
|
},
|
|
// If u is non-zero and v is zero, defined to return (0, FALSE).
|
|
{
|
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
0, "0000000000000000000000000000000000000000000000000000000000000000",
|
|
},
|
|
// 2/1 is not square in this field.
|
|
{
|
|
"0200000000000000000000000000000000000000000000000000000000000000",
|
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
0, "3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54",
|
|
},
|
|
// 4/1 == 2²
|
|
{
|
|
"0400000000000000000000000000000000000000000000000000000000000000",
|
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
1, "0200000000000000000000000000000000000000000000000000000000000000",
|
|
},
|
|
// 1/4 == (2⁻¹)² == (2^(p-2))² per Euler's theorem
|
|
{
|
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
"0400000000000000000000000000000000000000000000000000000000000000",
|
|
1, "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
u, _ := new(Element).SetBytes(decodeHex(tt.u))
|
|
v, _ := new(Element).SetBytes(decodeHex(tt.v))
|
|
want, _ := new(Element).SetBytes(decodeHex(tt.r))
|
|
got, wasSquare := new(Element).SqrtRatio(u, v)
|
|
if got.Equal(want) == 0 || wasSquare != tt.wasSquare {
|
|
t.Errorf("%d: got (%v, %v), want (%v, %v)", i, got, wasSquare, want, tt.wasSquare)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCarryPropagate(t *testing.T) {
|
|
asmLikeGeneric := func(a [5]uint64) bool {
|
|
t1 := &Element{a[0], a[1], a[2], a[3], a[4]}
|
|
t2 := &Element{a[0], a[1], a[2], a[3], a[4]}
|
|
|
|
t1.carryPropagate()
|
|
t2.carryPropagateGeneric()
|
|
|
|
if *t1 != *t2 {
|
|
t.Logf("got: %#v,\nexpected: %#v", t1, t2)
|
|
}
|
|
|
|
return *t1 == *t2 && isInBounds(t2)
|
|
}
|
|
|
|
if err := quick.Check(asmLikeGeneric, quickCheckConfig(1024)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if !asmLikeGeneric([5]uint64{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}) {
|
|
t.Errorf("failed for {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}")
|
|
}
|
|
}
|
|
|
|
func TestFeSquare(t *testing.T) {
|
|
asmLikeGeneric := func(a Element) bool {
|
|
t1 := a
|
|
t2 := a
|
|
|
|
feSquareGeneric(&t1, &t1)
|
|
feSquare(&t2, &t2)
|
|
|
|
if t1 != t2 {
|
|
t.Logf("got: %#v,\nexpected: %#v", t1, t2)
|
|
}
|
|
|
|
return t1 == t2 && isInBounds(&t2)
|
|
}
|
|
|
|
if err := quick.Check(asmLikeGeneric, quickCheckConfig(1024)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestFeMul(t *testing.T) {
|
|
asmLikeGeneric := func(a, b Element) bool {
|
|
a1 := a
|
|
a2 := a
|
|
b1 := b
|
|
b2 := b
|
|
|
|
feMulGeneric(&a1, &a1, &b1)
|
|
feMul(&a2, &a2, &b2)
|
|
|
|
if a1 != a2 || b1 != b2 {
|
|
t.Logf("got: %#v,\nexpected: %#v", a1, a2)
|
|
t.Logf("got: %#v,\nexpected: %#v", b1, b2)
|
|
}
|
|
|
|
return a1 == a2 && isInBounds(&a2) &&
|
|
b1 == b2 && isInBounds(&b2)
|
|
}
|
|
|
|
if err := quick.Check(asmLikeGeneric, quickCheckConfig(1024)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func decodeHex(s string) []byte {
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|