// 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()) } // quickCheckConfig1024 will make each quickcheck test run (1024 * -quickchecks) // times. The default value of -quickchecks is 100. var quickCheckConfig1024 = &quick.Config{MaxCountScale: 1 << 10} 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, quickCheckConfig1024); 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() if !bytes.Equal(b, tt.b) || new(Element).SetBytes(tt.b).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) // pad with zeroes copy(buf, swapEndianness(fe1.toBig().Bytes())) 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 } } return v.SetBytes(buf[:32]) } 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, quickCheckConfig1024); 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, quickCheckConfig1024); 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, quickCheckConfig1024); 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, quickCheckConfig1024); err != nil { t.Error(err) } } func decodeHex(s string) []byte { b, err := hex.DecodeString(s) if err != nil { panic(err) } return b }