// Copyright (c) 2013-2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package btcec import ( "crypto/ecdsa" "errors" "fmt" "math/big" ) // These constants define the lengths of serialized public keys. const ( PubKeyBytesLenCompressed = 33 PubKeyBytesLenUncompressed = 65 PubKeyBytesLenHybrid = 65 ) func isOdd(a *big.Int) bool { return a.Bit(0) == 1 } // decompressPoint decompresses a point on the given curve given the X point and // the solution to use. func decompressPoint(curve *KoblitzCurve, x *big.Int, ybit bool) (*big.Int, error) { // TODO: This will probably only work for secp256k1 due to // optimizations. // Y = +-sqrt(x^3 + B) x3 := new(big.Int).Mul(x, x) x3.Mul(x3, x) x3.Add(x3, curve.Params().B) // now calculate sqrt mod p of x2 + B // This code used to do a full sqrt based on tonelli/shanks, // but this was replaced by the algorithms referenced in // https://bitcointalk.org/index.php?topic=162805.msg1712294#msg1712294 y := new(big.Int).Exp(x3, curve.QPlus1Div4(), curve.Params().P) if ybit != isOdd(y) { y.Sub(curve.Params().P, y) } if ybit != isOdd(y) { return nil, fmt.Errorf("ybit doesn't match oddness") } return y, nil } const ( pubkeyCompressed byte = 0x2 // y_bit + x coord pubkeyUncompressed byte = 0x4 // x coord + y coord pubkeyHybrid byte = 0x6 // y_bit + x coord + y coord ) // IsCompressedPubKey returns true the the passed serialized public key has // been encoded in compressed format, and false otherwise. func IsCompressedPubKey(pubKey []byte) bool { // The public key is only compressed if it is the correct length and // the format (first byte) is one of the compressed pubkey values. return len(pubKey) == PubKeyBytesLenCompressed && (pubKey[0]&^byte(0x1) == pubkeyCompressed) } // ParsePubKey parses a public key for a koblitz curve from a bytestring into a // ecdsa.Publickey, verifying that it is valid. It supports compressed, // uncompressed and hybrid signature formats. func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err error) { pubkey := PublicKey{} pubkey.Curve = curve if len(pubKeyStr) == 0 { return nil, errors.New("pubkey string is empty") } format := pubKeyStr[0] ybit := (format & 0x1) == 0x1 format &= ^byte(0x1) switch len(pubKeyStr) { case PubKeyBytesLenUncompressed: if format != pubkeyUncompressed && format != pubkeyHybrid { return nil, fmt.Errorf("invalid magic in pubkey str: "+ "%d", pubKeyStr[0]) } pubkey.X = new(big.Int).SetBytes(pubKeyStr[1:33]) pubkey.Y = new(big.Int).SetBytes(pubKeyStr[33:]) // hybrid keys have extra information, make use of it. if format == pubkeyHybrid && ybit != isOdd(pubkey.Y) { return nil, fmt.Errorf("ybit doesn't match oddness") } case PubKeyBytesLenCompressed: // format is 0x2 | solution, // solution determines which solution of the curve we use. /// y^2 = x^3 + Curve.B if format != pubkeyCompressed { return nil, fmt.Errorf("invalid magic in compressed "+ "pubkey string: %d", pubKeyStr[0]) } pubkey.X = new(big.Int).SetBytes(pubKeyStr[1:33]) pubkey.Y, err = decompressPoint(curve, pubkey.X, ybit) if err != nil { return nil, err } default: // wrong! return nil, fmt.Errorf("invalid pub key length %d", len(pubKeyStr)) } if pubkey.X.Cmp(pubkey.Curve.Params().P) >= 0 { return nil, fmt.Errorf("pubkey X parameter is >= to P") } if pubkey.Y.Cmp(pubkey.Curve.Params().P) >= 0 { return nil, fmt.Errorf("pubkey Y parameter is >= to P") } if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { return nil, fmt.Errorf("pubkey isn't on secp256k1 curve") } return &pubkey, nil } // PublicKey is an ecdsa.PublicKey with additional functions to // serialize in uncompressed, compressed, and hybrid formats. type PublicKey ecdsa.PublicKey // ToECDSA returns the public key as a *ecdsa.PublicKey. func (p *PublicKey) ToECDSA() *ecdsa.PublicKey { return (*ecdsa.PublicKey)(p) } // SerializeUncompressed serializes a public key in a 65-byte uncompressed // format. func (p *PublicKey) SerializeUncompressed() []byte { b := make([]byte, 0, PubKeyBytesLenUncompressed) b = append(b, pubkeyUncompressed) b = paddedAppend(32, b, p.X.Bytes()) return paddedAppend(32, b, p.Y.Bytes()) } // SerializeCompressed serializes a public key in a 33-byte compressed format. func (p *PublicKey) SerializeCompressed() []byte { b := make([]byte, 0, PubKeyBytesLenCompressed) format := pubkeyCompressed if isOdd(p.Y) { format |= 0x1 } b = append(b, format) return paddedAppend(32, b, p.X.Bytes()) } // SerializeHybrid serializes a public key in a 65-byte hybrid format. func (p *PublicKey) SerializeHybrid() []byte { b := make([]byte, 0, PubKeyBytesLenHybrid) format := pubkeyHybrid if isOdd(p.Y) { format |= 0x1 } b = append(b, format) b = paddedAppend(32, b, p.X.Bytes()) return paddedAppend(32, b, p.Y.Bytes()) } // IsEqual compares this PublicKey instance to the one passed, returning true if // both PublicKeys are equivalent. A PublicKey is equivalent to another, if they // both have the same X and Y coordinate. func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool { return p.X.Cmp(otherPubKey.X) == 0 && p.Y.Cmp(otherPubKey.Y) == 0 } // paddedAppend appends the src byte slice to dst, returning the new slice. // If the length of the source is smaller than the passed size, leading zero // bytes are appended to the dst slice before appending src. func paddedAppend(size uint, dst, src []byte) []byte { for i := 0; i < int(size)-len(src); i++ { dst = append(dst, 0) } return append(dst, src...) }