Skip to content

Commit edcedcc

Browse files
authored
fix: reject malformed ed25519 private keys in PrivateKeyFromBase58
* feat: add test for private key validation against mismatched seed and public key * address review commnets
1 parent c3b10a0 commit edcedcc

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

keys.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@ func ValidatePrivateKey(b []byte) (bool, error) {
7272
if len(b) != ed25519.PrivateKeySize {
7373
return false, fmt.Errorf("invalid private key size, expected %v, got %d", ed25519.PrivateKeySize, len(b))
7474
}
75-
// check if the public key is on the ed25519 curve
76-
pub := ed25519.PrivateKey(b).Public().(ed25519.PublicKey)
77-
if !IsOnCurve(pub) {
78-
return false, errors.New("the corresponding public key is NOT on the ed25519 curve")
75+
76+
// ed25519 private keys are seed(32) + public(32); ensure they match.
77+
derived := ed25519.NewKeyFromSeed(b[:ed25519.SeedSize])
78+
if !bytes.Equal(derived, b) {
79+
if !IsOnCurve(b[ed25519.SeedSize:]) {
80+
return false, errors.New("invalid private key: seed/public key mismatch (provided public key is NOT on the ed25519 curve)")
81+
}
82+
return false, errors.New("invalid private key: seed/public key mismatch")
7983
}
8084
return true, nil
8185
}

keys_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package solana
1919

2020
import (
21+
"crypto/ed25519"
2122
"encoding/binary"
2223
"encoding/hex"
2324
"errors"
@@ -143,6 +144,43 @@ func TestPrivateKeyFromSolanaKeygenFile(t *testing.T) {
143144
}
144145
}
145146

147+
func TestPrivateKeyFromBase58RejectsMismatchedSeedAndPublicKey(t *testing.T) {
148+
original := MustPrivateKeyFromBase58("66cDvko73yAf8LYvFMM3r8vF5vJtkk7JKMgEKwkmBC86oHdq41C7i1a2vS3zE1yCcdLLk6VUatUb32ZzVjSBXtRs")
149+
require.Len(t, original, PrivateKeyLength)
150+
151+
tampered := append([]byte(nil), original...)
152+
tampered[0] ^= 0xFF
153+
154+
valid, err := ValidatePrivateKey(tampered)
155+
require.False(t, valid)
156+
require.EqualError(t, err, "invalid private key: seed/public key mismatch")
157+
158+
_, err = PrivateKeyFromBase58(PrivateKey(tampered).String())
159+
require.EqualError(t, err, "invalid private key: seed/public key mismatch")
160+
}
161+
162+
func TestPrivateKeyFromBase58ReturnsDiagnosticForOffCurvePublicKey(t *testing.T) {
163+
original := MustPrivateKeyFromBase58("66cDvko73yAf8LYvFMM3r8vF5vJtkk7JKMgEKwkmBC86oHdq41C7i1a2vS3zE1yCcdLLk6VUatUb32ZzVjSBXtRs")
164+
require.Len(t, original, PrivateKeyLength)
165+
166+
tampered := append([]byte(nil), original...)
167+
offCurve := make([]byte, ed25519.PublicKeySize)
168+
found := false
169+
for i := uint32(0); i < 100_000; i++ {
170+
binary.LittleEndian.PutUint32(offCurve[:4], i)
171+
if !IsOnCurve(offCurve) {
172+
found = true
173+
break
174+
}
175+
}
176+
require.True(t, found, "expected to find an off-curve public key test vector")
177+
copy(tampered[ed25519.SeedSize:], offCurve)
178+
179+
valid, err := ValidatePrivateKey(tampered)
180+
require.False(t, valid)
181+
require.EqualError(t, err, "invalid private key: seed/public key mismatch (provided public key is NOT on the ed25519 curve)")
182+
}
183+
146184
func TestPublicKey_MarshalText(t *testing.T) {
147185
keyString := "4wBqpZM9k69W87zdYXT2bRtLViWqTiJV3i2Kn9q7S6j"
148186
keyParsed := MustPublicKeyFromBase58(keyString)

0 commit comments

Comments
 (0)