diff --git a/ecdsa.go b/ecdsa.go index e392a8b..58049f5 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -6,10 +6,10 @@ import ( "crypto/rand" "crypto/sha512" "crypto/x509" + "fmt" "math/big" "github.com/nspcc-dev/neofs-crypto/internal" - "github.com/pkg/errors" ) const ( @@ -39,9 +39,18 @@ const ( // P256 is base elliptic curve. var curve = elliptic.P256() -// Marshal converts a points into the uncompressed form specified in section 4.3.6 of ANSI X9.62. -func marshalXY(x, y *big.Int) []byte { - return elliptic.Marshal(curve, x, y) +// marshalXY converts points into the uncompressed form specified in section 4.3.6 of ANSI X9.62. +// It is also used to marshal signature, for backwards compatibility. +func marshalXY(curve elliptic.Curve, x, y *big.Int) []byte { + params := curve.Params() + curveOrderByteSize := params.P.BitLen() / 8 + buf := make([]byte, 1+curveOrderByteSize*2) + // r and s are not on curve at all, leave for backwards compatibility + buf[0] = 4 + _ = x.FillBytes(buf[1 : 1+curveOrderByteSize]) + _ = y.FillBytes(buf[1+curveOrderByteSize:]) + + return buf } // unmarshalXY converts a point, serialized by Marshal, into an x, y pair. @@ -76,7 +85,7 @@ func MarshalPublicKey(key *ecdsa.PublicKey) []byte { return nil } - return encodePoint(key.X, key.Y) + return elliptic.MarshalCompressed(curve, key.X, key.Y) } // UnmarshalPublicKey from bytes. @@ -92,6 +101,28 @@ func UnmarshalPublicKey(data []byte) *ecdsa.PublicKey { return nil } +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 +} + // UnmarshalPrivateKey from bytes. // It is similar to `ecdsa.Generate()` but uses pre-defined big.Int and // curve for NEO Blockchain (elliptic.P256) @@ -135,7 +166,7 @@ func VerifyHash(pub *ecdsa.PublicKey, msgHash, sig []byte) error { } else if r, s := unmarshalXY(sig); r == nil || s == nil { return ErrCannotUnmarshal } else if !ecdsa.Verify(pub, msgHash, r, s) { - return errors.Wrapf(ErrInvalidSignature, "%0x : %0x", r, s) + return fmt.Errorf("%w: %0x : %0x", ErrInvalidSignature, r, s) } return nil @@ -156,5 +187,5 @@ func SignHash(key *ecdsa.PrivateKey, msgHash []byte) ([]byte, error) { return nil, err } - return marshalXY(x, y), nil + return marshalXY(key.Curve, x, y), nil } diff --git a/ecdsa_go1.15.go b/ecdsa_go1.15.go deleted file mode 100644 index 9f640f2..0000000 --- a/ecdsa_go1.15.go +++ /dev/null @@ -1,34 +0,0 @@ -// +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 deleted file mode 100644 index a841ff2..0000000 --- a/ecdsa_older_go.go +++ /dev/null @@ -1,79 +0,0 @@ -// +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 deleted file mode 100644 index 18769ea..0000000 --- a/ecdsa_older_go_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// +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 cd8d7fa..60f55a3 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -115,12 +115,12 @@ func TestSignVerify(t *testing.T) { r1, s1, err := ecdsa.Sign(rand.Reader, key, hashBytes(data)) require.NoError(t, err) - sign := marshalXY(r1, s1) + sign := marshalXY(key.Curve, r1, s1) UnmarshalPublicKey(sign) } { // 3. bad big.Ints - sign := marshalXY(big.NewInt(0), big.NewInt(1)) + sign := marshalXY(elliptic.P256(), big.NewInt(0), big.NewInt(1)) UnmarshalPublicKey(sign) } }) @@ -129,7 +129,7 @@ func TestSignVerify(t *testing.T) { t.Run("using prepared hash", func(t *testing.T) { var ( data = []byte("Hello world") - sum = sha512.Sum512(data) + sum = sha512.Sum512(data) key = test.DecodeKey(0) ) sig, err := SignHash(key, sum[:]) @@ -148,7 +148,7 @@ func TestSignVerify(t *testing.T) { hashBytes(data)) require.NoError(t, err) - sign := marshalXY(r1, s1) + sign := marshalXY(key.Curve, r1, s1) { // This is just to validate, that we are on right way.. try to unmarshal R/S from sign // validate bytes length diff --git a/go.mod b/go.mod index e67ecdd..79b5758 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/nspcc-dev/neofs-crypto -go 1.13 +go 1.16 require ( github.com/mr-tron/base58 v1.2.0 github.com/nspcc-dev/rfc6979 v0.2.0 - github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 3e76904..4ad6ddb 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,15 @@ 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= diff --git a/load.go b/load.go index 7c61277..09b561a 100644 --- a/load.go +++ b/load.go @@ -3,9 +3,8 @@ package crypto import ( "crypto/ecdsa" "encoding/hex" - "io/ioutil" - - "github.com/pkg/errors" + "fmt" + "os" ) // LoadPrivateKey allows to load private key from various formats: @@ -13,7 +12,7 @@ import ( // - hex string // - file path (D-bytes or SEC 1 / ASN.1 DER form) func LoadPrivateKey(val string) (*ecdsa.PrivateKey, error) { - if data, err := ioutil.ReadFile(val); err == nil { + if data, err := os.ReadFile(val); err == nil { return UnmarshalPrivateKey(data) } else if data, err = hex.DecodeString(val); err == nil { return UnmarshalPrivateKey(data) @@ -21,5 +20,5 @@ func LoadPrivateKey(val string) (*ecdsa.PrivateKey, error) { return key, nil } - return nil, errors.Errorf("unknown key format (%q), expect: hex-string, wif or file-path", val) + return nil, fmt.Errorf("unknown key format (%q), expect: hex-string, wif or file-path", val) } diff --git a/rfc6979.go b/rfc6979.go index e62284e..91055b4 100644 --- a/rfc6979.go +++ b/rfc6979.go @@ -3,11 +3,11 @@ package crypto import ( "crypto/ecdsa" "crypto/sha256" + "fmt" "math/big" "github.com/nspcc-dev/neofs-crypto/internal" "github.com/nspcc-dev/rfc6979" - "github.com/pkg/errors" ) const ( @@ -60,7 +60,8 @@ func SignRFC6979Hash(key *ecdsa.PrivateKey, msgHash []byte) ([]byte, error) { func decodeSignature(sig []byte) (*big.Int, *big.Int, error) { if ln := len(sig); ln != RFC6979SignatureSize { - return nil, nil, errors.Wrapf(ErrWrongHashSize, "actual=%d, expect=%d", ln, RFC6979SignatureSize) + return nil, nil, fmt.Errorf("%w: actual=%d, expect=%d", + ErrWrongHashSize, ln, RFC6979SignatureSize) } return new(big.Int).SetBytes(sig[:32]), new(big.Int).SetBytes(sig[32:]), nil diff --git a/wif.go b/wif.go index 8c3a407..877d776 100644 --- a/wif.go +++ b/wif.go @@ -4,10 +4,10 @@ import ( "bytes" "crypto/ecdsa" "crypto/sha256" + "fmt" "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-crypto/internal" - "github.com/pkg/errors" ) const ( @@ -51,9 +51,9 @@ func WIFEncode(key *ecdsa.PrivateKey) (string, error) { func WIFDecode(wif string) (*ecdsa.PrivateKey, error) { data, err := base58.Decode(wif) if err != nil { - return nil, errors.Wrap(ErrBadWIF, err.Error()) + return nil, fmt.Errorf("%w: %v", ErrBadWIF, err) } else if actual := len(data); actual != WIFLength { - return nil, errors.Wrapf(ErrBadWIF, "expect: %d, actual: %d", WIFLength, actual) + return nil, fmt.Errorf("%w: expect: %d, actual: %d", ErrBadWIF, WIFLength, actual) } else if sum := wifCheckSum(data[:34]); !bytes.Equal(data[34:], sum) { return nil, ErrBadChecksum } diff --git a/wif_test.go b/wif_test.go index e8c8240..ca18d0c 100644 --- a/wif_test.go +++ b/wif_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/nspcc-dev/neofs-crypto/test" - "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -82,7 +81,7 @@ func TestWIF(t *testing.T) { require.NoError(t, err) require.Equal(t, current.WIF, actual) default: - require.EqualError(t, errors.Cause(err), current.Error.Error()) + require.ErrorIs(t, err, current.Error) } }) } @@ -136,7 +135,7 @@ func TestWIF(t *testing.T) { require.NoError(t, err) require.Equal(t, current.Key, actual) default: - require.EqualError(t, errors.Cause(err), current.Error.Error()) + require.ErrorIs(t, err, current.Error) } }) }