Skip to content

Fix s256 public key encoding#112

Merged
anhthii merged 1 commit into
masterfrom
fix-s256-encoding
Nov 1, 2025
Merged

Fix s256 public key encoding#112
anhthii merged 1 commit into
masterfrom
fix-s256-encoding

Conversation

@anhthii
Copy link
Copy Markdown
Contributor

@anhthii anhthii commented Nov 1, 2025

Fix s256 Public Key Encoding

Problem

The secp256k1 public key encoding implementation had some issues:

  • No zero-padding: Small coordinate values produced variable-length output (1-64 bytes instead of fixed 64 bytes)
  • No curve validation: Accepted keys from any elliptic curve (P-256, Ed25519, etc.)
  • No safety checks: Nil pointers caused panics, no validation of points on curve
  • Missing decode function: No way to decode encoded keys back to public keys

Example Issue

// Before: Variable length output
x := big.NewInt(1)  // 1 byte
y := big.NewInt(2)  // 1 byte
encoded := append(x.Bytes(), y.Bytes()...)  // Only 2 bytes! Should be 64.

// Could also encode wrong curve
p256Key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
EncodeS256PubKey(&p256Key.PublicKey)  // Silently accepts wrong curve!

Solution

1. Fixed EncodeS256PubKey in pkg/encoding/ecdsa.go

func EncodeS256PubKey(pubKey *ecdsa.PublicKey) ([]byte, error) {
	if pubKey == nil {
		return nil, errors.New("public key is nil")
	}

	// Validate curve is secp256k1
	params := pubKey.Curve.Params()
	expected := btcec.S256().Params()
	if params.P.Cmp(expected.P) != 0 || params.N.Cmp(expected.N) != 0 {
		return nil, errors.New("unsupported curve, expected secp256k1")
	}

	// Always output exactly 64 bytes with zero-padding
	const coordSize = 32
	xBytes := pubKey.X.Bytes()
	yBytes := pubKey.Y.Bytes()

	if len(xBytes) > coordSize || len(yBytes) > coordSize {
		return nil, errors.New("coordinate length exceeds 32 bytes")
	}

	encoded := make([]byte, coordSize*2)
	copy(encoded[coordSize-len(xBytes):coordSize], xBytes)      // Zero-pad X
	copy(encoded[coordSize*2-len(yBytes):], yBytes)            // Zero-pad Y

	return encoded, nil
}

Key improvements:

  • Fixed 64-byte output with proper zero-padding
  • Validates curve is secp256k1
  • Error handling for nil pointers
  • Validates coordinate sizes

2. Added DecodeECDSAPubKey function

func DecodeECDSAPubKey(encodedKey []byte) (*ecdsa.PublicKey, error) {
	// Handle uncompressed format (0x04 prefix)
	if len(encodedKey) == 65 && encodedKey[0] == 0x04 {
		encodedKey = encodedKey[1:]
	}

	if len(encodedKey) != 64 {
		return nil, errors.New("invalid encoded key length, expected 64 bytes")
	}

	x := new(big.Int).SetBytes(encodedKey[:32])
	y := new(big.Int).SetBytes(encodedKey[32:])

	curve := btcec.S256()
	if !curve.IsOnCurve(x, y) {
		return nil, errors.New("invalid public key: point not on secp256k1 curve")
	}

	return &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil
}

Features:

  • Supports both 64-byte and 65-byte (with 0x04 prefix) formats
  • Validates point is on secp256k1 curve
  • Proper error handling

3. Enhanced Test Coverage in pkg/encoding/encoding_test.go

Added 5 comprehensive tests:

  1. TestEncodeS256PubKey - Validates zero-padding for small coordinates
  2. TestEncodeS256PubKey_WithValidKey - Tests real secp256k1 keys
  3. TestEncodeS256PubKey_NilPublicKey - Validates nil handling
  4. TestEncodeS256PubKey_UnsupportedCurve - Rejects non-secp256k1 curves (P-256, etc.)
  5. TestEncodeS256PubKey_CoordinateTooLarge - Validates overflow protection

Testing

# Run encoding tests
go test -v ./pkg/encoding/

# Run with coverage
go test -v -coverprofile=coverage.out ./pkg/encoding/
go tool cover -func=coverage.out

Impact

Before

  • ❌ Variable-length encoding (1-64 bytes)
  • ❌ Accepts any elliptic curve
  • ❌ Nil pointers cause panics
  • ❌ No decode function
  • ❌ Potential security vulnerabilities

After

  • ✅ Fixed 64-byte encoding (always)
  • ✅ Only accepts secp256k1 keys
  • ✅ Proper error handling
  • ✅ Full encode/decode support
  • ✅ Secure against elliptic curve attacks

Breaking Changes

Low impact: Most existing code will continue to work. The function now returns errors for:

  • Non-secp256k1 curves (was silently accepted)
  • Nil pointers (was panic)

Files Changed

  • pkg/encoding/ecdsa.go (+48, -2)
  • pkg/encoding/encoding_test.go (+89, -56)

@anhthii anhthii merged commit 61d4ac4 into master Nov 1, 2025
25 checks passed
@anhthii anhthii deleted the fix-s256-encoding branch November 1, 2025 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant