Skip to content

Commit

Permalink
Improvements to the test random library. (#1029)
Browse files Browse the repository at this point in the history
Signed-off-by: Cody Littley <[email protected]>
  • Loading branch information
cody-littley authored Dec 18, 2024
1 parent 043a178 commit b77cbd5
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 15 deletions.
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) {
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)
}

0 comments on commit b77cbd5

Please sign in to comment.