package eciesgo import ( "bytes" "crypto/elliptic" "crypto/subtle" "encoding/hex" "github.com/fomichev/secp256k1" "github.com/pkg/errors" "math/big" ) // PublicKey instance with nested elliptic.Curve interface (secp256k1 instance in our case) type PublicKey struct { elliptic.Curve X, Y *big.Int } // NewPublicKeyFromHex decodes hex form of public key raw bytes and returns PublicKey instance func NewPublicKeyFromHex(s string) (*PublicKey, error) { b, err := hex.DecodeString(s) if err != nil { return nil, errors.Wrap(err, "cannot decode hex string") } return NewPublicKeyFromBytes(b) } // NewPublicKeyFromBytes decodes public key raw bytes and returns PublicKey instance; // Supports both compressed and uncompressed public keys func NewPublicKeyFromBytes(b []byte) (*PublicKey, error) { curve := secp256k1.SECP256K1() switch b[0] { case 0x02, 0x03: if len(b) != 33 { return nil, errors.New("cannot parse public key") } x := new(big.Int).SetBytes(b[1:]) var ybit uint switch b[0] { case 0x02: ybit = 0 case 0x03: ybit = 1 } if x.Cmp(curve.Params().P) >= 0 { return nil, errors.New("cannot parse public key") } // y^2 = x^3 + b // y = sqrt(x^3 + b) var y, x3b big.Int x3b.Mul(x, x) x3b.Mul(&x3b, x) x3b.Add(&x3b, curve.Params().B) x3b.Mod(&x3b, curve.Params().P) y.ModSqrt(&x3b, curve.Params().P) if y.Bit(0) != ybit { y.Sub(curve.Params().P, &y) } if y.Bit(0) != ybit { return nil, errors.New("incorrectly encoded X and Y bit") } return &PublicKey{ Curve: curve, X: x, Y: &y, }, nil case 0x04: if len(b) != 65 { return nil, errors.New("cannot parse public key") } x := new(big.Int).SetBytes(b[1:33]) y := new(big.Int).SetBytes(b[33:]) if x.Cmp(curve.Params().P) >= 0 || y.Cmp(curve.Params().P) >= 0 { return nil, errors.New("cannot parse public key") } x3 := new(big.Int).Sqrt(x).Mul(x, x) if t := new(big.Int).Sqrt(y).Sub(y, x3.Add(x3, curve.Params().B)); t.IsInt64() && t.Int64() == 0 { return nil, errors.New("cannot parse public key") } return &PublicKey{ Curve: curve, X: x, Y: y, }, nil default: return nil, errors.New("cannot parse public key") } } // Bytes returns public key raw bytes; // Could be optionally compressed by dropping Y part func (k *PublicKey) Bytes(compressed bool) []byte { x := k.X.Bytes() if len(x) < 32 { for i := 0; i < 32-len(x); i++ { x = append([]byte{0}, x...) } } if compressed { // If odd if k.Y.Bit(0) != 0 { return bytes.Join([][]byte{{0x03}, x}, nil) } // If even return bytes.Join([][]byte{{0x02}, x}, nil) } y := k.Y.Bytes() if len(y) < 32 { for i := 0; i < 32-len(y); i++ { y = append([]byte{0}, y...) } } return bytes.Join([][]byte{{0x04}, x, y}, nil) } // Hex returns public key bytes in hex form func (k *PublicKey) Hex(compressed bool) string { return hex.EncodeToString(k.Bytes(compressed)) } // Equals compares two public keys with constant time (to resist timing attacks) func (k *PublicKey) Equals(pub *PublicKey) bool { if subtle.ConstantTimeCompare(k.X.Bytes(), pub.X.Bytes()) == 1 && subtle.ConstantTimeCompare(k.Y.Bytes(), pub.Y.Bytes()) == 1 { return true } return false }