mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-11-10 18:25:17 +00:00
566 lines
16 KiB
Go
566 lines
16 KiB
Go
//
|
||
// 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
|
||
}
|
||
}
|