|
|
// Copyright 2020 ConsenSys AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by goff (v0.2.0) DO NOT EDIT
// Package ff contains field arithmetic operations
package ff
import ( "crypto/rand" "math/big" "math/bits" mrand "math/rand" "testing" )
func TestELEMENTCorrectnessAgainstBigInt(t *testing.T) { modulus, _ := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) cmpEandB := func(e *Element, b *big.Int, name string) { var _e big.Int if e.FromMont().ToBigInt(&_e).Cmp(b) != 0 { t.Fatal(name, "failed") } } var modulusMinusOne, one big.Int one.SetUint64(1)
modulusMinusOne.Sub(modulus, &one)
var n int if testing.Short() { n = 10 } else { n = 500 }
for i := 0; i < n; i++ {
// sample 2 random big int
b1, _ := rand.Int(rand.Reader, modulus) b2, _ := rand.Int(rand.Reader, modulus) rExp := mrand.Uint64()
// adding edge cases
// TODO need more edge cases
switch i { case 0: rExp = 0 b1.SetUint64(0) case 1: b2.SetUint64(0) case 2: b1.SetUint64(0) b2.SetUint64(0) case 3: rExp = 0 case 4: rExp = 1 case 5: rExp = ^uint64(0) // max uint
case 6: rExp = 2 b1.Set(&modulusMinusOne) case 7: b2.Set(&modulusMinusOne) case 8: b1.Set(&modulusMinusOne) b2.Set(&modulusMinusOne) }
rbExp := new(big.Int).SetUint64(rExp)
var bMul, bAdd, bSub, bDiv, bNeg, bLsh, bInv, bExp, bExp2, bSquare big.Int
// e1 = mont(b1), e2 = mont(b2)
var e1, e2, eMul, eAdd, eSub, eDiv, eNeg, eLsh, eInv, eExp, eSquare, eMulAssign, eSubAssign, eAddAssign Element e1.SetBigInt(b1) e2.SetBigInt(b2)
// (e1*e2).FromMont() === b1*b2 mod q ... etc
eSquare.Square(&e1) eMul.Mul(&e1, &e2) eMulAssign.Set(&e1) eMulAssign.MulAssign(&e2) eAdd.Add(&e1, &e2) eAddAssign.Set(&e1) eAddAssign.AddAssign(&e2) eSub.Sub(&e1, &e2) eSubAssign.Set(&e1) eSubAssign.SubAssign(&e2) eDiv.Div(&e1, &e2) eNeg.Neg(&e1) eInv.Inverse(&e1) eExp.Exp(e1, rExp) eLsh.Double(&e1)
// same operations with big int
bAdd.Add(b1, b2).Mod(&bAdd, modulus) bMul.Mul(b1, b2).Mod(&bMul, modulus) bSquare.Mul(b1, b1).Mod(&bSquare, modulus) bSub.Sub(b1, b2).Mod(&bSub, modulus) bDiv.ModInverse(b2, modulus) bDiv.Mul(&bDiv, b1). Mod(&bDiv, modulus) bNeg.Neg(b1).Mod(&bNeg, modulus)
bInv.ModInverse(b1, modulus) bExp.Exp(b1, rbExp, modulus) bLsh.Lsh(b1, 1).Mod(&bLsh, modulus)
cmpEandB(&eSquare, &bSquare, "Square") cmpEandB(&eMul, &bMul, "Mul") cmpEandB(&eMulAssign, &bMul, "MulAssign") cmpEandB(&eAdd, &bAdd, "Add") cmpEandB(&eAddAssign, &bAdd, "AddAssign") cmpEandB(&eSub, &bSub, "Sub") cmpEandB(&eSubAssign, &bSub, "SubAssign") cmpEandB(&eDiv, &bDiv, "Div") cmpEandB(&eNeg, &bNeg, "Neg") cmpEandB(&eInv, &bInv, "Inv") cmpEandB(&eExp, &bExp, "Exp")
cmpEandB(&eLsh, &bLsh, "Lsh")
// legendre symbol
if e1.Legendre() != big.Jacobi(b1, modulus) { t.Fatal("legendre symbol computation failed") } if e2.Legendre() != big.Jacobi(b2, modulus) { t.Fatal("legendre symbol computation failed") }
// these are slow, killing circle ci
if n <= 5 { // sqrt
var eSqrt, eExp2 Element var bSqrt big.Int bSqrt.ModSqrt(b1, modulus) eSqrt.Sqrt(&e1) cmpEandB(&eSqrt, &bSqrt, "Sqrt")
bits := b2.Bits() exponent := make([]uint64, len(bits)) for k := 0; k < len(bits); k++ { exponent[k] = uint64(bits[k]) } eExp2.Exp(e1, exponent...) bExp2.Exp(b1, b2, modulus) cmpEandB(&eExp2, &bExp2, "Exp multi words") } } }
func TestELEMENTIsRandom(t *testing.T) { for i := 0; i < 50; i++ { var x, y Element x.SetRandom() y.SetRandom() if x.Equal(&y) { t.Fatal("2 random numbers are unlikely to be equal") } } }
// -------------------------------------------------------------------------------------------------
// benchmarks
// most benchmarks are rudimentary and should sample a large number of random inputs
// or be run multiple times to ensure it didn't measure the fastest path of the function
var benchResElement Element
func BenchmarkInverseELEMENT(b *testing.B) { var x Element x.SetRandom() benchResElement.SetRandom() b.ResetTimer()
for i := 0; i < b.N; i++ { benchResElement.Inverse(&x) }
} func BenchmarkExpELEMENT(b *testing.B) { var x Element x.SetRandom() benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Exp(x, mrand.Uint64()) } }
func BenchmarkDoubleELEMENT(b *testing.B) { benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Double(&benchResElement) } }
func BenchmarkAddELEMENT(b *testing.B) { var x Element x.SetRandom() benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Add(&x, &benchResElement) } }
func BenchmarkSubELEMENT(b *testing.B) { var x Element x.SetRandom() benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Sub(&x, &benchResElement) } }
func BenchmarkNegELEMENT(b *testing.B) { benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Neg(&benchResElement) } }
func BenchmarkDivELEMENT(b *testing.B) { var x Element x.SetRandom() benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Div(&x, &benchResElement) } }
func BenchmarkFromMontELEMENT(b *testing.B) { benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.FromMont() } }
func BenchmarkToMontELEMENT(b *testing.B) { benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.ToMont() } } func BenchmarkSquareELEMENT(b *testing.B) { benchResElement.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Square(&benchResElement) } }
func BenchmarkSqrtELEMENT(b *testing.B) { var a Element a.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Sqrt(&a) } }
func BenchmarkMulAssignELEMENT(b *testing.B) { x := Element{ 1997599621687373223, 6052339484930628067, 10108755138030829701, 150537098327114917, } benchResElement.SetOne() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.MulAssign(&x) } }
func BenchmarkMulAssignASMELEMENT(b *testing.B) { x := Element{ 1997599621687373223, 6052339484930628067, 10108755138030829701, 150537098327114917, } benchResElement.SetOne() b.ResetTimer() for i := 0; i < b.N; i++ { MulAssignElement(&benchResElement, &x) } }
func TestELEMENTAsm(t *testing.T) { // ensure ASM implementations matches the ones using math/bits
modulus, _ := new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10) for i := 0; i < 500; i++ { // sample 2 random big int
b1, _ := rand.Int(rand.Reader, modulus) b2, _ := rand.Int(rand.Reader, modulus)
// e1 = mont(b1), e2 = mont(b2)
var e1, e2, eTestMul, eMulAssign, eSquare, eTestSquare Element e1.SetBigInt(b1) e2.SetBigInt(b2)
eTestMul = e1 eTestMul.testMulAssign(&e2) eMulAssign = e1 eMulAssign.MulAssign(&e2)
if !eTestMul.Equal(&eMulAssign) { t.Fatal("inconsisntencies between MulAssign and testMulAssign --> check if MulAssign is calling ASM implementaiton on amd64") }
// square
eSquare.Square(&e1) eTestSquare.testSquare(&e1)
if !eTestSquare.Equal(&eSquare) { t.Fatal("inconsisntencies between Square and testSquare --> check if Square is calling ASM implementaiton on amd64") } } }
// this is here for consistency purposes, to ensure MulAssign on AMD64 using asm implementation gives consistent results
func (z *Element) testMulAssign(x *Element) *Element {
var t [4]uint64 var c [3]uint64 { // round 0
v := z[0] c[1], c[0] = bits.Mul64(v, x[0]) m := c[0] * 14042775128853446655 c[2] = madd0(m, 4891460686036598785, c[0]) c[1], c[0] = madd1(v, x[1], c[1]) c[2], t[0] = madd2(m, 2896914383306846353, c[2], c[0]) c[1], c[0] = madd1(v, x[2], c[1]) c[2], t[1] = madd2(m, 13281191951274694749, c[2], c[0]) c[1], c[0] = madd1(v, x[3], c[1]) t[3], t[2] = madd3(m, 3486998266802970665, c[0], c[2], c[1]) } { // round 1
v := z[1] c[1], c[0] = madd1(v, x[0], t[0]) m := c[0] * 14042775128853446655 c[2] = madd0(m, 4891460686036598785, c[0]) c[1], c[0] = madd2(v, x[1], c[1], t[1]) c[2], t[0] = madd2(m, 2896914383306846353, c[2], c[0]) c[1], c[0] = madd2(v, x[2], c[1], t[2]) c[2], t[1] = madd2(m, 13281191951274694749, c[2], c[0]) c[1], c[0] = madd2(v, x[3], c[1], t[3]) t[3], t[2] = madd3(m, 3486998266802970665, c[0], c[2], c[1]) } { // round 2
v := z[2] c[1], c[0] = madd1(v, x[0], t[0]) m := c[0] * 14042775128853446655 c[2] = madd0(m, 4891460686036598785, c[0]) c[1], c[0] = madd2(v, x[1], c[1], t[1]) c[2], t[0] = madd2(m, 2896914383306846353, c[2], c[0]) c[1], c[0] = madd2(v, x[2], c[1], t[2]) c[2], t[1] = madd2(m, 13281191951274694749, c[2], c[0]) c[1], c[0] = madd2(v, x[3], c[1], t[3]) t[3], t[2] = madd3(m, 3486998266802970665, c[0], c[2], c[1]) } { // round 3
v := z[3] c[1], c[0] = madd1(v, x[0], t[0]) m := c[0] * 14042775128853446655 c[2] = madd0(m, 4891460686036598785, c[0]) c[1], c[0] = madd2(v, x[1], c[1], t[1]) c[2], z[0] = madd2(m, 2896914383306846353, c[2], c[0]) c[1], c[0] = madd2(v, x[2], c[1], t[2]) c[2], z[1] = madd2(m, 13281191951274694749, c[2], c[0]) c[1], c[0] = madd2(v, x[3], c[1], t[3]) z[3], z[2] = madd3(m, 3486998266802970665, c[0], c[2], c[1]) }
// if z > q --> z -= q
// note: this is NOT constant time
if !(z[3] < 3486998266802970665 || (z[3] == 3486998266802970665 && (z[2] < 13281191951274694749 || (z[2] == 13281191951274694749 && (z[1] < 2896914383306846353 || (z[1] == 2896914383306846353 && (z[0] < 4891460686036598785))))))) { var b uint64 z[0], b = bits.Sub64(z[0], 4891460686036598785, 0) z[1], b = bits.Sub64(z[1], 2896914383306846353, b) z[2], b = bits.Sub64(z[2], 13281191951274694749, b) z[3], _ = bits.Sub64(z[3], 3486998266802970665, b) } return z }
// this is here for consistency purposes, to ensure Square on AMD64 using asm implementation gives consistent results
func (z *Element) testSquare(x *Element) *Element {
var p [4]uint64
var u, v uint64 { // round 0
u, p[0] = bits.Mul64(x[0], x[0]) m := p[0] * 14042775128853446655 C := madd0(m, 4891460686036598785, p[0]) var t uint64 t, u, v = madd1sb(x[0], x[1], u) C, p[0] = madd2(m, 2896914383306846353, v, C) t, u, v = madd1s(x[0], x[2], t, u) C, p[1] = madd2(m, 13281191951274694749, v, C) _, u, v = madd1s(x[0], x[3], t, u) p[3], p[2] = madd3(m, 3486998266802970665, v, C, u) } { // round 1
m := p[0] * 14042775128853446655 C := madd0(m, 4891460686036598785, p[0]) u, v = madd1(x[1], x[1], p[1]) C, p[0] = madd2(m, 2896914383306846353, v, C) var t uint64 t, u, v = madd2sb(x[1], x[2], p[2], u) C, p[1] = madd2(m, 13281191951274694749, v, C) _, u, v = madd2s(x[1], x[3], p[3], t, u) p[3], p[2] = madd3(m, 3486998266802970665, v, C, u) } { // round 2
m := p[0] * 14042775128853446655 C := madd0(m, 4891460686036598785, p[0]) C, p[0] = madd2(m, 2896914383306846353, p[1], C) u, v = madd1(x[2], x[2], p[2]) C, p[1] = madd2(m, 13281191951274694749, v, C) _, u, v = madd2sb(x[2], x[3], p[3], u) p[3], p[2] = madd3(m, 3486998266802970665, v, C, u) } { // round 3
m := p[0] * 14042775128853446655 C := madd0(m, 4891460686036598785, p[0]) C, z[0] = madd2(m, 2896914383306846353, p[1], C) C, z[1] = madd2(m, 13281191951274694749, p[2], C) u, v = madd1(x[3], x[3], p[3]) z[3], z[2] = madd3(m, 3486998266802970665, v, C, u) }
// if z > q --> z -= q
// note: this is NOT constant time
if !(z[3] < 3486998266802970665 || (z[3] == 3486998266802970665 && (z[2] < 13281191951274694749 || (z[2] == 13281191951274694749 && (z[1] < 2896914383306846353 || (z[1] == 2896914383306846353 && (z[0] < 4891460686036598785))))))) { var b uint64 z[0], b = bits.Sub64(z[0], 4891460686036598785, 0) z[1], b = bits.Sub64(z[1], 2896914383306846353, b) z[2], b = bits.Sub64(z[2], 13281191951274694749, b) z[3], _ = bits.Sub64(z[3], 3486998266802970665, b) } return z
}
|