ceremonyclient/nekryptology/pkg/core/mod_test.go
2023-07-05 00:32:28 -05:00

566 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
}
}