c7d523f83c
This commit optimizes the decompressPoint subroutine, used in extracting compressed pubkeys and performing pubkey recovery. We do so by replacing the use of big.Int.Exp with with square-and-multiply exponentiation of btcec's more optimized fieldVals, reducing the overall latency and memory requirements of decompressPoint. Instead of operating on bits of Q = (P+1)/4, the exponentiation applies the square-and-multiply operations on full bytes of Q. Compared to the original speedup. Compared the bit-wise version, the improvement is roughly 10%. A new pair fieldVal methods called Sqrt and SqrtVal are added, which applies the square-and-multiply exponentiation using precomputed byte-slice of the value Q. Comparison against big.Int sqrt and SAM sqrt over bytes of Q: benchmark old ns/op new ns/op delta BenchmarkParseCompressedPubKey-8 35545 23119 -34.96% benchmark old allocs new allocs delta BenchmarkParseCompressedPubKey-8 35 6 -82.86% benchmark old bytes new bytes delta BenchmarkParseCompressedPubKey-8 2777 256 -90.78%
195 lines
5.8 KiB
Go
195 lines
5.8 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 secp256k1 curve given the X point and
|
|
// the solution to use.
|
|
func decompressPoint(curve *KoblitzCurve, bigX *big.Int, ybit bool) (*big.Int, error) {
|
|
var x fieldVal
|
|
x.SetByteSlice(bigX.Bytes())
|
|
|
|
// Compute x^3 + B mod p.
|
|
var x3 fieldVal
|
|
x3.SquareVal(&x).Mul(&x)
|
|
x3.Add(curve.fieldB).Normalize()
|
|
|
|
// Now calculate sqrt mod p of x^3 + 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
|
|
var y fieldVal
|
|
y.SqrtVal(&x3)
|
|
if ybit != y.IsOdd() {
|
|
y.Negate(1)
|
|
}
|
|
y.Normalize()
|
|
|
|
// Check that y is a square root of x^3 + B.
|
|
var y2 fieldVal
|
|
y2.SquareVal(&y).Normalize()
|
|
if !y2.Equals(&x3) {
|
|
return nil, fmt.Errorf("invalid square root")
|
|
}
|
|
|
|
// Verify that y-coord has expected parity.
|
|
if ybit != y.IsOdd() {
|
|
return nil, fmt.Errorf("ybit doesn't match oddness")
|
|
}
|
|
|
|
return new(big.Int).SetBytes(y.Bytes()[:]), 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")
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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...)
|
|
}
|