From 14a3ff9b090119b5f7b92b6bbc723a560de0aca2 Mon Sep 17 00:00:00 2001 From: pikomonde <32364823+pikomonde@users.noreply.github.com> Date: Tue, 30 Jul 2024 23:15:18 +0700 Subject: [PATCH 1/2] create unit test and docs for PrivateKeyFromBase58 and MustPrivateKeyFromBase58 --- keys.go | 2 ++ keys_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/keys.go b/keys.go index fa650fe7..d4035f1d 100644 --- a/keys.go +++ b/keys.go @@ -37,6 +37,7 @@ import ( type PrivateKey []byte +// MustPrivateKeyFromBase58 returns a PrivateKey from a base58-encoded string, panicking if the input is invalid. func MustPrivateKeyFromBase58(in string) PrivateKey { out, err := PrivateKeyFromBase58(in) if err != nil { @@ -45,6 +46,7 @@ func MustPrivateKeyFromBase58(in string) PrivateKey { return out } +// PrivateKeyFromBase58 returns a PrivateKey from a base58-encoded string. func PrivateKeyFromBase58(privkey string) (PrivateKey, error) { res, err := base58.Decode(privkey) if err != nil { diff --git a/keys_test.go b/keys_test.go index 923c3206..a40c325c 100644 --- a/keys_test.go +++ b/keys_test.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "errors" "flag" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -101,6 +102,89 @@ func TestPublicKeyFromBase58(t *testing.T) { } } +func TestPrivateKeyFromBase58(t *testing.T) { + tests := []struct { + name string + in string + want string + wantErr error + }{ + { + name: "normal case", + in: "3yZe7d", + want: "3yZe7d", + }, + { + name: "edge case - empty string", + in: "", + want: "", + wantErr: errors.New("zero length string"), + }, + { + name: "edge case - invalid base58", + in: "invalid-base58", + want: "", + wantErr: errors.New("invalid base58 digit ('l')"), + }, + { + name: "extreme case - very long input", + in: strings.Repeat("3yZe7d", 100), + want: strings.Repeat("3yZe7d", 100), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := PrivateKeyFromBase58(test.in) + require.Equal(t, test.wantErr, err) + require.Equal(t, test.want, got.String()) + }) + } +} + +func TestMustPrivateKeyFromBase58(t *testing.T) { + tests := []struct { + name string + in string + want string + wantPanic bool + }{ + { + name: "normal case", + in: "3yZe7d", + want: "3yZe7d", + }, + { + name: "edge case - empty string", + in: "", + wantPanic: true, + }, + { + name: "edge case - invalid base58", + in: "invalid-base58", + wantPanic: true, + }, + { + name: "extreme case - very long input", + in: strings.Repeat("3yZe7d", 100), + want: strings.Repeat("3yZe7d", 100), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.wantPanic { + require.Panics(t, func() { + MustPrivateKeyFromBase58(test.in) + }) + } else { + got := MustPrivateKeyFromBase58(test.in) + require.Equal(t, test.want, got.String()) + } + }) + } +} + func TestPrivateKeyFromSolanaKeygenFile(t *testing.T) { tests := []struct { inFile string From 91e428090c95caf2aa64ce58772e9c5f288964d4 Mon Sep 17 00:00:00 2001 From: pikomonde <32364823+pikomonde@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:04:58 +0700 Subject: [PATCH 2/2] update PrivateKeyFromBase58 with IsOnCurve --- keys.go | 27 +++++++++++++++++++++++++++ keys_test.go | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/keys.go b/keys.go index d4035f1d..dde406ae 100644 --- a/keys.go +++ b/keys.go @@ -47,11 +47,29 @@ func MustPrivateKeyFromBase58(in string) PrivateKey { } // PrivateKeyFromBase58 returns a PrivateKey from a base58-encoded string. +// +// PrivateKeyFromBase58 returns a PrivateKey from a base58-encoded string. The function +// first decodes the input string using base58, then checks if the resulting private key +// is valid by deriving the corresponding public key and checking if it is on the Ed25519 +// curve. If the private key is invalid, an error is returned. +// +// Parameters: +// +// privkey - the base58-encoded private key string +// +// Returns: +// +// PrivateKey - the decoded private key +// error - an error if the input string is invalid or the derived public key is not on the curve func PrivateKeyFromBase58(privkey string) (PrivateKey, error) { res, err := base58.Decode(privkey) if err != nil { return nil, err } + pub := PrivateKey(res).PublicKey().Bytes() + if !IsOnCurve(pub) { + return nil, errors.New("invalid private key") + } return res, nil } @@ -74,6 +92,15 @@ func (k PrivateKey) String() string { return base58.Encode(k) } +// NewRandomPrivateKey generates a new random Ed25519 private key. +// +// NewRandomPrivateKey returns a new random Ed25519 private key. The private key is +// generated using a cryptographically secure random number generator. +// +// Returns: +// +// PrivateKey: a new random Ed25519 private key +// error: an error if the key generation fails func NewRandomPrivateKey() (PrivateKey, error) { pub, priv, err := ed25519.GenerateKey(crypto_rand.Reader) if err != nil { diff --git a/keys_test.go b/keys_test.go index a40c325c..d29e9dac 100644 --- a/keys_test.go +++ b/keys_test.go @@ -18,6 +18,8 @@ package solana import ( + "crypto/ed25519" + "crypto/rand" "encoding/binary" "encoding/hex" "errors" @@ -111,8 +113,8 @@ func TestPrivateKeyFromBase58(t *testing.T) { }{ { name: "normal case", - in: "3yZe7d", - want: "3yZe7d", + in: "6HsFaXKVD7mo43oTbdqyGgAnYFeNNhqY75B3JGJ6K8a227KjjG3uW3v", + want: "6HsFaXKVD7mo43oTbdqyGgAnYFeNNhqY75B3JGJ6K8a227KjjG3uW3v", }, { name: "edge case - empty string", @@ -479,7 +481,38 @@ func TestGetAddedRemoved(t *testing.T) { ) } } +func TestIsOnCurve(t *testing.T) { + // Test a valid private key + privateKey, err := NewRandomPrivateKey() + if err != nil { + t.Errorf("Failed to generate private key: %v", err) + } + // Test a valid public key + publicKey := privateKey.PublicKey() + if !IsOnCurve(publicKey.Bytes()) { + t.Errorf("Valid public key is not on the curve") + } + + // Test an invalid key (too short) + shortKey := []byte{1, 2, 3} + if IsOnCurve(shortKey) { + t.Errorf("Invalid key (too short) is on the curve") + } + + // Test an invalid key (too long) + longKey := make([]byte, ed25519.PrivateKeySize+1) + if IsOnCurve(longKey) { + t.Errorf("Invalid key (too long) is on the curve") + } + + // Test an invalid key (random bytes) + randKey := make([]byte, ed25519.PrivateKeySize) + _, _ = rand.Read(randKey) + if IsOnCurve(randKey) { + t.Errorf("Invalid key (random bytes) is on the curve") + } +} func TestIsNativeProgramID(t *testing.T) { require.True(t, isNativeProgramID(ConfigProgramID)) }