github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/crypto/koblitz/pubkey.go (about)

     1  // Copyright (c) 2013-2014 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package koblitz
     6  
     7  import (
     8  	"crypto/ecdsa"
     9  	"errors"
    10  	"fmt"
    11  	"math/big"
    12  )
    13  
    14  // These constants define the lengths of serialized public keys.
    15  const (
    16  	PubKeyBytesLenCompressed   = 33
    17  	PubKeyBytesLenUncompressed = 65
    18  	PubKeyBytesLenHybrid       = 65
    19  )
    20  
    21  func isOdd(a *big.Int) bool {
    22  	return a.Bit(0) == 1
    23  }
    24  
    25  // decompressPoint decompresses a point on the given curve given the X point and
    26  // the solution to use.
    27  func decompressPoint(curve *KoblitzCurve, x *big.Int, ybit bool) (*big.Int, error) {
    28  	// TODO(oga) This will probably only work for secp256k1 due to
    29  	// optimizations.
    30  
    31  	// Y = +-sqrt(x^3 + B)
    32  	x3 := new(big.Int).Mul(x, x)
    33  	x3.Mul(x3, x)
    34  	x3.Add(x3, curve.Params().B)
    35  
    36  	// now calculate sqrt mod p of x2 + B
    37  	// This code used to do a full sqrt based on tonelli/shanks,
    38  	// but this was replaced by the algorithms referenced in
    39  	// https://bitcointalk.org/index.php?topic=162805.msg1712294#msg1712294
    40  	y := new(big.Int).Exp(x3, curve.QPlus1Div4(), curve.Params().P)
    41  
    42  	if ybit != isOdd(y) {
    43  		y.Sub(curve.Params().P, y)
    44  	}
    45  	if ybit != isOdd(y) {
    46  		return nil, fmt.Errorf("ybit doesn't match oddness")
    47  	}
    48  	return y, nil
    49  }
    50  
    51  const (
    52  	pubkeyCompressed   byte = 0x2 // y_bit + x coord
    53  	pubkeyUncompressed byte = 0x4 // x coord + y coord
    54  	pubkeyHybrid       byte = 0x6 // y_bit + x coord + y coord
    55  )
    56  
    57  // ParsePubKey parses a public key for a koblitz curve from a bytestring into a
    58  // ecdsa.Publickey, verifying that it is valid. It supports compressed,
    59  // uncompressed and hybrid signature formats.
    60  func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err error) {
    61  	pubkey := PublicKey{}
    62  	pubkey.Curve = curve
    63  
    64  	if len(pubKeyStr) == 0 {
    65  		return nil, errors.New("pubkey string is empty")
    66  	}
    67  
    68  	format := pubKeyStr[0]
    69  	ybit := (format & 0x1) == 0x1
    70  	format &= ^byte(0x1)
    71  
    72  	switch len(pubKeyStr) {
    73  	case PubKeyBytesLenUncompressed:
    74  		if format != pubkeyUncompressed && format != pubkeyHybrid {
    75  			return nil, fmt.Errorf("invalid magic in pubkey str: "+
    76  				"%d", pubKeyStr[0])
    77  		}
    78  
    79  		pubkey.X = new(big.Int).SetBytes(pubKeyStr[1:33])
    80  		pubkey.Y = new(big.Int).SetBytes(pubKeyStr[33:])
    81  		// hybrid keys have extra information, make use of it.
    82  		if format == pubkeyHybrid && ybit != isOdd(pubkey.Y) {
    83  			return nil, fmt.Errorf("ybit doesn't match oddness")
    84  		}
    85  	case PubKeyBytesLenCompressed:
    86  		// format is 0x2 | solution, <X coordinate>
    87  		// solution determines which solution of the curve we use.
    88  		/// y^2 = x^3 + Curve.B
    89  		if format != pubkeyCompressed {
    90  			return nil, fmt.Errorf("invalid magic in compressed "+
    91  				"pubkey string: %d", pubKeyStr[0])
    92  		}
    93  		pubkey.X = new(big.Int).SetBytes(pubKeyStr[1:33])
    94  		pubkey.Y, err = decompressPoint(curve, pubkey.X, ybit)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  	default: // wrong!
    99  		return nil, fmt.Errorf("invalid pub key length %d",
   100  			len(pubKeyStr))
   101  	}
   102  
   103  	if pubkey.X.Cmp(pubkey.Curve.Params().P) >= 0 {
   104  		return nil, fmt.Errorf("pubkey X parameter is >= to P")
   105  	}
   106  	if pubkey.Y.Cmp(pubkey.Curve.Params().P) >= 0 {
   107  		return nil, fmt.Errorf("pubkey Y parameter is >= to P")
   108  	}
   109  	if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) {
   110  		return nil, fmt.Errorf("pubkey isn't on secp256k1 curve")
   111  	}
   112  	return &pubkey, nil
   113  }
   114  
   115  // PublicKey is an ecdsa.PublicKey with additional functions to
   116  // serialize in uncompressed, compressed, and hybrid formats.
   117  type PublicKey ecdsa.PublicKey
   118  
   119  // ToECDSA returns the public key as a *ecdsa.PublicKey.
   120  func (p *PublicKey) ToECDSA() *ecdsa.PublicKey {
   121  	return (*ecdsa.PublicKey)(p)
   122  }
   123  
   124  // SerializeUncompressed serializes a public key in a 65-byte uncompressed
   125  // format.
   126  func (p *PublicKey) SerializeUncompressed() []byte {
   127  	b := make([]byte, 0, PubKeyBytesLenUncompressed)
   128  	b = append(b, pubkeyUncompressed)
   129  	b = paddedAppend(32, b, p.X.Bytes())
   130  	return paddedAppend(32, b, p.Y.Bytes())
   131  }
   132  
   133  // SerializeCompressed serializes a public key in a 33-byte compressed format.
   134  func (p *PublicKey) SerializeCompressed() []byte {
   135  	b := make([]byte, 0, PubKeyBytesLenCompressed)
   136  	format := pubkeyCompressed
   137  	if isOdd(p.Y) {
   138  		format |= 0x1
   139  	}
   140  	b = append(b, format)
   141  	return paddedAppend(32, b, p.X.Bytes())
   142  }
   143  
   144  // SerializeHybrid serializes a public key in a 65-byte hybrid format.
   145  func (p *PublicKey) SerializeHybrid() []byte {
   146  	b := make([]byte, 0, PubKeyBytesLenHybrid)
   147  	format := pubkeyHybrid
   148  	if isOdd(p.Y) {
   149  		format |= 0x1
   150  	}
   151  	b = append(b, format)
   152  	b = paddedAppend(32, b, p.X.Bytes())
   153  	return paddedAppend(32, b, p.Y.Bytes())
   154  }
   155  
   156  // IsEqual compares this PublicKey instance to the one passed, returning true if
   157  // both PublicKeys are equivalent. A PublicKey is equivalent to another, if they
   158  // both have the same X and Y coordinate.
   159  func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool {
   160  	return p.X.Cmp(otherPubKey.X) == 0 &&
   161  		p.Y.Cmp(otherPubKey.Y) == 0
   162  }
   163  
   164  // paddedAppend appends the src byte slice to dst, returning the new slice.
   165  // If the length of the source is smaller than the passed size, leading zero
   166  // bytes are appended to the dst slice before appending src.
   167  func paddedAppend(size uint, dst, src []byte) []byte {
   168  	for i := 0; i < int(size)-len(src); i++ {
   169  		dst = append(dst, 0)
   170  	}
   171  	return append(dst, src...)
   172  }