pubkey.go raw

   1  // Copyright (c) 2013-2014 The btcsuite developers
   2  // Copyright (c) 2015-2024 The Decred developers
   3  // Use of this source code is governed by an ISC
   4  // license that can be found in the LICENSE file.
   5  
   6  package secp256k1
   7  
   8  // References:
   9  //   [SEC1] Elliptic Curve Cryptography
  10  //     https://www.secg.org/sec1-v2.pdf
  11  //
  12  //   [SEC2] Recommended Elliptic Curve Domain Parameters
  13  //     https://www.secg.org/sec2-v2.pdf
  14  //
  15  //   [ANSI X9.62-1998] Public Key Cryptography For The Financial Services
  16  //     Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)
  17  
  18  import (
  19  	"fmt"
  20  )
  21  
  22  const (
  23  	// PubKeyBytesLenCompressed is the number of bytes of a serialized
  24  	// compressed public key.
  25  	PubKeyBytesLenCompressed = 33
  26  
  27  	// PubKeyBytesLenUncompressed is the number of bytes of a serialized
  28  	// uncompressed public key.
  29  	PubKeyBytesLenUncompressed = 65
  30  
  31  	// PubKeyFormatCompressedEven is the identifier prefix byte for a public key
  32  	// whose Y coordinate is even when serialized in the compressed format per
  33  	// section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4).
  34  	PubKeyFormatCompressedEven byte = 0x02
  35  
  36  	// PubKeyFormatCompressedOdd is the identifier prefix byte for a public key
  37  	// whose Y coordinate is odd when serialized in the compressed format per
  38  	// section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4).
  39  	PubKeyFormatCompressedOdd byte = 0x03
  40  
  41  	// PubKeyFormatUncompressed is the identifier prefix byte for a public key
  42  	// when serialized according in the uncompressed format per section 2.3.3 of
  43  	// [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.3).
  44  	PubKeyFormatUncompressed byte = 0x04
  45  
  46  	// PubKeyFormatHybridEven is the identifier prefix byte for a public key
  47  	// whose Y coordinate is even when serialized according to the hybrid format
  48  	// per section 4.3.6 of [ANSI X9.62-1998].
  49  	//
  50  	// NOTE: This format makes little sense in practice an therefore this
  51  	// package will not produce public keys serialized in this format.  However,
  52  	// it will parse them since they exist in the wild.
  53  	PubKeyFormatHybridEven byte = 0x06
  54  
  55  	// PubKeyFormatHybridOdd is the identifier prefix byte for a public key
  56  	// whose Y coordingate is odd when serialized according to the hybrid format
  57  	// per section 4.3.6 of [ANSI X9.62-1998].
  58  	//
  59  	// NOTE: This format makes little sense in practice an therefore this
  60  	// package will not produce public keys serialized in this format.  However,
  61  	// it will parse them since they exist in the wild.
  62  	PubKeyFormatHybridOdd byte = 0x07
  63  )
  64  
  65  // PublicKey provides facilities for efficiently working with secp256k1 public
  66  // keys within this package and includes functions to serialize in both
  67  // uncompressed and compressed SEC (Standards for Efficient Cryptography)
  68  // formats.
  69  type PublicKey struct {
  70  	x FieldVal
  71  	y FieldVal
  72  }
  73  
  74  // NewPublicKey instantiates a new public key with the given x and y
  75  // coordinates.
  76  //
  77  // It should be noted that, unlike ParsePubKey, since this accepts arbitrary x
  78  // and y coordinates, it allows creation of public keys that are not valid
  79  // points on the secp256k1 curve.  The IsOnCurve method of the returned instance
  80  // can be used to determine validity.
  81  func NewPublicKey(x, y *FieldVal) *PublicKey {
  82  	var pubKey PublicKey
  83  	pubKey.x.Set(x)
  84  	pubKey.y.Set(y)
  85  	return &pubKey
  86  }
  87  
  88  // ParsePubKey parses a secp256k1 public key encoded according to the format
  89  // specified by ANSI X9.62-1998, which means it is also compatible with the
  90  // SEC (Standards for Efficient Cryptography) specification which is a subset of
  91  // the former.  In other words, it supports the uncompressed, compressed, and
  92  // hybrid formats as follows:
  93  //
  94  // Compressed:
  95  //
  96  //	<format byte = 0x02/0x03><32-byte X coordinate>
  97  //
  98  // Uncompressed:
  99  //
 100  //	<format byte = 0x04><32-byte X coordinate><32-byte Y coordinate>
 101  //
 102  // Hybrid:
 103  //
 104  //	<format byte = 0x05/0x06><32-byte X coordinate><32-byte Y coordinate>
 105  //
 106  // NOTE: The hybrid format makes little sense in practice an therefore this
 107  // package will not produce public keys serialized in this format.  However,
 108  // this function will properly parse them since they exist in the wild.
 109  func ParsePubKey(serialized []byte) (key *PublicKey, err error) {
 110  	var x, y FieldVal
 111  	switch len(serialized) {
 112  	case PubKeyBytesLenUncompressed:
 113  		// Reject unsupported public key formats for the given length.
 114  		format := serialized[0]
 115  		switch format {
 116  		case PubKeyFormatUncompressed:
 117  		case PubKeyFormatHybridEven, PubKeyFormatHybridOdd:
 118  		default:
 119  			str := fmt.Sprintf("invalid public key: unsupported format: %x",
 120  				format)
 121  			return nil, makeError(ErrPubKeyInvalidFormat, str)
 122  		}
 123  
 124  		// Parse the x and y coordinates while ensuring that they are in the
 125  		// allowed range.
 126  		if overflow := x.SetByteSlice(serialized[1:33]); overflow {
 127  			str := "invalid public key: x >= field prime"
 128  			return nil, makeError(ErrPubKeyXTooBig, str)
 129  		}
 130  		if overflow := y.SetByteSlice(serialized[33:]); overflow {
 131  			str := "invalid public key: y >= field prime"
 132  			return nil, makeError(ErrPubKeyYTooBig, str)
 133  		}
 134  
 135  		// Ensure the oddness of the y coordinate matches the specified format
 136  		// for hybrid public keys.
 137  		if format == PubKeyFormatHybridEven || format == PubKeyFormatHybridOdd {
 138  			wantOddY := format == PubKeyFormatHybridOdd
 139  			if y.IsOdd() != wantOddY {
 140  				str := fmt.Sprintf("invalid public key: y oddness does not "+
 141  					"match specified value of %v", wantOddY)
 142  				return nil, makeError(ErrPubKeyMismatchedOddness, str)
 143  			}
 144  		}
 145  
 146  		// Reject public keys that are not on the secp256k1 curve.
 147  		if !isOnCurve(&x, &y) {
 148  			str := fmt.Sprintf("invalid public key: [%v,%v] not on secp256k1 "+
 149  				"curve", x, y)
 150  			return nil, makeError(ErrPubKeyNotOnCurve, str)
 151  		}
 152  
 153  	case PubKeyBytesLenCompressed:
 154  		// Reject unsupported public key formats for the given length.
 155  		format := serialized[0]
 156  		switch format {
 157  		case PubKeyFormatCompressedEven, PubKeyFormatCompressedOdd:
 158  		default:
 159  			str := fmt.Sprintf("invalid public key: unsupported format: %x",
 160  				format)
 161  			return nil, makeError(ErrPubKeyInvalidFormat, str)
 162  		}
 163  
 164  		// Parse the x coordinate while ensuring that it is in the allowed
 165  		// range.
 166  		if overflow := x.SetByteSlice(serialized[1:33]); overflow {
 167  			str := "invalid public key: x >= field prime"
 168  			return nil, makeError(ErrPubKeyXTooBig, str)
 169  		}
 170  
 171  		// Attempt to calculate the y coordinate for the given x coordinate such
 172  		// that the result pair is a point on the secp256k1 curve and the
 173  		// solution with desired oddness is chosen.
 174  		wantOddY := format == PubKeyFormatCompressedOdd
 175  		if !DecompressY(&x, wantOddY, &y) {
 176  			str := fmt.Sprintf("invalid public key: x coordinate %v is not on "+
 177  				"the secp256k1 curve", x)
 178  			return nil, makeError(ErrPubKeyNotOnCurve, str)
 179  		}
 180  
 181  	default:
 182  		str := fmt.Sprintf("malformed public key: invalid length: %d",
 183  			len(serialized))
 184  		return nil, makeError(ErrPubKeyInvalidLen, str)
 185  	}
 186  
 187  	return NewPublicKey(&x, &y), nil
 188  }
 189  
 190  // SerializeUncompressed serializes a public key in the 65-byte uncompressed
 191  // format.
 192  func (p PublicKey) SerializeUncompressed() []byte {
 193  	// 0x04 || 32-byte x coordinate || 32-byte y coordinate
 194  	var b [PubKeyBytesLenUncompressed]byte
 195  	b[0] = PubKeyFormatUncompressed
 196  	p.x.PutBytesUnchecked(b[1:33])
 197  	p.y.PutBytesUnchecked(b[33:65])
 198  	return b[:]
 199  }
 200  
 201  // SerializeCompressed serializes a public key in the 33-byte compressed format.
 202  func (p PublicKey) SerializeCompressed() []byte {
 203  	// Choose the format byte depending on the oddness of the Y coordinate.
 204  	format := PubKeyFormatCompressedEven
 205  	if p.y.IsOdd() {
 206  		format = PubKeyFormatCompressedOdd
 207  	}
 208  
 209  	// 0x02 or 0x03 || 32-byte x coordinate
 210  	var b [PubKeyBytesLenCompressed]byte
 211  	b[0] = format
 212  	p.x.PutBytesUnchecked(b[1:33])
 213  	return b[:]
 214  }
 215  
 216  // IsEqual compares this public key instance to the one passed, returning true
 217  // if both public keys are equivalent.  A public key is equivalent to another,
 218  // if they both have the same X and Y coordinates.
 219  func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool {
 220  	return p.x.Equals(&otherPubKey.x) && p.y.Equals(&otherPubKey.y)
 221  }
 222  
 223  // AsJacobian converts the public key into a Jacobian point with Z=1 and stores
 224  // the result in the provided result param.  This allows the public key to be
 225  // treated a Jacobian point in the secp256k1 group in calculations.
 226  func (p *PublicKey) AsJacobian(result *JacobianPoint) {
 227  	result.X.Set(&p.x)
 228  	result.Y.Set(&p.y)
 229  	result.Z.SetInt(1)
 230  }
 231  
 232  // IsOnCurve returns whether or not the public key represents a point on the
 233  // secp256k1 curve.
 234  func (p *PublicKey) IsOnCurve() bool {
 235  	return isOnCurve(&p.x, &p.y)
 236  }
 237