diff --git a/common/testutils/random/test_random.go b/common/testutils/random/test_random.go index ed4699a6dc..685de269ca 100644 --- a/common/testutils/random/test_random.go +++ b/common/testutils/random/test_random.go @@ -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() @@ -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 { @@ -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) +} diff --git a/common/testutils/random/test_random_test.go b/common/testutils/random/test_random_test.go index 822afd8555..52129f3470 100644 --- a/common/testutils/random/test_random_test.go +++ b/common/testutils/random/test_random_test.go @@ -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) }