package crypto import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" "math/big" "github.com/nspcc-dev/neofs-crypto/internal" "github.com/pkg/errors" ) const ( // ErrEmptyPublicKey when PK passed to Verify method is nil ErrEmptyPublicKey = internal.Error("empty public key") // ErrInvalidSignature when signature passed to Verify method is mismatch ErrInvalidSignature = internal.Error("invalid signature") // ErrCannotUnmarshal when signature ([]byte) passed to Verify method has wrong format // and cannot be parsed. ErrCannotUnmarshal = internal.Error("could not unmarshal signature") // PrivateKeyCompressedSize is constant with compressed size of private key (SK). // D coordinate stored, recover PK by formula x, y = curve.ScalarBaseMul(d,bytes). PrivateKeyCompressedSize = 32 // PublicKeyCompressedSize is constant with compressed size of public key (PK). PublicKeyCompressedSize = 33 // PublicKeyUncompressedSize is constant with uncompressed size of public key (PK). // First byte always should be 0x4 other 64 bytes is X and Y (32 bytes per coordinate). // 2 * 32 + 1. PublicKeyUncompressedSize = 65 ) // 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) } // unmarshalXY converts a point, serialized by Marshal, into an x, y pair. // It is an error if the point is not in uncompressed form. // On error, x,y = nil. // Unlike the original version of the code, we ignore that x or y not on the curve // -------------- // It's copy-paste elliptic.Unmarshal(curve, data) stdlib function, without last line // of code 🤔 // Link - https://golang.org/pkg/crypto/elliptic/#Unmarshal func unmarshalXY(data []byte) (x *big.Int, y *big.Int) { if len(data) != PublicKeyUncompressedSize { return } else if data[0] != 4 { // uncompressed form return } p := curve.Params().P x = new(big.Int).SetBytes(data[1:PublicKeyCompressedSize]) y = new(big.Int).SetBytes(data[PublicKeyCompressedSize:]) if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 { x, y = nil, nil } return } // decompressPoints using formula y^2 = x^3 + ax + b mod p func decompressPoints(x *big.Int, yBit uint) (*big.Int, *big.Int) { params := curve.Params() // x^3 mod P x3 := new(big.Int).Exp(x, new(big.Int).SetInt64(3), params.P) // a * x mod P ax := new(big.Int).Mul(x, new(big.Int).SetInt64(-3)) ax.Mod(ax, params.P) // x^3 + a * x mod P x3.Add(x3, ax) x3.Mod(x3, params.P) // x^3 + a * x + b mod P x3.Add(x3, params.B) x3.Mod(x3, params.P) // y = sqrt(x^3 + ax + b mod p) 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) copy(data[1:], 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 { return nil } return encodePoint(key.X, key.Y) } // UnmarshalPublicKey from bytes func UnmarshalPublicKey(data []byte) *ecdsa.PublicKey { if x, y := decodePoint(data); x != nil && y != nil && curve.IsOnCurve(x, y) { return &ecdsa.PublicKey{ Curve: curve, X: x, Y: y, } } return nil } // UnmarshalPrivateKey method to parse SK from bytes. // It is similar to `ecdsa.Generate()` but uses pre-defined big.Int and curve for NEO Blockchain // Link - https://golang.org/pkg/crypto/ecdsa/#GenerateKey func UnmarshalPrivateKey(data []byte) (*ecdsa.PrivateKey, error) { if len(data) == PrivateKeyCompressedSize { // todo: consider using only NEO blockchain private keys d := new(big.Int).SetBytes(data) priv := new(ecdsa.PrivateKey) priv.PublicKey.Curve = curve priv.D = d priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(data) return priv, nil } return x509.ParseECPrivateKey(data) } // MarshalPrivateKey to bytes func MarshalPrivateKey(key *ecdsa.PrivateKey) []byte { return key.D.Bytes() } // hashBytes returns the sha256 sum. func hashBytes(data []byte) []byte { buf := sha256.Sum256(data) return buf[:] } // Verify verifies the signature in r, s of hash using the public key, pub. Its // return value records whether the signature is valid. func Verify(pub *ecdsa.PublicKey, hash, data []byte) error { if r, s := unmarshalXY(hash); r == nil || s == nil { // panic("could not unmarshal r / s") return ErrCannotUnmarshal } else if pub == nil { return ErrEmptyPublicKey } else if !ecdsa.Verify(pub, hashBytes(data), r, s) { return errors.Wrapf(ErrInvalidSignature, "%0x : %0x", r, s) } return nil } // Sign signs a data using the private key. If the data is longer than // the bit-length of the private key's curve order, the hash will be // truncated to that length. It returns the signature as slice bytes. // The security of the private key depends on the entropy of rand. func Sign(key *ecdsa.PrivateKey, data []byte) ([]byte, error) { x, y, err := ecdsa.Sign(rand.Reader, key, hashBytes(data)) if err != nil { return nil, err } return marshalXY(x, y), nil }