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.
234 lines
8.4 KiB
234 lines
8.4 KiB
// Copyright (c) 2013-2014 The btcsuite developers |
|
// Copyright (c) 2015-2022 The Decred developers |
|
// Use of this source code is governed by an ISC |
|
// license that can be found in the LICENSE file. |
|
|
|
package secp256k1 |
|
|
|
// References: |
|
// [SEC1] Elliptic Curve Cryptography |
|
// https://www.secg.org/sec1-v2.pdf |
|
// |
|
// [SEC2] Recommended Elliptic Curve Domain Parameters |
|
// https://www.secg.org/sec2-v2.pdf |
|
// |
|
// [ANSI X9.62-1998] Public Key Cryptography For The Financial Services |
|
// Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA) |
|
|
|
import ( |
|
"fmt" |
|
) |
|
|
|
const ( |
|
// PubKeyBytesLenCompressed is the number of bytes of a serialized |
|
// compressed public key. |
|
PubKeyBytesLenCompressed = 33 |
|
// PubKeyBytesLenUncompressed is the number of bytes of a serialized |
|
// uncompressed public key. |
|
PubKeyBytesLenUncompressed = 65 |
|
// PubKeyFormatCompressedEven is the identifier prefix byte for a public key |
|
// whose Y coordinate is even when serialized in the compressed format per |
|
// section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4). |
|
PubKeyFormatCompressedEven byte = 0x02 |
|
// PubKeyFormatCompressedOdd is the identifier prefix byte for a public key |
|
// whose Y coordinate is odd when serialized in the compressed format per |
|
// section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4). |
|
PubKeyFormatCompressedOdd byte = 0x03 |
|
// PubKeyFormatUncompressed is the identifier prefix byte for a public key |
|
// when serialized according in the uncompressed format per section 2.3.3 of |
|
// [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.3). |
|
PubKeyFormatUncompressed byte = 0x04 |
|
// PubKeyFormatHybridEven is the identifier prefix byte for a public key |
|
// whose Y coordinate is even when serialized according to the hybrid format |
|
// per section 4.3.6 of [ANSI X9.62-1998]. |
|
// |
|
// NOTE: This format makes little sense in practice an therefore this |
|
// package will not produce public keys serialized in this format. However, |
|
// it will parse them since they exist in the wild. |
|
PubKeyFormatHybridEven byte = 0x06 |
|
// PubKeyFormatHybridOdd is the identifier prefix byte for a public key |
|
// whose Y coordingate is odd when serialized according to the hybrid format |
|
// per section 4.3.6 of [ANSI X9.62-1998]. |
|
// |
|
// NOTE: This format makes little sense in practice an therefore this |
|
// package will not produce public keys serialized in this format. However, |
|
// it will parse them since they exist in the wild. |
|
PubKeyFormatHybridOdd byte = 0x07 |
|
) |
|
|
|
// PublicKey provides facilities for efficiently working with secp256k1 public |
|
// keys within this package and includes functions to serialize in both |
|
// uncompressed and compressed SEC (Standards for Efficient Cryptography) |
|
// formats. |
|
type PublicKey struct { |
|
x FieldVal |
|
y FieldVal |
|
} |
|
|
|
// NewPublicKey instantiates a new public key with the given x and y |
|
// coordinates. |
|
// |
|
// It should be noted that, unlike ParsePubKey, since this accepts arbitrary x |
|
// and y coordinates, it allows creation of public keys that are not valid |
|
// points on the secp256k1 curve. The IsOnCurve method of the returned instance |
|
// can be used to determine validity. |
|
func NewPublicKey(x, y *FieldVal) *PublicKey { |
|
var pubKey PublicKey |
|
pubKey.x.Set(x) |
|
pubKey.y.Set(y) |
|
return &pubKey |
|
} |
|
|
|
// ParsePubKey parses a secp256k1 public key encoded according to the format |
|
// specified by ANSI X9.62-1998, which means it is also compatible with the |
|
// SEC (Standards for Efficient Cryptography) specification which is a subset of |
|
// the former. In other words, it supports the uncompressed, compressed, and |
|
// hybrid formats as follows: |
|
// |
|
// Compressed: |
|
// |
|
// <format byte = 0x02/0x03><32-byte X coordinate> |
|
// |
|
// Uncompressed: |
|
// |
|
// <format byte = 0x04><32-byte X coordinate><32-byte Y coordinate> |
|
// |
|
// Hybrid: |
|
// |
|
// <format byte = 0x05/0x06><32-byte X coordinate><32-byte Y coordinate> |
|
// |
|
// NOTE: The hybrid format makes little sense in practice an therefore this |
|
// package will not produce public keys serialized in this format. However, |
|
// this function will properly parse them since they exist in the wild. |
|
func ParsePubKey(serialized []byte) (key *PublicKey, err error) { |
|
var x, y FieldVal |
|
switch len(serialized) { |
|
case PubKeyBytesLenUncompressed: |
|
// Reject unsupported public key formats for the given length. |
|
format := serialized[0] |
|
switch format { |
|
case PubKeyFormatUncompressed: |
|
case PubKeyFormatHybridEven, PubKeyFormatHybridOdd: |
|
default: |
|
str := fmt.Sprintf( |
|
"invalid public key: unsupported format: %x", |
|
format, |
|
) |
|
return nil, makeError(ErrPubKeyInvalidFormat, str) |
|
} |
|
// Parse the x and y coordinates while ensuring that they are in the |
|
// allowed range. |
|
if overflow := x.SetByteSlice(serialized[1:33]); overflow { |
|
str := "invalid public key: x >= field prime" |
|
return nil, makeError(ErrPubKeyXTooBig, str) |
|
} |
|
if overflow := y.SetByteSlice(serialized[33:]); overflow { |
|
str := "invalid public key: y >= field prime" |
|
return nil, makeError(ErrPubKeyYTooBig, str) |
|
} |
|
// Ensure the oddness of the y coordinate matches the specified format |
|
// for hybrid public keys. |
|
if format == PubKeyFormatHybridEven || format == PubKeyFormatHybridOdd { |
|
wantOddY := format == PubKeyFormatHybridOdd |
|
if y.IsOdd() != wantOddY { |
|
str := fmt.Sprintf( |
|
"invalid public key: y oddness does not "+ |
|
"match specified value of %v", wantOddY, |
|
) |
|
return nil, makeError(ErrPubKeyMismatchedOddness, str) |
|
} |
|
} |
|
// Reject public keys that are not on the secp256k1 curve. |
|
if !isOnCurve(&x, &y) { |
|
str := fmt.Sprintf( |
|
"invalid public key: [%v,%v] not on secp256k1 "+ |
|
"curve", x, y, |
|
) |
|
return nil, makeError(ErrPubKeyNotOnCurve, str) |
|
} |
|
case PubKeyBytesLenCompressed: |
|
// Reject unsupported public key formats for the given length. |
|
format := serialized[0] |
|
switch format { |
|
case PubKeyFormatCompressedEven, PubKeyFormatCompressedOdd: |
|
default: |
|
str := fmt.Sprintf( |
|
"invalid public key: unsupported format: %x", |
|
format, |
|
) |
|
return nil, makeError(ErrPubKeyInvalidFormat, str) |
|
} |
|
// Parse the x coordinate while ensuring that it is in the allowed |
|
// range. |
|
if overflow := x.SetByteSlice(serialized[1:33]); overflow { |
|
str := "invalid public key: x >= field prime" |
|
return nil, makeError(ErrPubKeyXTooBig, str) |
|
} |
|
// Attempt to calculate the y coordinate for the given x coordinate such |
|
// that the result pair is a point on the secp256k1 curve and the |
|
// solution with desired oddness is chosen. |
|
wantOddY := format == PubKeyFormatCompressedOdd |
|
if !DecompressY(&x, wantOddY, &y) { |
|
str := fmt.Sprintf( |
|
"invalid public key: x coordinate %v is not on "+ |
|
"the secp256k1 curve", x, |
|
) |
|
return nil, makeError(ErrPubKeyNotOnCurve, str) |
|
} |
|
y.Normalize() |
|
default: |
|
str := fmt.Sprintf( |
|
"malformed public key: invalid length: %d", |
|
len(serialized), |
|
) |
|
return nil, makeError(ErrPubKeyInvalidLen, str) |
|
} |
|
return NewPublicKey(&x, &y), nil |
|
} |
|
|
|
// SerializeUncompressed serializes a public key in the 65-byte uncompressed |
|
// format. |
|
func (p PublicKey) SerializeUncompressed() []byte { |
|
// 0x04 || 32-byte x coordinate || 32-byte y coordinate |
|
var b [PubKeyBytesLenUncompressed]byte |
|
b[0] = PubKeyFormatUncompressed |
|
p.x.PutBytesUnchecked(b[1:33]) |
|
p.y.PutBytesUnchecked(b[33:65]) |
|
return b[:] |
|
} |
|
|
|
// SerializeCompressed serializes a public key in the 33-byte compressed format. |
|
func (p PublicKey) SerializeCompressed() []byte { |
|
// Choose the format byte depending on the oddness of the Y coordinate. |
|
format := PubKeyFormatCompressedEven |
|
if p.y.IsOdd() { |
|
format = PubKeyFormatCompressedOdd |
|
} |
|
// 0x02 or 0x03 || 32-byte x coordinate |
|
var b [PubKeyBytesLenCompressed]byte |
|
b[0] = format |
|
p.x.PutBytesUnchecked(b[1:33]) |
|
return b[:] |
|
} |
|
|
|
// IsEqual compares this public key instance to the one passed, returning true |
|
// if both public keys are equivalent. A public key is equivalent to another, |
|
// if they both have the same X and Y coordinates. |
|
func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool { |
|
return p.x.Equals(&otherPubKey.x) && p.y.Equals(&otherPubKey.y) |
|
} |
|
|
|
// AsJacobian converts the public key into a Jacobian point with Z=1 and stores |
|
// the result in the provided result param. This allows the public key to be |
|
// treated a Jacobian point in the secp256k1 group in calculations. |
|
func (p *PublicKey) AsJacobian(result *JacobianPoint) { |
|
result.X.Set(&p.x) |
|
result.Y.Set(&p.y) |
|
result.Z.SetInt(1) |
|
} |
|
|
|
// IsOnCurve returns whether or not the public key represents a point on the |
|
// secp256k1 curve. |
|
func (p *PublicKey) IsOnCurve() bool { |
|
return isOnCurve(&p.x, &p.y) |
|
}
|
|
|