mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-14 03:48:17 +01:00
This change removes the internal pad function in favor a more opimized paddedAppend function. Unlike pad, which would always alloate a new slice of the desired size and copy the bytes into it, paddedAppend only appends the leading padding when necesary, and uses the builtin append to copy the remaining source bytes. pad was also used in combination with another call to the builtin copy func to copy into a zeroed byte slice. As the slice is now created using make with an initial length of zero, this copy can also be removed. As confirmed by poking the bytes with the unsafe package, gc does not zero array elements between the len and cap when allocating slices with make(). In combination with the paddedAppend func, this results in only a single copy of each byte, with no unnecssary zeroing, when creating the serialized pubkeys. This has not been tested with other Go compilers (namely, gccgo and llgo), but the new behavior is still functionally correct regardless of compiler optimizations. The TestPad function has been removed as the pad func it tested has likewise been removed. ok @davecgh
164 lines
4.8 KiB
Go
164 lines
4.8 KiB
Go
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
|
// 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(oga) This will probably only work for secp256k1 due to
|
|
// optimisations.
|
|
|
|
// 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 secp265k1 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())
|
|
}
|
|
|
|
// 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...)
|
|
}
|