Skip to content

Commit 61d4ac4

Browse files
authored
Fix s256 public key encoding (#112)
1 parent 4c8529f commit 61d4ac4

2 files changed

Lines changed: 81 additions & 56 deletions

File tree

pkg/encoding/ecdsa.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,53 @@ package encoding
22

33
import (
44
"crypto/ecdsa"
5+
"errors"
6+
"math/big"
7+
8+
"github.com/btcsuite/btcd/btcec/v2"
59
)
610

711
func EncodeS256PubKey(pubKey *ecdsa.PublicKey) ([]byte, error) {
8-
publicKeyBytes := append(pubKey.X.Bytes(), pubKey.Y.Bytes()...)
9-
return publicKeyBytes, nil
12+
if pubKey == nil {
13+
return nil, errors.New("public key is nil")
14+
}
15+
16+
params := pubKey.Curve.Params()
17+
expected := btcec.S256().Params()
18+
if params.P.Cmp(expected.P) != 0 || params.N.Cmp(expected.N) != 0 {
19+
return nil, errors.New("unsupported curve, expected secp256k1")
20+
}
21+
22+
const coordSize = 32
23+
xBytes := pubKey.X.Bytes()
24+
yBytes := pubKey.Y.Bytes()
25+
26+
if len(xBytes) > coordSize || len(yBytes) > coordSize {
27+
return nil, errors.New("coordinate length exceeds 32 bytes")
28+
}
29+
30+
encoded := make([]byte, coordSize*2)
31+
copy(encoded[coordSize-len(xBytes):coordSize], xBytes)
32+
copy(encoded[coordSize*2-len(yBytes):], yBytes)
33+
34+
return encoded, nil
35+
}
36+
37+
func DecodeECDSAPubKey(encodedKey []byte) (*ecdsa.PublicKey, error) {
38+
if len(encodedKey) == 65 && encodedKey[0] == 0x04 {
39+
encodedKey = encodedKey[1:] // Strip uncompressed prefix
40+
}
41+
if len(encodedKey) != 64 {
42+
return nil, errors.New("invalid encoded key length, expected 64 bytes")
43+
}
44+
45+
x := new(big.Int).SetBytes(encodedKey[:32])
46+
y := new(big.Int).SetBytes(encodedKey[32:])
47+
48+
curve := btcec.S256()
49+
if !curve.IsOnCurve(x, y) {
50+
return nil, errors.New("invalid public key: point not on secp256k1 curve")
51+
}
52+
53+
return &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil
1054
}

pkg/encoding/encoding_test.go

Lines changed: 35 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,42 @@ import (
77
"math/big"
88
"testing"
99

10+
"github.com/btcsuite/btcd/btcec/v2"
1011
"github.com/decred/dcrd/dcrec/edwards/v2"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314
)
1415

1516
func TestEncodeS256PubKey(t *testing.T) {
16-
// Generate a test ECDSA key pair
17-
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
18-
require.NoError(t, err)
19-
20-
pubKey := &privateKey.PublicKey
17+
pubKey := &ecdsa.PublicKey{
18+
Curve: btcec.S256(),
19+
X: big.NewInt(1),
20+
Y: big.NewInt(2),
21+
}
2122

22-
// Test encoding
2323
encoded, err := EncodeS256PubKey(pubKey)
2424
require.NoError(t, err)
25-
assert.NotEmpty(t, encoded)
26-
27-
// The encoded key should contain both X and Y coordinates appended together
28-
xBytes := pubKey.X.Bytes()
29-
yBytes := pubKey.Y.Bytes()
30-
expectedLength := len(xBytes) + len(yBytes)
31-
assert.Equal(t, expectedLength, len(encoded))
25+
require.Len(t, encoded, 64)
3226

33-
// Verify the encoded data contains the coordinates
34-
assert.Equal(t, xBytes, encoded[:len(xBytes)])
35-
assert.Equal(t, yBytes, encoded[len(xBytes):])
27+
expectedX := pubKey.X.FillBytes(make([]byte, 32))
28+
expectedY := pubKey.Y.FillBytes(make([]byte, 32))
29+
expected := append(expectedX, expectedY...)
30+
assert.Equal(t, expected, encoded)
3631
}
3732

38-
func TestEncodeS256PubKey_SpecificValues(t *testing.T) {
39-
// Create a public key with specific values
40-
x := big.NewInt(12345)
41-
y := big.NewInt(67890)
42-
pubKey := &ecdsa.PublicKey{
43-
Curve: elliptic.P256(),
44-
X: x,
45-
Y: y,
46-
}
33+
func TestEncodeS256PubKey_WithValidKey(t *testing.T) {
34+
privateKey, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader)
35+
require.NoError(t, err)
36+
37+
pubKey := &privateKey.PublicKey
4738

4839
encoded, err := EncodeS256PubKey(pubKey)
4940
require.NoError(t, err)
41+
require.Len(t, encoded, 64)
5042

51-
// Verify the encoding - should be X bytes followed by Y bytes
52-
xBytes := x.Bytes()
53-
yBytes := y.Bytes()
54-
expected := append(xBytes, yBytes...)
55-
43+
expectedX := pubKey.X.FillBytes(make([]byte, 32))
44+
expectedY := pubKey.Y.FillBytes(make([]byte, 32))
45+
expected := append(expectedX, expectedY...)
5646
assert.Equal(t, expected, encoded)
5747
}
5848

@@ -135,34 +125,25 @@ func TestEncodeDecodeEDDSA_RoundTrip(t *testing.T) {
135125
}
136126

137127
func TestEncodeS256PubKey_NilPublicKey(t *testing.T) {
138-
// Test with nil public key - this should panic or return an error
139-
// depending on the implementation
140-
defer func() {
141-
if r := recover(); r != nil {
142-
// Expected panic due to nil pointer
143-
t.Log("Correctly panicked on nil public key")
144-
}
145-
}()
146-
147128
_, err := EncodeS256PubKey(nil)
148-
if err == nil {
149-
t.Error("Expected error or panic with nil public key")
150-
}
129+
require.EqualError(t, err, "public key is nil")
151130
}
152131

153-
func TestEncodeS256PubKey_ZeroCoordinates(t *testing.T) {
154-
// Test with zero coordinates
155-
x := big.NewInt(0)
156-
y := big.NewInt(0)
132+
func TestEncodeS256PubKey_UnsupportedCurve(t *testing.T) {
133+
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
134+
require.NoError(t, err)
135+
136+
_, err = EncodeS256PubKey(&privateKey.PublicKey)
137+
require.ErrorContains(t, err, "unsupported curve")
138+
}
139+
140+
func TestEncodeS256PubKey_CoordinateTooLarge(t *testing.T) {
157141
pubKey := &ecdsa.PublicKey{
158-
Curve: elliptic.P256(),
159-
X: x,
160-
Y: y,
142+
Curve: btcec.S256(),
143+
X: new(big.Int).Lsh(big.NewInt(1), 256),
144+
Y: big.NewInt(0),
161145
}
162146

163-
encoded, err := EncodeS256PubKey(pubKey)
164-
require.NoError(t, err)
165-
166-
// Should still work, though the result will be a very short byte array
167-
assert.NotNil(t, encoded)
147+
_, err := EncodeS256PubKey(pubKey)
148+
require.EqualError(t, err, "coordinate length exceeds 32 bytes")
168149
}

0 commit comments

Comments
 (0)