// // Copyright Coinbase, Inc. All Rights Reserved. // // SPDX-License-Identifier: Apache-2.0 // package core import ( "math/big" "testing" "github.com/stretchr/testify/require" "source.quilibrium.com/quilibrium/monorepo/nekryptology/internal" ) var ( four = big.NewInt(4) // Large numbers for testing -- computing with independent tooling // x,y 100-digit numbers x, _ = new(big.Int).SetString("7146643783615963513942641287213372249533955323510461217840179896547799100626220786140425637990097431", 10) y, _ = new(big.Int).SetString("1747698065194620177681258504464368264357359841192790848951902311522815739310792522712583635858354245", 10) sumxy, _ = new(big.Int).SetString("8894341848810583691623899791677740513891315164703252066792082208070614839937013308853009273848451676", 10) xy, _ = new(big.Int).SetString("12490175513260779219420155073726764321605372267033815716483640700978475653623775696463227582174703069158832890348206546318843052423532258178885792744599932977235221784868792263260215861775082862444595", 10) // 101-digit modulus m, _ = new(big.Int).SetString("85832751158419329546684678412285185885848111422509523329716452068504806021136687603399722116388773253", 10) // 99-digit modulus n, _ = new(big.Int).SetString("604464499356780653111583485887412477603580949137220100557796699530113283915988830359783807274682723", 10) ) func TestConstantTimeEqByteSound(t *testing.T) { hundoDigit := internal.B10("3593421565679030456559622742114065111786271367498220644136232358421457354322411370928949366452183472") tests := []struct { name string a, b *big.Int expected byte }{ {"positive: 5", internal.B10("5"), internal.B10("5"), 1}, {"positive: 100", internal.B10("100"), internal.B10("100"), 1}, {"positive: -1204", internal.B10("-1204"), internal.B10("-1204"), 1}, {"positive: 100 digits", hundoDigit, hundoDigit, 1}, {"positive: 0", internal.B10("0"), internal.B10("0"), 1}, {"positive: 0/-0", internal.B10("0"), internal.B10("-0"), 1}, {"positive: -0/-0", internal.B10("-0"), internal.B10("-0"), 1}, {"negative: 5/-5", internal.B10("5"), internal.B10("-5"), 0}, {"negative: 5/500", internal.B10("5"), internal.B10("500"), 0}, {"negative: 100/100 digit", internal.B10("100"), hundoDigit, 0}, {"negative: -1204/-5", internal.B10("-1204"), internal.B10("-15"), 0}, {"negative: 0/-5 digits", internal.B10("0"), internal.B10("-5"), 0}, } // Run all the tests! for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := ConstantTimeEqByte(test.a, test.b) require.Equal(t, test.expected, actual) }) } } func TestConstantTimeEqSound(t *testing.T) { hundoDigit := internal.B10("3593421565679030456559622742114065111786271367498220644136232358421457354322411370928949366452183472") tests := []struct { name string a, b *big.Int expected bool }{ {"positive: 5", internal.B10("5"), internal.B10("5"), true}, {"positive: 100", internal.B10("100"), internal.B10("100"), true}, {"positive: -1204", internal.B10("-1204"), internal.B10("-1204"), true}, {"positive: 100 digits", hundoDigit, hundoDigit, true}, {"positive: 0", internal.B10("0"), internal.B10("0"), true}, {"positive: 0/-0", internal.B10("0"), internal.B10("-0"), true}, {"positive: -0/-0", internal.B10("-0"), internal.B10("-0"), true}, {"negative: 5/-5", internal.B10("5"), internal.B10("-5"), false}, {"negative: 5/500", internal.B10("5"), internal.B10("500"), false}, {"negative: 100/100 digit", internal.B10("100"), hundoDigit, false}, {"negative: -1204/-5", internal.B10("-1204"), internal.B10("-15"), false}, {"negative: 0/-5 digits", internal.B10("0"), internal.B10("-5"), false}, } // Run all the tests! for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := ConstantTimeEq(test.a, test.b) require.Equal(t, test.expected, actual) }) } } // Ring membership tests func TestIn(t *testing.T) { // Some large numbers for testing x, _ := new(big.Int).SetString("21888242871839275222246405745257275088696311157297823662689037894645226208583", 10) y, _ := new(big.Int).SetString("32168432167132168106409840321684604654063138460840123871234181628904319728058", 10) N := new(big.Int).Mul(x, y) // N = xy NN := new(big.Int).Mul(N, N) // N^2 = N*N = x^2y^2 errMember := internal.ErrZmMembership var tests = []struct { x *big.Int m *big.Int expected error }{ // // Completist test for: Z_4 // // Too small: -x ∉ Z_4, \forall \x \in \N {big.NewInt(-4), four, errMember}, {big.NewInt(-3), four, errMember}, {big.NewInt(-2), four, errMember}, {big.NewInt(-1), four, errMember}, // Just right: {0,1,2,3} = Z_4 {big.NewInt(0), four, nil}, {big.NewInt(1), four, nil}, {big.NewInt(2), four, nil}, {big.NewInt(3), four, nil}, // Too big: {4,5,6,7} ∉ Z_4 {big.NewInt(4), four, errMember}, {big.NewInt(5), four, errMember}, {big.NewInt(6), four, errMember}, {big.NewInt(7), four, errMember}, // // Large numbers // // x,y,N < N^2 {x, NN, nil}, {y, NN, nil}, {N, NN, nil}, // N+x,N+y,2N < N^2 ⇒ x ∈ Z_N^2 {big.NewInt(0).Add(N, x), NN, nil}, {big.NewInt(0).Add(N, y), NN, nil}, {big.NewInt(0).Add(N, N), NN, nil}, // Nx,Ny < N^2 ⇒ x ∈ Z_N^2 {big.NewInt(0).Mul(N, x), NN, nil}, {big.NewInt(0).Mul(N, y), NN, nil}, // -x,-y,-N ∉ Z_N^2 {big.NewInt(0).Neg(x), NN, errMember}, {big.NewInt(0).Neg(y), NN, errMember}, {big.NewInt(0).Neg(N), NN, errMember}, // N^2 ∉ Z_N^2 {NN, NN, errMember}, } // All the tests! for _, test := range tests { actual := In(test.x, test.m) require.Equal(t, test.expected, actual) } } // Tests for modular addition with known answers func TestAdd(t *testing.T) { // Pre-compute some values sumXyModn, err := Add(x, y, n) require.Nil(t, err) var tests = []struct { x, y, m, expected *big.Int // inputs: x,y,m }{ // Small number tests {big.NewInt(-1), big.NewInt(1), four, big.NewInt(0)}, {big.NewInt(2), big.NewInt(1), four, big.NewInt(3)}, {big.NewInt(0), big.NewInt(2), four, big.NewInt(2)}, {big.NewInt(2), big.NewInt(4), four, big.NewInt(2)}, {big.NewInt(15), big.NewInt(15), four, big.NewInt(2)}, // Large number tests {x, y, m, sumxy}, {y, x, m, sumxy}, // Large number Zero tests {Zero, x, m, x}, {x, Zero, m, x}, {Zero, y, m, y}, {y, Zero, m, y}, // Commutative {x, y, m, sumxy}, {y, x, m, sumxy}, {x, y, n, sumXyModn}, {y, x, n, sumXyModn}, {sumXyModn, Zero, n, sumXyModn}, {Zero, sumXyModn, n, sumXyModn}, } // All the tests! for _, test := range tests { actual, err := Add(test.x, test.y, test.m) require.NoError(t, err) require.Zero(t, actual.Cmp(test.expected)) } } // Tests for modular addition according to known invariants func TestAddInvariants(t *testing.T) { inputs := []*big.Int{x, y, Zero, One, new(big.Int).Neg(x), new(big.Int).Neg(y)} moduli := []*big.Int{m, n, big.NewInt(10001)} // Run all combinations of the inputs/moduli for _, x := range inputs { for _, y := range inputs { for _, m := range moduli { // Addition is commutative z0, err := Add(x, y, m) require.NoError(t, err) z1, err := Add(y, x, m) require.NoError(t, err) require.Equal(t, z0, z1) // Addition is transitive: x+x+y == y+x+x == x+y+x a0, _ := Add(x, x, m) a0, _ = Add(a0, y, m) a1, _ := Add(y, x, m) a1, _ = Add(a1, x, m) a2, _ := Add(x, y, m) a2, _ = Add(a2, x, m) require.Equal(t, a0, a1) require.Equal(t, a1, a2) } } } } // Tests modular multiplication with known answers func TestMul(t *testing.T) { // Pre-compute some values xyModm := new(big.Int).Mod(xy, m) var tests = []struct { x, y, m, expected *big.Int // inputs: x,y,m }{ // Small number tests {big.NewInt(-1), big.NewInt(1), four, big.NewInt(3)}, {big.NewInt(2), big.NewInt(1), four, big.NewInt(2)}, {big.NewInt(0), big.NewInt(2), four, big.NewInt(0)}, {big.NewInt(2), big.NewInt(4), four, big.NewInt(0)}, {big.NewInt(15), big.NewInt(15), four, big.NewInt(1)}, // Large number tests {x, y, m, xyModm}, {y, x, m, xyModm}, // Large number Zero tests {Zero, x, m, Zero}, {x, Zero, m, Zero}, {Zero, y, n, Zero}, } // All the tests! for _, test := range tests { z, err := Mul(test.x, test.y, test.m) require.NoError(t, err) require.Zero(t, z.Cmp(test.expected)) } } // Tests for modular multiplication according to known invariants func TestMulInvariants(t *testing.T) { inputs := []*big.Int{x, y, Zero, One, new(big.Int).Neg(x), new(big.Int).Neg(y)} moduli := []*big.Int{m, n, big.NewInt(10001)} // Run all combinations of the inputs/moduli for _, x := range inputs { for _, y := range inputs { for _, m := range moduli { // Mul is commutative a, err := Mul(x, y, m) require.NoError(t, err) aʹ, err := Mul(y, x, m) require.NoError(t, err) require.Equal(t, a, aʹ) // Mul is transitive: (xx)y == (xy)x z, _ := Mul(x, x, m) z, _ = Mul(z, y, m) zʹ, _ := Mul(x, y, m) zʹ, _ = Mul(zʹ, x, m) require.Equal(t, z, zʹ) } } } } // Tests modular negation with known answers func TestNeg(t *testing.T) { var tests = []struct { x, m, e *big.Int }{ {big.NewInt(1), big.NewInt(7), big.NewInt(6)}, {big.NewInt(2), big.NewInt(7), big.NewInt(5)}, {big.NewInt(3), big.NewInt(7), big.NewInt(4)}, {big.NewInt(4), big.NewInt(7), big.NewInt(3)}, {big.NewInt(5), big.NewInt(7), big.NewInt(2)}, {big.NewInt(6), big.NewInt(7), big.NewInt(1)}, {big.NewInt(-1), big.NewInt(7), big.NewInt(1)}, {big.NewInt(-2), big.NewInt(7), big.NewInt(2)}, {big.NewInt(-3), big.NewInt(7), big.NewInt(3)}, {big.NewInt(-4), big.NewInt(7), big.NewInt(4)}, {big.NewInt(-5), big.NewInt(7), big.NewInt(5)}, {big.NewInt(-6), big.NewInt(7), big.NewInt(6)}, {big.NewInt(8), big.NewInt(7), big.NewInt(6)}, {big.NewInt(9), big.NewInt(7), big.NewInt(5)}, {big.NewInt(10), big.NewInt(7), big.NewInt(4)}, {big.NewInt(11), big.NewInt(7), big.NewInt(3)}, {big.NewInt(12), big.NewInt(7), big.NewInt(2)}, {big.NewInt(13), big.NewInt(7), big.NewInt(1)}, } for _, test := range tests { r, err := Neg(test.x, test.m) require.NoError(t, err) if r.Cmp(test.e) != 0 { t.Errorf("TestNeg failed. Expected %v, got: %v ", test.e, r) } } } func TestNegInvariants(t *testing.T) { var tests = []struct { x, m, e *big.Int }{ {big.NewInt(0), big.NewInt(7), big.NewInt(0)}, {big.NewInt(7), big.NewInt(7), big.NewInt(0)}, {big.NewInt(-7), big.NewInt(7), big.NewInt(0)}, } for _, test := range tests { r, err := Neg(test.x, test.m) require.NoError(t, err) if r.Cmp(test.e) != 0 { t.Errorf("TestNeg failed. Expected %v, got: %v ", test.e, r) } } } // Simple test for distinct Rand output func TestRandDistinct(t *testing.T) { // Each value should be distinct a, _ := Rand(n) b, _ := Rand(n) c, _ := Rand(n) // ❄️❄️❄️ require.NotEqual(t, a, b) require.NotEqual(t, a, c) require.NotEqual(t, b, c) } // Rand values should be O(log2(m)) bits func TestRandIsExpectedLength(t *testing.T) { trials := 1000 max := big.NewInt(-1) // Generate many nonces, keep the max for i := 0; i < trials; i++ { r, err := Rand(m) require.NoError(t, err) // Nonces should be < m if r.Cmp(m) != -1 { t.Errorf("nonce too large, require %v < %v", r, m) } if r.Cmp(max) == 1 { max = r } } // With high probability, the max nonce should be very close N lowerBound := new(big.Int).Rsh(m, 1) if max.Cmp(lowerBound) == -1 { t.Errorf("Expected max nonce: %v > %v", max, lowerBound) } } // Randomly selected nonces with a large modulus will be unique with overwhelming probability func TestRandDistinctWithLargeModulus(t *testing.T) { const iterations = 1000 testUnique(t, iterations, func() *big.Int { r, _ := Rand(m) return r }) } // Calls sampleFunc() n times and asserts that the lower 64B of each output are unique. func testUnique(t *testing.T, iterations int, sampleFunc func() *big.Int) { // For simplicity, we test only the lower 64B of each nonce. This is sufficient // to prove uniqueness and go-lang doesn't hash slices (no slices in maps) const size = 256 / 8 seen := make(map[[size]byte]bool) var x [size]byte // Check the pre-computed commitments for uniquness for i := 0; i < iterations; i++ { // Retrieve a sample sample := sampleFunc() require.NotNil(t, sample) // Copy the bytes from slice>array copy(x[:], sample.Bytes()) // Ensure each sample is unique if seen[x] { t.Errorf("duplicate sample found: %v", x) } seen[x] = true } } // Ensure Rand never returns 0 or 1. func TestRandNotZeroNotOne(t *testing.T) { // Test for non-zero only useful when iterations >> |Z_m| const iterations = 1000 m := big.NewInt(5) for i := 0; i < iterations; i++ { r, err := Rand(m) require.NoError(t, err) // Not 0 or 1 require.NotEqual(t, r, Zero) require.NotEqual(t, r, One) } } func TestRand_NilModulusErrors(t *testing.T) { r, err := Rand(nil) require.Nil(t, r) require.Contains(t, err.Error(), internal.ErrNilArguments.Error()) } // Double-inverse is the identity function in fields func TestInvRoundTrip(t *testing.T) { m := internal.B10("1031") // Prime-order modulus for _, a := range []*big.Int{ internal.B10("500"), internal.B10("-500"), internal.B10("1"), internal.B10("1030"), } { // Our expected value is the modular reduction of the test value expected := a.Mod(a, m) // Invert and check aInv, err := Inv(a, m) require.NoError(t, err, "a=%v", a) require.NotNil(t, aInv) // Invert again and check a_, err := Inv(aInv, m) if err != nil { require.Equal(t, expected, a_) } } } // Tests values for which there is no inverse in the given field func TestInvNotFound(t *testing.T) { m := internal.B10("1024") // m = 2^10 // 0 and even numbers will not have inverse in this ring for _, a := range []*big.Int{ internal.B10("500"), internal.B10("-500"), internal.B10("0"), internal.B10("1024"), internal.B10("512"), internal.B10("300000000"), } { // Invert and check aInv, err := Inv(a, m) require.Error(t, err, "a=%v", a) require.Nil(t, aInv) } } func TestExpKnownAnswer(t *testing.T) { p := internal.B10("1031") // prime-order field pMinus1 := internal.B10("1030") tests := []struct { name string x, e, m *big.Int expected *big.Int }{ {"fermat's little thm: 500", internal.B10("500"), p, p, internal.B10("500")}, {"fermat's little thm (p-1): 500", internal.B10("500"), pMinus1, p, One}, {"fermat's little thm (p-1): 5000", internal.B10("5000"), pMinus1, p, One}, {"399^0 = 1", internal.B10("399"), Zero, p, One}, {"673^1 = 673", internal.B10("673"), One, p, internal.B10("673")}, } // Run all the tests! for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual, err := Exp(test.x, test.e, test.m) if err != nil { require.Equal(t, test.expected, actual) } }) } } // A product of two 1024b safe primes var N1024 = internal.B10("22657252520748253292205422817162431301953923432914829530688424232913850279325496327198502914522231560238552529734156383924448818535517634061008476071362010781638360092704508943571866960229942049437914690556866055765377519627454975682400206932320319743805083072214857842762721537739950074695623974079312071498296625705376593890814889314744719469735809152488403143751157723139035869185892099006348653635981206799193781030834368833947197930944812082594326193527332208252230115672713914945889734620959932802893197325106135662762752470236627025599443912886530954179753873735786171937758916890000958846322096261981191349917") // A product of two 256b safe primes var N256 = internal.B10("10815068324662993508164204692909269429257853772524581783499643160896147777579932560873002543907262462663453338979819981987639157192530671167315407970757417") func Benchmark_rand1024(b *testing.B) { if testing.Short() { b.Skip("skipping test in short mode.") } for i := 0; i < b.N; i++ { Rand(N1024) // nolint } } func BenchmarkRand1024(b *testing.B) { if testing.Short() { b.Skip("skipping test in short mode.") } for i := 0; i < b.N; i++ { Rand(N1024) // nolint } } func Benchmark_rand256(b *testing.B) { if testing.Short() { b.Skip("skipping test in short mode.") } for i := 0; i < b.N; i++ { Rand(N256) // nolint } } func BenchmarkRandStar256(b *testing.B) { if testing.Short() { b.Skip("skipping test in short mode.") } for i := 0; i < b.N; i++ { Rand(N256) // nolint } }