diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index d2c3e2d79..418f943a6 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -626,7 +626,7 @@ func (ic *interopContext) eccRecover(curve elliptic.Curve, v *vm.VM) error { v.Estack().PushVal([]byte{}) return nil } - v.Estack().PushVal(pKey.Bytes()[1:]) + v.Estack().PushVal(pKey.UncompressedBytes()[1:]) return nil } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 86c648b63..d03a864f5 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -474,7 +474,7 @@ func TestSecp256k1Recover(t *testing.T) { X: privateKey.PubKey().X, Y: privateKey.PubKey().Y, } - expected := pubKey.Bytes()[1:] + expected := pubKey.UncompressedBytes()[1:] // We don't know which of two recovered keys suites, so let's try both. putOnStackGetResult := func(isEven bool) []byte { @@ -505,7 +505,7 @@ func TestSecp256r1Recover(t *testing.T) { messageHash := hash.Sha256(message).BytesBE() signature := privateKey.Sign(message) require.True(t, privateKey.PublicKey().Verify(signature, messageHash)) - expected := privateKey.PublicKey().Bytes()[1:] + expected := privateKey.PublicKey().UncompressedBytes()[1:] // We don't know which of two recovered keys suites, so let's try both. putOnStackGetResult := func(isEven bool) []byte { diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index 5bcdc2c51..471d06675 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -1,7 +1,6 @@ package keys import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/x509" @@ -19,6 +18,9 @@ import ( "github.com/pkg/errors" ) +// coordLen is the number of bytes in serialized X or Y coordinate. +const coordLen = 32 + // PublicKeys is a list of public keys. type PublicKeys []*PublicKey @@ -95,23 +97,49 @@ func NewPublicKeyFromString(s string) (*PublicKey, error) { return pubKey, nil } -// Bytes returns the byte array representation of the public key. -func (p *PublicKey) Bytes() []byte { +// getBytes serializes X and Y using compressed or uncompressed format. +func (p *PublicKey) getBytes(compressed bool) []byte { if p.IsInfinity() { return []byte{0x00} } - var ( - x = p.X.Bytes() - paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...) - prefix = byte(0x03) - ) - - if p.Y.Bit(0) == 0 { - prefix = byte(0x02) + var resLen = 1 + coordLen + if !compressed { + resLen += coordLen } + var res = make([]byte, resLen) + var prefix byte - return append([]byte{prefix}, paddedX...) + xBytes := p.X.Bytes() + copy(res[1+coordLen-len(xBytes):], xBytes) + if compressed { + if p.Y.Bit(0) == 0 { + prefix = 0x02 + } else { + prefix = 0x03 + } + } else { + prefix = 0x04 + yBytes := p.Y.Bytes() + copy(res[1+coordLen+coordLen-len(yBytes):], yBytes) + + } + res[0] = prefix + + return res +} + +// Bytes returns byte array representation of the public key in compressed +// form (33 bytes with 0x02 or 0x03 prefix, except infinity which is always 0). +func (p *PublicKey) Bytes() []byte { + return p.getBytes(true) +} + +// UncompressedBytes returns byte array representation of the public key in +// uncompressed form (65 bytes with 0x04 prefix, except infinity which is +// always 0). +func (p *PublicKey) UncompressedBytes() []byte { + return p.getBytes(false) } // NewPublicKeyFromASN1 returns a NEO PublicKey from the ASN.1 serialized key. diff --git a/pkg/crypto/keys/publickey_test.go b/pkg/crypto/keys/publickey_test.go index f140fce49..ddee5a332 100644 --- a/pkg/crypto/keys/publickey_test.go +++ b/pkg/crypto/keys/publickey_test.go @@ -89,10 +89,14 @@ func TestPubkeyToAddress(t *testing.T) { func TestDecodeBytes(t *testing.T) { pubKey := getPubKey(t) - decodedPubKey := &PublicKey{} - err := decodedPubKey.DecodeBytes(pubKey.Bytes()) - require.NoError(t, err) - require.Equal(t, pubKey, decodedPubKey) + var testBytesFunction = func(t *testing.T, bytesFunction func() []byte) { + decodedPubKey := &PublicKey{} + err := decodedPubKey.DecodeBytes(bytesFunction()) + require.NoError(t, err) + require.Equal(t, pubKey, decodedPubKey) + } + t.Run("compressed", func(t *testing.T) { testBytesFunction(t, pubKey.Bytes) }) + t.Run("uncompressed", func(t *testing.T) { testBytesFunction(t, pubKey.UncompressedBytes) }) } func TestDecodeBytesBadInfinity(t *testing.T) {