aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/btcsuite/btcd/btcec/pubkey.go
blob: f145414eccb81c2860e74ed2b1b4e52d956aa2c8 (plain) (tree)











































































































































































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

// 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, <X coordinate>
        // 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...)
}