Fix tests on go1.19 (#16)

* [#15] ecdsa: Fix signature marshaling with go1.19

Signed-off-by: Evgenii Stratonikov <stratonikov@runbox.com>

* [#15] go.mod: Update go version to 1.16

Signed-off-by: Evgenii Stratonikov <stratonikov@runbox.com>

* [#15] go.mod: Drop `pkg/errors` dependency

Signed-off-by: Evgenii Stratonikov <stratonikov@runbox.com>

* [#15] *: Perform `goimports -w`

Signed-off-by: Evgenii Stratonikov <stratonikov@runbox.com>

Signed-off-by: Evgenii Stratonikov <stratonikov@runbox.com>
Co-authored-by: Evgenii Stratonikov <stratonikov@runbox.com>
pull/1/head
Evgenii Stratonikov 2022-08-15 14:49:39 +03:00 committed by GitHub
parent 225b24f7f4
commit d29d7f9c3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 55 additions and 177 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}
})
}

View File

@ -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

3
go.mod
View File

@ -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
)

10
go.sum
View File

@ -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=

View File

@ -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)
}

View File

@ -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

6
wif.go
View File

@ -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
}

View File

@ -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)
}
})
}