Categorygithub.com/dustinxie/ecc
modulepackage
0.0.0-20210511000915-959544187564
Repository: https://github.com/dustinxie/ecc.git
Documentation: pkg.go.dev

# README

secp256k1

Golang native implementation of the secp256k1 elliptic curve

LICENSE Go version Go Report card Go Reference

Features

  • Based on Golang's native crypto/ecdsa and crypto/elliptic package, no external dependency at all
  • Full compatible with the secp256k1 signature in go-ethereum

Motivation

Golang's elliptic.Curve implements the short-form Weierstrass curve y² = x³ + ax + b, but only with a = -3, which are the case for NIST-recommended curves P224, P256,P384, and P521. For a general curve with a != -3, one would have to rely on external packages, which is quite an inconvenience.

For example, a very popular curve is secp256k1 with equation y² = x³ + 7, used by many crypto projects such as Bitcoin and Ethereum. In order to use it, one would usually need to import for example go-ethereum, which is a very large package with many dependencies.

This package provides a secp256k1 implementation solely based on Golang's native code. No external dependency is introduced.

How to use

Package's P256k1() method returns a elliptic.Curve that implements the secp256k1 curve, use it the same way as you would use other curves in the ecdsa package.

Or use package's SignBytes() and VerifyBytes() API that signs/verifies the signature as a byte-stream. See example below:

package anyname

import (
	"crypto/ecdsa"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	
	"github.com/dustinxie/ecc"
)

func signVerify(msg []byte) error {
	// generate secp256k1 private key
	p256k1 := ecc.P256k1()
	privKey, err := ecdsa.GenerateKey(p256k1, rand.Reader)
	if err != nil {
		// handle error
		return err
	}
	
	// sign message
	hash := sha256.Sum256(msg)
	sig, err := ecc.SignBytes(privKey, hash[:], ecc.Normal)
	if err != nil {
		return err
	}
	
	// verify message
	if !ecc.VerifyBytes(&privKey.PublicKey, hash[:], sig, ecc.Normal) {
		return fmt.Errorf("failed to verify secp256k1 signature")
	}
	return nil
}

Signing options

The package provides 2 additional signing options:

  • To tackle the ECDSA signature malleability issue (see "Rationale" in here), pass the flag LowerS to signing API. This ensures the resulting s value in the signature is less than or equal to half of N (the order of the curve)
// generate 64-byte signature R || S, with s <= N/2
sig, err := ecc.SignBytes(privKey, hash, ecc.LowerS)
if err != nil {
	return err
}

if !ecc.VerifyBytes(&privKey.PublicKey, hash, sig, ecc.LowerS) {
	return fmt.Errorf("failed to verify secp256k1 signature")
}
return nil
  • To return the one-byte recovery ID that can be used to recover public key from the signature, pass the flag RecID to signing API
// generate 65-byte signature R || S || V
sig, err := ecc.SignBytes(privKey, hash, ecc.RecID)
if err != nil {
	return err
}

if !ecc.VerifyBytes(&privKey.PublicKey, hash, sig, ecc.RecID) {
	return fmt.Errorf("failed to verify secp256k1 signature")
}

the resulting 65-byte signature allows you to recover public key from it:

pubKey, err := RecoverPubkey("P-256k1", hash, sig)
if err != nil {
	return err
}

if !pubKey.Equal(&privKey.PublicKey) {
	return fmt.Errorf("recovered public key not equal to signing public key")
}
return nil

The recommendation is to always enable ecc.LowerS option when signing any message. And finally, you can pass both flags to signing API:

sig, err := ecc.SignBytes(privKey, hash, ecc.LowerS | ecc.RecID)

Full Ethereum compatibility

Package also provides the following 3 API that are fully compatible with the official go-ethereum. They are actually just a wrapper of our API using proper options.

func SignEthereum(hash []byte, priv *ecdsa.PrivateKey) ([]byte, error)

func VerifyEthereum(pubkey, hash, sig []byte, isHomestead bool) bool

func RecoverEthereum(hash, sig []byte) ([]byte, error)

# Functions

MarshalCompressed converts a point on the curve into the compressed form specified in section 4.3.6 of ANSI X9.62.
MaybeReadByte reads a single byte from r with ~50% probability.
P256k1 returns a Curve which implements secp256k1 (https://www.secg.org/sec2-v2.pdf, section 2.4.1), also known as secp521k1.
P384 returns a Curve which implements NIST P-384 (FIPS 186-3, section D.2.4), also known as secp384r1.
P521 returns a Curve which implements NIST P-521 (FIPS 186-3, section D.2.5), also known as secp521r1.
RecoverEthereum returns the public key of the signer signature must be the 65-byte [R || S || V] format with recovery id as the last byte.
RecoverPubkey recovers the public key from the signature.
Sign signs a hash (which should be the result of hashing a larger message) using the private key, priv.
SignASN1 signs a hash (which should be the result of hashing a larger message) using the private key, priv.
SignBytes returns the signature in bytes.
SignEthereum returns an Ethereum-compatible signature The produced signature is in the 65-byte [R || S || V] format This function is susceptible to chosen plaintext attackes.
UnmarshalCompressed converts a point, serialized by MarshalCompressed, into an x, y pair.
Verify verifies the signature in r, s of hash using the public key, pub.
VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the public key, pub.
VerifyBytes verifies the signature in bytes.
VerifyEthereum verifies an Ethereum signature The public key is either compressed (33-byte) or uncompressed (65-byte) format, and the signature should have the 64-byte [R || S] format ECDSA malleability issue: For an ECDSA signature (r, s, v), it can be shown that (r, N-s, v^1) is also a valid signature that can correctly recover the public key.

# Constants

return (r, s) with s <= N/2.
signing options.
return recovery id in addition to (r, s).

# Variables

# Structs

CurveParams contains the parameters of an elliptic curve y² = x³ + ax + b, and also provides a generic, non-constant time implementation of Curve.