e85b68ef53
We need those operations for p2p/enr. Also upgrade github.com/btcsuite/btcd/btcec to the latest version and improve BenchmarkSha3. The benchmark printed extra output that confused tools like benchstat and ignored N.
182 lines
5.6 KiB
Go
182 lines
5.6 KiB
Go
// 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, <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...)
|
|
}
|