Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to the test random library. #1029

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 78 additions & 9 deletions common/testutils/random/test_random.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
package random

import (
"crypto/ecdsa"
crand "crypto/rand"
"fmt"
"github.com/Layr-Labs/eigenda/core"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"io"
"math/big"
"math/rand"
"testing"
"time"
)

// charset is the set of characters that can be used to generate random strings
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

// TestRandom provides all the functionality of math/rand.Rand, plus additional randomness functionality useful for testing
type TestRandom struct {
// The source of randomness
*rand.Rand

// The testing object
t *testing.T

// The seed used to initialize the random number generator
seed int64
}

// NewTestRandom creates a new instance of TestRandom
// This method may either be seeded, or not seeded. If no seed is provided, then current unix nano time is used.
func NewTestRandom(fixedSeed ...int64) *TestRandom {
func NewTestRandom(t *testing.T, fixedSeed ...int64) *TestRandom {
var seed int64
if len(fixedSeed) == 0 {
seed = time.Now().UnixNano()
Expand All @@ -25,12 +44,20 @@ func NewTestRandom(fixedSeed ...int64) *TestRandom {

fmt.Printf("Random seed: %d\n", seed)
return &TestRandom{
rand.New(rand.NewSource(seed)),
Rand: rand.New(rand.NewSource(seed)),
t: t,
seed: seed,
}
}

// RandomBytes generates a random byte slice of a given length.
func (r *TestRandom) RandomBytes(length int) []byte {
// Reset resets the random number generator to the state it was in when it was first created.
// This method is not thread safe with respect to other methods in this struct.
func (r *TestRandom) Reset() {
r.Seed(r.seed)
}

// Bytes generates a random byte slice of a given length.
func (r *TestRandom) Bytes(length int) []byte {
bytes := make([]byte, length)
_, err := r.Read(bytes)
if err != nil {
Expand All @@ -39,17 +66,59 @@ func (r *TestRandom) RandomBytes(length int) []byte {
return bytes
}

// RandomTime generates a random time.
func (r *TestRandom) RandomTime() time.Time {
// Time generates a random time.
func (r *TestRandom) Time() time.Time {
return time.Unix(r.Int63(), r.Int63())
}

// RandomString generates a random string out of printable ASCII characters.
func (r *TestRandom) RandomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// String generates a random string out of printable ASCII characters.
func (r *TestRandom) String(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[r.Intn(len(charset))]
}
return string(b)
}

var _ io.Reader = &randIOReader{}

// randIOReader is an io.Reader that reads from a random number generator.
type randIOReader struct {
rand *TestRandom
}

// Read reads random bytes into the provided buffer, returning the number of bytes read.
func (i *randIOReader) Read(p []byte) (n int, err error) {
litt3 marked this conversation as resolved.
Show resolved Hide resolved
return i.rand.Read(p)
}

// IOReader creates an io.Reader that reads from a random number generator.
func (r *TestRandom) IOReader() io.Reader {
return &randIOReader{r}
}

// ECDSA generates a random ECDSA key. Note that the returned keys are not deterministic due to limitations
// **intentionally** imposed by the Go standard libraries. (╯°□°)╯︵ ┻━┻
//
// NOT CRYPTOGRAPHICALLY SECURE!!! FOR TESTING PURPOSES ONLY. DO NOT USE THESE KEYS FOR SECURITY PURPOSES.
func (r *TestRandom) ECDSA() (*ecdsa.PublicKey, *ecdsa.PrivateKey) {
key, err := ecdsa.GenerateKey(crypto.S256(), crand.Reader)
require.NoError(r.t, err)
return &key.PublicKey, key
}

// BLS generates a random BLS key pair.
//
// NOT CRYPTOGRAPHICALLY SECURE!!! FOR TESTING PURPOSES ONLY. DO NOT USE THESE KEYS FOR SECURITY PURPOSES.
func (r *TestRandom) BLS() *core.KeyPair {
//Max random value is order of the curve
maxValue := new(big.Int)
maxValue.SetString(fr.Modulus().String(), 10)

//Generate cryptographically strong pseudo-random between 0 - max
n, err := crand.Int(r.IOReader(), maxValue)
require.NoError(r.t, err)

sk := new(core.PrivateKey).SetBigInt(n)
return core.MakeKeyPair(sk)
}
89 changes: 83 additions & 6 deletions common/testutils/random/test_random_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,104 @@
package random

import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"math/rand"
"testing"
)

// Tests that random seeding produces random results, and that consistent seeding produces consistent results
func TestSetup(t *testing.T) {
testRandom1 := NewTestRandom()
testRandom1 := NewTestRandom(t)
x := testRandom1.Int()

testRandom2 := NewTestRandom()
testRandom2 := NewTestRandom(t)
y := testRandom2.Int()

assert.NotEqual(t, x, y)
require.NotEqual(t, x, y)

seed := rand.Int63()
testRandom3 := NewTestRandom(seed)
testRandom3 := NewTestRandom(t, seed)
a := testRandom3.Int()

testRandom4 := NewTestRandom(seed)
testRandom4 := NewTestRandom(t, seed)
b := testRandom4.Int()

assert.Equal(t, a, b)
require.Equal(t, a, b)
}

func TestReset(t *testing.T) {
random := NewTestRandom(t)

a := random.Uint64()
b := random.Uint64()
c := random.Uint64()
d := random.Uint64()

random.Reset()

require.Equal(t, a, random.Uint64())
require.Equal(t, b, random.Uint64())
require.Equal(t, c, random.Uint64())
require.Equal(t, d, random.Uint64())
}

func TestECDSAKeyGeneration(t *testing.T) {
random := NewTestRandom(t)

// We should not get the same key pair twice in a row
public1, private1 := random.ECDSA()
public2, private2 := random.ECDSA()

assert.NotEqual(t, &public1, &public2)
assert.NotEqual(t, &private1, &private2)

// Getting keys should result in deterministic generator state.
generatorState := random.Uint64()
random.Reset()
random.ECDSA()
random.ECDSA()
require.Equal(t, generatorState, random.Uint64())

// Keypair should be valid.
data := random.Bytes(32)

signature, err := crypto.Sign(data, private1)
require.NoError(t, err)

signingPublicKey, err := crypto.SigToPub(data, signature)
require.NoError(t, err)
require.Equal(t, &public1, &signingPublicKey)
}

func TestBLSKeyGeneration(t *testing.T) {
random := NewTestRandom(t)

// We should not get the same key pair twice in a row
keypair1 := random.BLS()
keypair2 := random.BLS()

require.False(t, keypair1.PrivKey.Equal(keypair2.PrivKey))
require.False(t, keypair1.PubKey.Equal(keypair2.PubKey.G1Affine))

// Getting keys should result in deterministic generator state.
generatorState := random.Uint64()
random.Reset()
random.BLS()
random.BLS()
require.Equal(t, generatorState, random.Uint64())

// Keys should be deterministic.
random.Reset()
keypair3 := random.BLS()
require.True(t, keypair1.PrivKey.Equal(keypair3.PrivKey))
require.True(t, keypair1.PubKey.Equal(keypair3.PubKey.G1Affine))

// Keypair should be valid.
data := random.Bytes(32)
signature := keypair1.SignMessage(([32]byte)(data))

isValid := signature.Verify(keypair1.GetPubKeyG2(), ([32]byte)(data))
require.True(t, isValid)
}
Loading