You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
4.8 KiB
164 lines
4.8 KiB
// 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(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 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()) |
|
} |
|
|
|
// 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...) |
|
}
|
|
|