github.com/MetalBlockchain/metalgo@v1.11.9/utils/crypto/secp256k1/secp256k1.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package secp256k1
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    12  
    13  	"github.com/MetalBlockchain/metalgo/cache"
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/utils/cb58"
    16  	"github.com/MetalBlockchain/metalgo/utils/hashing"
    17  
    18  	stdecdsa "crypto/ecdsa"
    19  	secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
    20  )
    21  
    22  const (
    23  	// SignatureLen is the number of bytes in a secp2561k recoverable signature
    24  	SignatureLen = 65
    25  
    26  	// PrivateKeyLen is the number of bytes in a secp2561k recoverable private
    27  	// key
    28  	PrivateKeyLen = 32
    29  
    30  	// PublicKeyLen is the number of bytes in a secp2561k recoverable public key
    31  	PublicKeyLen = 33
    32  
    33  	// from the decred library:
    34  	// compactSigMagicOffset is a value used when creating the compact signature
    35  	// recovery code inherited from Bitcoin and has no meaning, but has been
    36  	// retained for compatibility.  For historical purposes, it was originally
    37  	// picked to avoid a binary representation that would allow compact
    38  	// signatures to be mistaken for other components.
    39  	compactSigMagicOffset = 27
    40  
    41  	PrivateKeyPrefix = "PrivateKey-"
    42  	nullStr          = "null"
    43  )
    44  
    45  var (
    46  	ErrInvalidSig              = errors.New("invalid signature")
    47  	errCompressed              = errors.New("wasn't expecting a compressed key")
    48  	errMissingQuotes           = errors.New("first and last characters should be quotes")
    49  	errMissingKeyPrefix        = fmt.Errorf("private key missing %s prefix", PrivateKeyPrefix)
    50  	errInvalidPrivateKeyLength = fmt.Errorf("private key has unexpected length, expected %d", PrivateKeyLen)
    51  	errInvalidPublicKeyLength  = fmt.Errorf("public key has unexpected length, expected %d", PublicKeyLen)
    52  	errInvalidSigLen           = errors.New("invalid signature length")
    53  	errMutatedSig              = errors.New("signature was mutated from its original format")
    54  )
    55  
    56  func NewPrivateKey() (*PrivateKey, error) {
    57  	k, err := secp256k1.GeneratePrivateKey()
    58  	return &PrivateKey{sk: k}, err
    59  }
    60  
    61  func ToPublicKey(b []byte) (*PublicKey, error) {
    62  	if len(b) != PublicKeyLen {
    63  		return nil, errInvalidPublicKeyLength
    64  	}
    65  
    66  	key, err := secp256k1.ParsePubKey(b)
    67  	return &PublicKey{
    68  		pk:    key,
    69  		bytes: b,
    70  	}, err
    71  }
    72  
    73  func ToPrivateKey(b []byte) (*PrivateKey, error) {
    74  	if len(b) != PrivateKeyLen {
    75  		return nil, errInvalidPrivateKeyLength
    76  	}
    77  	return &PrivateKey{
    78  		sk:    secp256k1.PrivKeyFromBytes(b),
    79  		bytes: b,
    80  	}, nil
    81  }
    82  
    83  func RecoverPublicKey(msg, sig []byte) (*PublicKey, error) {
    84  	return RecoverPublicKeyFromHash(hashing.ComputeHash256(msg), sig)
    85  }
    86  
    87  func RecoverPublicKeyFromHash(hash, sig []byte) (*PublicKey, error) {
    88  	if err := verifySECP256K1RSignatureFormat(sig); err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	sig, err := sigToRawSig(sig)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	rawPubkey, compressed, err := ecdsa.RecoverCompact(sig, hash)
    98  	if err != nil {
    99  		return nil, ErrInvalidSig
   100  	}
   101  
   102  	if compressed {
   103  		return nil, errCompressed
   104  	}
   105  
   106  	return &PublicKey{pk: rawPubkey}, nil
   107  }
   108  
   109  type RecoverCache struct {
   110  	cache.LRU[ids.ID, *PublicKey]
   111  }
   112  
   113  func (r *RecoverCache) RecoverPublicKey(msg, sig []byte) (*PublicKey, error) {
   114  	return r.RecoverPublicKeyFromHash(hashing.ComputeHash256(msg), sig)
   115  }
   116  
   117  func (r *RecoverCache) RecoverPublicKeyFromHash(hash, sig []byte) (*PublicKey, error) {
   118  	cacheBytes := make([]byte, len(hash)+len(sig))
   119  	copy(cacheBytes, hash)
   120  	copy(cacheBytes[len(hash):], sig)
   121  	id := hashing.ComputeHash256Array(cacheBytes)
   122  	if cachedPublicKey, ok := r.Get(id); ok {
   123  		return cachedPublicKey, nil
   124  	}
   125  
   126  	pubKey, err := RecoverPublicKeyFromHash(hash, sig)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	r.Put(id, pubKey)
   132  	return pubKey, nil
   133  }
   134  
   135  type PublicKey struct {
   136  	pk    *secp256k1.PublicKey
   137  	addr  ids.ShortID
   138  	bytes []byte
   139  }
   140  
   141  func (k *PublicKey) Verify(msg, sig []byte) bool {
   142  	return k.VerifyHash(hashing.ComputeHash256(msg), sig)
   143  }
   144  
   145  func (k *PublicKey) VerifyHash(hash, sig []byte) bool {
   146  	pk, err := RecoverPublicKeyFromHash(hash, sig)
   147  	if err != nil {
   148  		return false
   149  	}
   150  	return k.Address() == pk.Address()
   151  }
   152  
   153  // ToECDSA returns the ecdsa representation of this public key
   154  func (k *PublicKey) ToECDSA() *stdecdsa.PublicKey {
   155  	return k.pk.ToECDSA()
   156  }
   157  
   158  func (k *PublicKey) Address() ids.ShortID {
   159  	if k.addr == ids.ShortEmpty {
   160  		addr, err := ids.ToShortID(hashing.PubkeyBytesToAddress(k.Bytes()))
   161  		if err != nil {
   162  			panic(err)
   163  		}
   164  		k.addr = addr
   165  	}
   166  	return k.addr
   167  }
   168  
   169  func (k *PublicKey) Bytes() []byte {
   170  	if k.bytes == nil {
   171  		k.bytes = k.pk.SerializeCompressed()
   172  	}
   173  	return k.bytes
   174  }
   175  
   176  type PrivateKey struct {
   177  	sk    *secp256k1.PrivateKey
   178  	pk    *PublicKey
   179  	bytes []byte
   180  }
   181  
   182  func (k *PrivateKey) PublicKey() *PublicKey {
   183  	if k.pk == nil {
   184  		k.pk = &PublicKey{pk: k.sk.PubKey()}
   185  	}
   186  	return k.pk
   187  }
   188  
   189  func (k *PrivateKey) Address() ids.ShortID {
   190  	return k.PublicKey().Address()
   191  }
   192  
   193  func (k *PrivateKey) Sign(msg []byte) ([]byte, error) {
   194  	return k.SignHash(hashing.ComputeHash256(msg))
   195  }
   196  
   197  func (k *PrivateKey) SignHash(hash []byte) ([]byte, error) {
   198  	sig := ecdsa.SignCompact(k.sk, hash, false) // returns [v || r || s]
   199  	return rawSigToSig(sig)
   200  }
   201  
   202  // ToECDSA returns the ecdsa representation of this private key
   203  func (k *PrivateKey) ToECDSA() *stdecdsa.PrivateKey {
   204  	return k.sk.ToECDSA()
   205  }
   206  
   207  func (k *PrivateKey) Bytes() []byte {
   208  	if k.bytes == nil {
   209  		k.bytes = k.sk.Serialize()
   210  	}
   211  	return k.bytes
   212  }
   213  
   214  func (k *PrivateKey) String() string {
   215  	// We assume that the maximum size of a byte slice that
   216  	// can be stringified is at least the length of a SECP256K1 private key
   217  	keyStr, _ := cb58.Encode(k.Bytes())
   218  	return PrivateKeyPrefix + keyStr
   219  }
   220  
   221  func (k *PrivateKey) MarshalJSON() ([]byte, error) {
   222  	return []byte(`"` + k.String() + `"`), nil
   223  }
   224  
   225  func (k *PrivateKey) MarshalText() ([]byte, error) {
   226  	return []byte(k.String()), nil
   227  }
   228  
   229  func (k *PrivateKey) UnmarshalJSON(b []byte) error {
   230  	str := string(b)
   231  	if str == nullStr { // If "null", do nothing
   232  		return nil
   233  	} else if len(str) < 2 {
   234  		return errMissingQuotes
   235  	}
   236  
   237  	lastIndex := len(str) - 1
   238  	if str[0] != '"' || str[lastIndex] != '"' {
   239  		return errMissingQuotes
   240  	}
   241  
   242  	strNoQuotes := str[1:lastIndex]
   243  	if !strings.HasPrefix(strNoQuotes, PrivateKeyPrefix) {
   244  		return errMissingKeyPrefix
   245  	}
   246  
   247  	strNoPrefix := strNoQuotes[len(PrivateKeyPrefix):]
   248  	keyBytes, err := cb58.Decode(strNoPrefix)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	if len(keyBytes) != PrivateKeyLen {
   253  		return errInvalidPrivateKeyLength
   254  	}
   255  
   256  	*k = PrivateKey{
   257  		sk:    secp256k1.PrivKeyFromBytes(keyBytes),
   258  		bytes: keyBytes,
   259  	}
   260  	return nil
   261  }
   262  
   263  func (k *PrivateKey) UnmarshalText(text []byte) error {
   264  	return k.UnmarshalJSON(text)
   265  }
   266  
   267  // raw sig has format [v || r || s] whereas the sig has format [r || s || v]
   268  func rawSigToSig(sig []byte) ([]byte, error) {
   269  	if len(sig) != SignatureLen {
   270  		return nil, errInvalidSigLen
   271  	}
   272  	recCode := sig[0]
   273  	copy(sig, sig[1:])
   274  	sig[SignatureLen-1] = recCode - compactSigMagicOffset
   275  	return sig, nil
   276  }
   277  
   278  // sig has format [r || s || v] whereas the raw sig has format [v || r || s]
   279  func sigToRawSig(sig []byte) ([]byte, error) {
   280  	if len(sig) != SignatureLen {
   281  		return nil, errInvalidSigLen
   282  	}
   283  	newSig := make([]byte, SignatureLen)
   284  	newSig[0] = sig[SignatureLen-1] + compactSigMagicOffset
   285  	copy(newSig[1:], sig)
   286  	return newSig, nil
   287  }
   288  
   289  // verifies the signature format in format [r || s || v]
   290  func verifySECP256K1RSignatureFormat(sig []byte) error {
   291  	if len(sig) != SignatureLen {
   292  		return errInvalidSigLen
   293  	}
   294  
   295  	var s secp256k1.ModNScalar
   296  	s.SetByteSlice(sig[32:64])
   297  	if s.IsOverHalfOrder() {
   298  		return errMutatedSig
   299  	}
   300  	return nil
   301  }