diff --git a/libevm/ethtest/devkey.go b/libevm/ethtest/devkey.go new file mode 100644 index 0000000000..9f547c5433 --- /dev/null +++ b/libevm/ethtest/devkey.go @@ -0,0 +1,66 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package ethtest + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/libevm/crypto" +) + +// UNSAFEDeterministicPrivateKey returns a new [crypto.S256] private key, +// deterministically generated from the `seed`. +func UNSAFEDeterministicPrivateKey(tb testing.TB, seed []byte) *ecdsa.PrivateKey { + tb.Helper() + + curve := crypto.S256() + d := privateKeyScalar(tb, seed, curve) + + x, y := curve.ScalarBaseMult(d.Bytes()) + return &ecdsa.PrivateKey{ + D: d, + PublicKey: ecdsa.PublicKey{ + X: x, + Y: y, + Curve: curve, + }, + } +} + +func privateKeyScalar(tb testing.TB, seed []byte, curve elliptic.Curve) *big.Int { + tb.Helper() + + s := crypto.NewKeccakState() + _, err := s.Write(seed) + require.NoError(tb, err, "%T.Write()", s) + + buf := make([]byte, 32) + for { + _, err := s.Read(buf) + require.NoError(tb, err, "%T.Read()", s) + d := new(big.Int).SetBytes(buf) + + if isZero := d.Sign() == 0; !isZero && d.Cmp(curve.Params().N) == -1 { + return d + } + } +} diff --git a/libevm/ethtest/devkey_test.go b/libevm/ethtest/devkey_test.go new file mode 100644 index 0000000000..443909a401 --- /dev/null +++ b/libevm/ethtest/devkey_test.go @@ -0,0 +1,72 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package ethtest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/crypto" + "github.com/ava-labs/libevm/params" +) + +func TestDeterministicPrivateKey(t *testing.T) { + tests := []struct { + seed []byte + // Specific values are random, but we lock them in to ensure + // deterministic generation. + want common.Address + }{ + { + seed: nil, + want: common.HexToAddress("0x9cce34F7aB185c7ABA1b7C8140d620B4BDA941d6"), + }, + { + seed: []byte{0}, + want: common.HexToAddress("0xa385D2E939787Af0B304512b2b6d56364F1722FA"), + }, + { + seed: []byte{1}, + want: common.HexToAddress("0x3Eea25034397B249a3eD8614BB4d0533e5b03594"), + }, + } + + signer := types.LatestSigner(params.MergedTestChainConfig) + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + key := UNSAFEDeterministicPrivateKey(t, tt.seed) + + t.Run("address_from_pubkey", func(t *testing.T) { + got := crypto.PubkeyToAddress(key.PublicKey) + require.Equal(t, tt.want, got, "crypto.PubKeyToAddress(UNSAFEDeterministicPrivateKey())") + }) + + t.Run("address_via_sender_recovery", func(t *testing.T) { + got, err := types.Sender( + signer, + types.MustSignNewTx(key, signer, &types.LegacyTx{}), + ) + require.NoError(t, err, "types.Sender(...)") + require.Equal(t, tt.want, got, "types.Sender(..., types.MustSignNewTx(UNSAFEDeterministicPrivateKey(), ....))") + }) + }) + } +}