diff --git a/ecdsa.go b/ecdsa.go index 78ecfed..df66cf4 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -70,75 +70,6 @@ func unmarshalXY(data []byte) (x *big.Int, y *big.Int) { return } -// decompressPoints using formula y² = x³ - 3x + b -// crypto/elliptic/elliptic.go:55 -func decompressPoints(x *big.Int, yBit uint) (*big.Int, *big.Int) { - params := curve.Params() - - x3 := new(big.Int).Mul(x, x) - x3.Mul(x3, x) - - threeX := new(big.Int).Lsh(x, 1) - threeX.Add(threeX, x) - - x3.Sub(x3, threeX) - x3.Add(x3, params.B) - x3.Mod(x3, params.P) - - // y = √(x³ - 3x + b) mod p - y := new(big.Int).ModSqrt(x3, params.P) - - // big.Int.Jacobi(a, b) can return nil - if y == nil { - return nil, nil - } - - if y.Bit(0) != (yBit & 0x1) { - y.Neg(y) - y.Mod(y, params.P) - } - - return x, y -} - -func encodePoint(x, y *big.Int) []byte { - data := make([]byte, PublicKeyCompressedSize) - i := PublicKeyCompressedSize - len(x.Bytes()) - copy(data[i:], x.Bytes()) - - if y.Bit(0) == 0x1 { - data[0] = 0x3 - } else { - data[0] = 0x2 - } - - return data -} - -func decodePoint(data []byte) (*big.Int, *big.Int) { - // empty data - if len(data) == 0 { - return nil, nil - } - - switch prefix := data[0]; prefix { - case 0x02, 0x03: // compressed key - // Incorrect length for compressed encoding - if len(data) != PublicKeyCompressedSize { - return nil, nil - } - - return decompressPoints(new(big.Int).SetBytes(data[1:]), uint(prefix)) - case 0x04: // uncompressed key - // To get the public key, besides getting it from the data and checking, - // we also must to check that the points are on an elliptic curve - return unmarshalXY(data) - } - - // unknown type - return nil, nil -} - // MarshalPublicKey to bytes. func MarshalPublicKey(key *ecdsa.PublicKey) []byte { if key == nil || key.X == nil || key.Y == nil { @@ -150,7 +81,7 @@ func MarshalPublicKey(key *ecdsa.PublicKey) []byte { // UnmarshalPublicKey from bytes. func UnmarshalPublicKey(data []byte) *ecdsa.PublicKey { - if x, y := decodePoint(data); x != nil && y != nil && curve.IsOnCurve(x, y) { + if x, y := decodePoint(data); x != nil && y != nil { return &ecdsa.PublicKey{ Curve: curve, X: x, diff --git a/ecdsa_go1.15.go b/ecdsa_go1.15.go new file mode 100644 index 0000000..9f640f2 --- /dev/null +++ b/ecdsa_go1.15.go @@ -0,0 +1,34 @@ +// +build go1.15 + +package crypto + +import ( + "crypto/elliptic" + "math/big" +) + +func encodePoint(x, y *big.Int) []byte { + return elliptic.MarshalCompressed(curve, x, y) +} + +func decodePoint(data []byte) (x *big.Int, y *big.Int) { + // empty data + if len(data) == 0 { + return + } + + // tries to unmarshal compressed form + // returns (nil, nil) when: + // - wrong len(data) + // - data[0] != 2 && data[0] != 3 + if x, y = elliptic.UnmarshalCompressed(curve, data); x != nil && y != nil { + return x, y + } + + // tries to unmarshal uncompressed form and check that points on curve + if x, y = unmarshalXY(data); x == nil || y == nil || !curve.IsOnCurve(x, y) { + x, y = nil, nil + } + + return x, y +} diff --git a/ecdsa_older_go.go b/ecdsa_older_go.go new file mode 100644 index 0000000..a841ff2 --- /dev/null +++ b/ecdsa_older_go.go @@ -0,0 +1,79 @@ +// +build !go1.15 + +package crypto + +import "math/big" + +// decompressPoints using formula y² = x³ - 3x + b +// crypto/elliptic/elliptic.go:55 +func decompressPoints(x *big.Int, yBit uint) (*big.Int, *big.Int) { + params := curve.Params() + + x3 := new(big.Int).Mul(x, x) + x3.Mul(x3, x) + + threeX := new(big.Int).Lsh(x, 1) + threeX.Add(threeX, x) + + x3.Sub(x3, threeX) + x3.Add(x3, params.B) + x3.Mod(x3, params.P) + + // y = √(x³ - 3x + b) mod p + y := new(big.Int).ModSqrt(x3, params.P) + + // big.Int.Jacobi(a, b) can return nil + if y == nil { + return nil, nil + } + + if y.Bit(0) != (yBit & 0x1) { + y.Neg(y) + y.Mod(y, params.P) + } + + return x, y +} + +func encodePoint(x, y *big.Int) []byte { + data := make([]byte, PublicKeyCompressedSize) + i := PublicKeyCompressedSize - len(x.Bytes()) + copy(data[i:], x.Bytes()) + + if y.Bit(0) == 0x1 { + data[0] = 0x3 + } else { + data[0] = 0x2 + } + + return data +} + +func decodePoint(data []byte) (x *big.Int, y *big.Int) { + // empty data + if len(data) == 0 { + return + } + + switch prefix := data[0]; prefix { + case 0x02, 0x03: // compressed key + // Incorrect length for compressed encoding + if len(data) != PublicKeyCompressedSize { + return nil, nil + } + + x, y = decompressPoints(new(big.Int).SetBytes(data[1:]), uint(prefix)) + case 0x04: // uncompressed key + // To get the public key, besides getting it from the data and checking, + // we also must to check that the points are on an elliptic curve + x, y = unmarshalXY(data) + default: // unknown type + return + } + + if x == nil || y == nil || !curve.IsOnCurve(x, y) { + x, y = nil, nil + } + + return +} diff --git a/ecdsa_older_go_test.go b/ecdsa_older_go_test.go new file mode 100644 index 0000000..18769ea --- /dev/null +++ b/ecdsa_older_go_test.go @@ -0,0 +1,28 @@ +// +build !go1.15 + +package crypto + +import ( + "crypto/ecdsa" + "encoding/hex" + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_decompressPoints(t *testing.T) { + t.Run("prepared public keys: decompressPoints", func(t *testing.T) { + for i := range testKeys { + bytes, err := hex.DecodeString(testKeys[i]) + require.NoErrorf(t, err, testKeys[i]) + + x, y := decompressPoints(new(big.Int).SetBytes(bytes[1:]), uint(bytes[0])) + require.NotNil(t, x) + require.NotNil(t, y) + + res := MarshalPublicKey(&ecdsa.PublicKey{Curve: curve, X: x, Y: y}) + require.Equal(t, testKeys[i], hex.EncodeToString(res)) + } + }) +} diff --git a/ecdsa_test.go b/ecdsa_test.go index 681704c..3190a46 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -30,22 +30,6 @@ var testKeys = [...]string{ "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61", } -func Test_decompressPoints(t *testing.T) { - t.Run("prepared public keys: decompressPoints", func(t *testing.T) { - for i := range testKeys { - bytes, err := hex.DecodeString(testKeys[i]) - require.NoErrorf(t, err, testKeys[i]) - - x, y := decompressPoints(new(big.Int).SetBytes(bytes[1:]), uint(bytes[0])) - require.NotNil(t, x) - require.NotNil(t, y) - - res := MarshalPublicKey(&ecdsa.PublicKey{Curve: curve, X: x, Y: y}) - require.Equal(t, testKeys[i], hex.EncodeToString(res)) - } - }) -} - func TestMarshalUnmarshal(t *testing.T) { t.Run("prepared public keys: unmarshal / marshal", func(t *testing.T) { for i := range testKeys { diff --git a/go.mod b/go.mod index 4c3f661..e67ecdd 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/nspcc-dev/neofs-crypto go 1.13 require ( - github.com/mr-tron/base58 v1.1.2 + github.com/mr-tron/base58 v1.2.0 github.com/nspcc-dev/rfc6979 v0.2.0 - github.com/pkg/errors v0.8.1 - github.com/stretchr/testify v1.4.0 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index d6d8473..3e76904 100644 --- a/go.sum +++ b/go.sum @@ -2,16 +2,24 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=