forked from TrueCloudLab/frostfs-crypto
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>
This commit is contained in:
parent
225b24f7f4
commit
d29d7f9c3e
11 changed files with 55 additions and 177 deletions
45
ecdsa.go
45
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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
3
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
|
||||
)
|
||||
|
|
10
go.sum
10
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=
|
||||
|
|
9
load.go
9
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)
|
||||
}
|
||||
|
|
|
@ -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
6
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue