Repository: lestrrat-go/jwx
Pull request #346 (2021-02-25 12:03:58)

Toggle ES256K using a build tag
Per #345 by default we're going to skip compiling in ES256K.

We WILL NOT be using `database/sql` style explicit importing scheme for the time being. If we do, this will be done in the next API breaking release. Instead we will just toggle these "extra" features using build tags like `jwx_goccy`. For this one we will be using `jwx_es256k`


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 76dc6c9b..f5fdd52c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        json_backend: [ 'stdlib', 'goccy' ]
+        json_backend: [ 'stdlib', 'goccy', 'es256k', 'all']
         go: [ '1.16.x', '1.15.x' ]
     name: "Test [ Go ${{ matrix.go }} / JSON Backend ${{ matrix.json_backend }} ]"
     steps:
@@ -47,10 +47,10 @@ jobs:
       - name: Test with coverage
         run: make cover-${{ matrix.json_backend }}
       - name: Upload code coverage to codecov
-        if: matrix.go == '1.15.x'
+        if: matrix.go == '1.16.x'
         uses: codecov/codecov-action@v1
         with:
           file: ./coverage.out
       - name: Check difference between generation code and commit code
         run: make check_diffs
-
+ 
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
index 9c37e4e0..ab383660 100644
--- a/.github/workflows/smoke.yml
+++ b/.github/workflows/smoke.yml
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        json_backend: [ 'stdlib', 'goccy' ]
+        json_backend: [ 'stdlib', 'goccy', 'es256k', 'all' ]
         go: [ '1.16.x', '1.15.x' ]
     name: "Smoke [ Go ${{ matrix.go }} / JSON Backend ${{ matrix.json_backend }} ]"
     steps:
@@ -36,7 +36,7 @@ jobs:
         run: sudo apt-get install -y --no-install-recommends jose
       - run: make generate
       - name: Run smoke tests
-        run: make smoke
+        run: make smoke-${{ matrix.json_backend }}
       - name: Check difference between generation code and commit code
         run: make check_diffs
       - name: Run go mod tidy
diff --git a/Changes b/Changes
index 48f022b2..21b63f9b 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,16 @@
 Changes
 =======
 
+v1.1.4
+[Miscellaneous]
+  * ES256K has been made non-default. You must enable it using a build tag
+
+     go build -tags jwx_es256k ...
+
+    Your program will still compile without this tag, but it will return
+    an error during runtime, when ES256K is encountered.
+    This feature is still experimental.
+
 v1.1.3 22 Feb 2021
 [New features]
   * Implemented ES256K signing (#337)
diff --git a/Makefile b/Makefile
index 97501f68..25d6d35c 100644
--- a/Makefile
+++ b/Makefile
@@ -10,51 +10,59 @@ generate-%:
 realclean:
 	rm coverage.out
 
-_test:
-	go test -race $(TESTOPTS)
+test-cmd:
+	go test -v -race $(TESTOPTS)
 
 test:
-	$(MAKE) -C examples _test
-	$(MAKE) -C bench _test
-	$(MAKE) _test TESOPTS=./...
+	$(MAKE) test-cmd TESOPTS=./...
+	$(MAKE) -f $(PWD)/Makefile -C examples test-cmd
+	$(MAKE) -f $(PWD)/Makefile -C bench test-cmd
+
+cover-cmd:
+	$(MAKE) test-cmd 
+	$(MAKE) -f $(PWD)/Makefile -C examples TESTOPTS= test-cmd
+	$(MAKE) -f $(PWD)/Makefile -C bench TESTOPTS= test-cmd
+	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx TESTOPTS= test-cmd
+	@# This is NOT cheating. tools to generate code, and tools to
+	@# run tests don't need to be included in the final result.
+	@cat coverage.out.tmp | grep -v "internal/jose" | grep -v "internal/jwxtest" | grep -v "internal/cmd" > coverage.out
+	@rm coverage.out.tmp
 
 cover:
 	$(MAKE) cover-stdlib
 
 cover-stdlib:
-	$(MAKE) -f $(PWD)/Makefile -C examples _test
-	$(MAKE) -f $(PWD)/Makefile -C bench _test
-	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx _test
-	$(MAKE) _test TESTOPTS="-coverpkg=./... -coverprofile=coverage.out.tmp ./..."
-	@# This is NOT cheating. tools to generate code don't need to be
-	@# included in the final result. Also, we currently don't do
-	@# any active development on the jwx command
-	@cat coverage.out.tmp | grep -v "internal/jose" | grep -v "internal/jwxtest" | grep -v "internal/cmd" | grep -v "cmd/jwx/jwx.go" > coverage.out
-	@rm coverage.out.tmp
+	$(MAKE) cover-cmd TESTOPTS="-coverpkg=./... -coverprofile=coverage.out.tmp ./..."
 
 cover-goccy:
-	$(MAKE) -f $(PWD)/Makefile -C examples _test TESTOPTS="-tags jwx_goccy"
-	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx _test TESTOPTS="-tags jwx_goccy"
-	$(MAKE) _test TESTOPTS="-tags jwx_goccy -coverpkg=./... -coverprofile=coverage.out.tmp ./..."
-	@# This is NOT cheating. tools to generate code don't need to be
-	@# included in the final result
-	@cat coverage.out.tmp | grep -v "internal/jose" | grep -v "internal/jwxtest" | grep -v "internal/cmd" | grep -v "cmd/jwx/jwx.go" > coverage.out
-	@rm coverage.out.tmp
+	$(MAKE) cover-cmd TESTOPTS="-tags jwx_goccy -coverpkg=./... -coverprofile=coverage.out.tmp ./..."
+
+cover-es256k:
+	$(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k -coverpkg=./... -coverprofile=coverage.out.tmp ./..."
+
+cover-all:
+	$(MAKE) cover-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k -coverpkg=./... -coverprofile=coverage.out.tmp ./..."
+
+smoke-cmd:
+	$(MAKE) test-cmd
+	$(MAKE) -f $(PWD)/Makefile -C examples test-cmd
+	$(MAKE) -f $(PWD)/Makefile -C bench test-cmd
+	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx test-cmd
 
 smoke:
 	$(MAKE) smoke-stdlib
 
 smoke-stdlib:
-	$(MAKE) -f $(PWD)/Makefile -C examples _test
-	$(MAKE) -f $(PWD)/Makefile -C bench _test
-	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx _test
-	$(MAKE) _test TESTOPTS="-short ./..."
+	$(MAKE) smoke-cmd TESTOPTS="-short ./..."
 
 smoke-goccy:
-	$(MAKE) -f $(PWD)/Makefile -C examples _test TESTOPTS="-tags jwx_goccy"
-	$(MAKE) -f $(PWD)/Makefile -C bench _test TESTOPTS="-tags jwx_goccy"
-	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx _test TESTOPTS="-tags jwx_goccy"
-	$(MAKE) _test TESOPTS="-short -tags jwx_goccy ./..."
+	$(MAKE) smoke-cmd TESTOPTS="-short -tags jwx_goccy ./..."
+
+smoke-es256k:
+	$(MAKE) smoke-cmd TESTOPTS="-short -tags jwx_es256k ./..."
+
+smoke-all:
+	$(MAKE) smoke-cmd TESTOPTS="-short -tags jwx_goccy,jwx_es256k ./..."
 
 viewcover:
 	go tool cover -html=coverage.out
@@ -69,6 +77,10 @@ imports:
 	goimports -w ./
 
 tidy:
+	$(MAKE) tidy-cmd
+	$(MAKE) -f $(PWD)/Makefile -C examples tidy-cmd
+	$(MAKE) -f $(PWD)/Makefile -C bench tidy-cmd
+	$(MAKE) -f $(PWD)/Makefile -C cmd/jwx tidy-cmd
+
+tidy-cmd:
 	go mod tidy
-	cd examples && go mod tidy && cd ..
-	cd bench && go mod tidy && cd ..
diff --git a/README.md b/README.md
index a3162991..8df7d111 100644
--- a/README.md
+++ b/README.md
@@ -123,9 +123,10 @@ Supported signature algorithms:
 | RSASSA-PSS using SHA256 and MGF1-SHA256 | YES        | jwa.PS256                |
 | RSASSA-PSS using SHA384 and MGF1-SHA384 | YES        | jwa.PS384                |
 | RSASSA-PSS using SHA512 and MGF1-SHA512 | YES        | jwa.PS512                |
-| EdDSA (1)                               | YES        | jwa.EdDSA                |
+| EdDSA (2)                               | YES        | jwa.EdDSA                |
 
 * Note 1: Experimental
+* Note 2: Experimental, and must be toggled using `-tags jwx_es256k` build tag
 
 ## JWE [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/jwe.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwe)
 
@@ -174,6 +175,17 @@ Supported content encryption algorithm:
 
 # Global Settings
 
+## Enabling ES256K
+
+Some algorithms are intentionally left out because they are not as common in the wild, and you may want to avoid compiling this extra information in.
+To enable these, you must explicitly provide a build tag.
+
+| Algorithm        | Build Tag  |
+|:-----------------|:-----------|
+| secp256k1/ES256K | jwx_es256k |
+
+If you do not provide these tags, the program will still compile, but it will return an error during runtime saying that these algorithms are not supported.
+
 ## Switching to a faster JSON library
 
 By default we use the standard library's `encoding/json` for all of our JSON needs.
diff --git a/cmd/jwx/go.mod b/cmd/jwx/go.mod
index b9abecc2..40b21a67 100644
--- a/cmd/jwx/go.mod
+++ b/cmd/jwx/go.mod
@@ -3,8 +3,10 @@ module github.com/lestrrat-go/jwx/cmd/jwx
 go 1.15
 
 require (
-	github.com/lestrrat-go/jwx v1.1.1-0.20210204233036-9593b54afe33
+	github.com/lestrrat-go/jwx v1.1.3
 	github.com/pkg/errors v0.9.1
 	github.com/urfave/cli/v2 v2.3.0
 	golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
 )
+
+replace github.com/lestrrat-go/jwx => ../..
diff --git a/cmd/jwx/go.sum b/cmd/jwx/go.sum
index 8caeb444..04789c48 100644
--- a/cmd/jwx/go.sum
+++ b/cmd/jwx/go.sum
@@ -3,8 +3,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/goccy/go-json v0.3.5 h1:HqrLjEWx7hD62JRhBh+mHv+rEEzBANIu6O0kbDlaLzU=
-github.com/goccy/go-json v0.3.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw=
+github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8=
+github.com/goccy/go-json v0.4.7 h1:xGUjaNfhpqhKAV2LoyNXihFLZ8ABSST8B+W+duHqkPI=
+github.com/goccy/go-json v0.4.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE=
 github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
 github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
@@ -12,8 +16,6 @@ github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkO
 github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
 github.com/lestrrat-go/iter v1.0.0 h1:QD+hHQPDSHC4rCJkZYY/yXChYr/vjfBopKekTc+7l4Q=
 github.com/lestrrat-go/iter v1.0.0/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
-github.com/lestrrat-go/jwx v1.1.1-0.20210204233036-9593b54afe33 h1:+eOD953MdZnVecCScOxEagK80xaaa/DSMvBrCYkQYbM=
-github.com/lestrrat-go/jwx v1.1.1-0.20210204233036-9593b54afe33/go.mod h1:vn9FzD6gJtKkgYs7RTKV7CjWtEka8F/voUollhnn4QE=
 github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
@@ -63,6 +65,7 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/cmd/jwx/jwk.go b/cmd/jwx/jwk.go
index 4ea09a48..b3616c16 100644
--- a/cmd/jwx/jwk.go
+++ b/cmd/jwx/jwk.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"bytes"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
@@ -9,6 +10,7 @@ import (
 	"io"
 	"io/ioutil"
 
+	"github.com/lestrrat-go/jwx/internal/ecutil"
 	"github.com/lestrrat-go/jwx/jwa"
 	"github.com/lestrrat-go/jwx/jwk"
 	"github.com/lestrrat-go/jwx/x25519"
@@ -87,6 +89,14 @@ func dumpJWKSet(dst io.Writer, keyset jwk.Set, format string, preserve bool) err
 }
 
 func makeJwkGenerateCmd() *cli.Command {
+	var crvnames bytes.Buffer
+	for i, crv := range ecutil.AvailableCurves() {
+		if i > 0 {
+			crvnames.WriteByte('/')
+		}
+		crvnames.WriteString(crv.Params().Name)
+	}
+
 	var cmd cli.Command
 	cmd.Name = "generate"
 	cmd.Aliases = []string{"gen"}
@@ -101,7 +111,7 @@ func makeJwkGenerateCmd() *cli.Command {
 		&cli.StringFlag{
 			Name:    "curve",
 			Aliases: []string{"c"},
-			Usage:   "Elliptic curve name `CURVE` (P-256/P-384/P-521) for ECDSA and OKP keys",
+			Usage:   "Elliptic curve name `CURVE` (" + crvnames.String() + ") for ECDSA and OKP keys",
 		},
 		&cli.StringFlag{
 			Name:  "template",
@@ -133,18 +143,12 @@ func makeJwkGenerateCmd() *cli.Command {
 
 			var crvalg jwa.EllipticCurveAlgorithm
 			if err := crvalg.Accept(c.String("curve")); err != nil {
-				return errors.Wrap(err, `invalid elliptic curve name`)
+				return errors.Wrapf(err, `invalid elliptic curve name %s`, c.String("curve"))
 			}
 
-			switch crvalg {
-			case jwa.P256:
-				crv = elliptic.P256()
-			case jwa.P384:
-				crv = elliptic.P384()
-			case jwa.P521:
-				crv = elliptic.P521()
-			default:
-				return errors.Errorf(`invalid elliptic curve for ECDSA: %s (expected %s/%s/%s)`, crvalg, jwa.P256, jwa.P384, jwa.P521)
+			crv, ok := ecutil.CurveForAlgorithm(crvalg)
+			if !ok {
+				return errors.Errorf(`invalid elliptic curve for ECDSA: %s (expected %s)`, crvalg, crvnames.String())
 			}
 
 			v, err := ecdsa.GenerateKey(crv, rand.Reader)
diff --git a/internal/ecutil/ecutil.go b/internal/ecutil/ecutil.go
index c80cdf59..1d79aeb3 100644
--- a/internal/ecutil/ecutil.go
+++ b/internal/ecutil/ecutil.go
@@ -6,8 +6,46 @@ import (
 	"crypto/elliptic"
 	"math/big"
 	"sync"
+
+	"github.com/lestrrat-go/jwx/jwa"
 )
 
+// data for available curves. Some algorithms may be compiled in/out
+var curveToAlg = map[elliptic.Curve]jwa.EllipticCurveAlgorithm{}
+var algToCurve = map[jwa.EllipticCurveAlgorithm]elliptic.Curve{}
+var availableAlgs []jwa.EllipticCurveAlgorithm
+var availableCrvs []elliptic.Curve
+
+func RegisterCurve(crv elliptic.Curve, alg jwa.EllipticCurveAlgorithm) {
+	curveToAlg[crv] = alg
+	algToCurve[alg] = crv
+	availableAlgs = append(availableAlgs, alg)
+	availableCrvs = append(availableCrvs, crv)
+}
+
+func IsAvailable(alg jwa.EllipticCurveAlgorithm) bool {
+	_, ok := algToCurve[alg]
+	return ok
+}
+
+func AvailableAlgorithms() []jwa.EllipticCurveAlgorithm {
+	return availableAlgs
+}
+
+func AvailableCurves() []elliptic.Curve {
+	return availableCrvs
+}
+
+func AlgorithmForCurve(crv elliptic.Curve) (jwa.EllipticCurveAlgorithm, bool) {
+	v, ok := curveToAlg[crv]
+	return v, ok
+}
+
+func CurveForAlgorithm(alg jwa.EllipticCurveAlgorithm) (elliptic.Curve, bool) {
+	v, ok := algToCurve[alg]
+	return v, ok
+}
+
 const (
 	// size of buffer that needs to be allocated for EC521 curve
 	ec521BufferSize = 66 // (521 / 8) + 1
diff --git a/jwa/compression_gen.go b/jwa/compression_gen.go
index ff25cb45..5eeb796b 100644
--- a/jwa/compression_gen.go
+++ b/jwa/compression_gen.go
@@ -4,6 +4,8 @@ package jwa
 
 import (
 	"fmt"
+	"sort"
+	"sync"
 
 	"github.com/pkg/errors"
 )
@@ -17,14 +19,26 @@ const (
 	NoCompress CompressionAlgorithm = ""    // No compression
 )
 
-var allCompressionAlgorithms = []CompressionAlgorithm{
-	Deflate,
-	NoCompress,
+var allCompressionAlgorithms = map[CompressionAlgorithm]struct{}{
+	Deflate:    {},
+	NoCompress: {},
 }
 
+var listCompressionAlgorithmOnce sync.Once
+var listCompressionAlgorithm []CompressionAlgorithm
+
 // CompressionAlgorithms returns a list of all available values for CompressionAlgorithm
 func CompressionAlgorithms() []CompressionAlgorithm {
-	return allCompressionAlgorithms
+	listCompressionAlgorithmOnce.Do(func() {
+		listCompressionAlgorithm = make([]CompressionAlgorithm, 0, len(allCompressionAlgorithms))
+		for v := range allCompressionAlgorithms {
+			listCompressionAlgorithm = append(listCompressionAlgorithm, v)
+		}
+		sort.Slice(listCompressionAlgorithm, func(i, j int) bool {
+			return string(listCompressionAlgorithm[i]) < string(listCompressionAlgorithm[j])
+		})
+	})
+	return listCompressionAlgorithm
 }
 
 // Accept is used when conversion from values given by
@@ -45,9 +59,7 @@ func (v *CompressionAlgorithm) Accept(value interface{}) error {
 		}
 		tmp = CompressionAlgorithm(s)
 	}
-	switch tmp {
-	case Deflate, NoCompress:
-	default:
+	if _, ok := allCompressionAlgorithms[tmp]; !ok {
 		return errors.Errorf(`invalid jwa.CompressionAlgorithm value`)
 	}
 
diff --git a/jwa/content_encryption_gen.go b/jwa/content_encryption_gen.go
index cfe3d5ff..8b2eb956 100644
--- a/jwa/content_encryption_gen.go
+++ b/jwa/content_encryption_gen.go
@@ -4,6 +4,8 @@ package jwa
 
 import (
 	"fmt"
+	"sort"
+	"sync"
 
 	"github.com/pkg/errors"
 )
@@ -21,18 +23,30 @@ const (
 	A256GCM       ContentEncryptionAlgorithm = "A256GCM"       // AES-GCM (256)
 )
 
-var allContentEncryptionAlgorithms = []ContentEncryptionAlgorithm{
-	A128CBC_HS256,
-	A128GCM,
-	A192CBC_HS384,
-	A192GCM,
-	A256CBC_HS512,
-	A256GCM,
+var allContentEncryptionAlgorithms = map[ContentEncryptionAlgorithm]struct{}{
+	A128CBC_HS256: {},
+	A128GCM:       {},
+	A192CBC_HS384: {},
+	A192GCM:       {},
+	A256CBC_HS512: {},
+	A256GCM:       {},
 }
 
+var listContentEncryptionAlgorithmOnce sync.Once
+var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm
+
 // ContentEncryptionAlgorithms returns a list of all available values for ContentEncryptionAlgorithm
 func ContentEncryptionAlgorithms() []ContentEncryptionAlgorithm {
-	return allContentEncryptionAlgorithms
+	listContentEncryptionAlgorithmOnce.Do(func() {
+		listContentEncryptionAlgorithm = make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithms))
+		for v := range allContentEncryptionAlgorithms {
+			listContentEncryptionAlgorithm = append(listContentEncryptionAlgorithm, v)
+		}
+		sort.Slice(listContentEncryptionAlgorithm, func(i, j int) bool {
+			return string(listContentEncryptionAlgorithm[i]) < string(listContentEncryptionAlgorithm[j])
+		})
+	})
+	return listContentEncryptionAlgorithm
 }
 
 // Accept is used when conversion from values given by
@@ -53,9 +67,7 @@ func (v *ContentEncryptionAlgorithm) Accept(value interface{}) error {
 		}
 		tmp = ContentEncryptionAlgorithm(s)
 	}
-	switch tmp {
-	case A128CBC_HS256, A128GCM, A192CBC_HS384, A192GCM, A256CBC_HS512, A256GCM:
-	default:
+	if _, ok := allContentEncryptionAlgorithms[tmp]; !ok {
 		return errors.Errorf(`invalid jwa.ContentEncryptionAlgorithm value`)
 	}
 
diff --git a/jwa/elliptic_gen.go b/jwa/elliptic_gen.go
index 5ef8a87f..acc045cc 100644
--- a/jwa/elliptic_gen.go
+++ b/jwa/elliptic_gen.go
@@ -4,6 +4,8 @@ package jwa
 
 import (
 	"fmt"
+	"sort"
+	"sync"
 
 	"github.com/pkg/errors"
 )
@@ -19,25 +21,35 @@ const (
 	P256                 EllipticCurveAlgorithm = "P-256"
 	P384                 EllipticCurveAlgorithm = "P-384"
 	P521                 EllipticCurveAlgorithm = "P-521"
-	Secp256k1            EllipticCurveAlgorithm = "secp256k1"
 	X25519               EllipticCurveAlgorithm = "X25519"
 	X448                 EllipticCurveAlgorithm = "X448"
 )
 
-var allEllipticCurveAlgorithms = []EllipticCurveAlgorithm{
-	Ed25519,
-	Ed448,
-	P256,
-	P384,
-	P521,
-	Secp256k1,
-	X25519,
-	X448,
+var allEllipticCurveAlgorithms = map[EllipticCurveAlgorithm]struct{}{
+	Ed25519: {},
+	Ed448:   {},
+	P256:    {},
+	P384:    {},
+	P521:    {},
+	X25519:  {},
+	X448:    {},
 }
 
+var listEllipticCurveAlgorithmOnce sync.Once
+var listEllipticCurveAlgorithm []EllipticCurveAlgorithm
+
 // EllipticCurveAlgorithms returns a list of all available values for EllipticCurveAlgorithm
 func EllipticCurveAlgorithms() []EllipticCurveAlgorithm {
-	return allEllipticCurveAlgorithms
+	listEllipticCurveAlgorithmOnce.Do(func() {
+		listEllipticCurveAlgorithm = make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithms))
+		for v := range allEllipticCurveAlgorithms {
+			listEllipticCurveAlgorithm = append(listEllipticCurveAlgorithm, v)
+		}
+		sort.Slice(listEllipticCurveAlgorithm, func(i, j int) bool {
+			return string(listEllipticCurveAlgorithm[i]) < string(listEllipticCurveAlgorithm[j])
+		})
+	})
+	return listEllipticCurveAlgorithm
 }
 
 // Accept is used when conversion from values given by
@@ -58,9 +70,7 @@ func (v *EllipticCurveAlgorithm) Accept(value interface{}) error {
 		}
 		tmp = EllipticCurveAlgorithm(s)
 	}
-	switch tmp {
-	case Ed25519, Ed448, P256, P384, P521, Secp256k1, X25519, X448:
-	default:
+	if _, ok := allEllipticCurveAlgorithms[tmp]; !ok {
 		return errors.Errorf(`invalid jwa.EllipticCurveAlgorithm value`)
 	}
 
diff --git a/jwa/internal/cmd/gentypes/main.go b/jwa/internal/cmd/gentypes/main.go
index 616bd798..05417276 100644
--- a/jwa/internal/cmd/gentypes/main.go
+++ b/jwa/internal/cmd/gentypes/main.go
@@ -9,8 +9,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/lestrrat-go/codegen"
 	"github.com/pkg/errors"
-	"golang.org/x/tools/imports"
 )
 
 func main() {
@@ -131,10 +131,6 @@ func _main() error {
 					name:  `P521`,
 					value: `P-521`,
 				},
-				{
-					name:  `Secp256k1`,
-					value: `secp256k1`,
-				},
 				{
 					name:  `Ed25519`,
 					value: `Ed25519`,
@@ -404,16 +400,28 @@ func (t typ) Generate() error {
 	}
 	fmt.Fprintf(&buf, "\n)") // end const
 
-	fmt.Fprintf(&buf, "\nvar all%[1]ss = []%s {", t.name, t.name)
+	fmt.Fprintf(&buf, "\nvar all%[1]ss = map[%[1]s]struct{} {", t.name)
 	for _, e := range t.elements {
 		if !e.invalid {
-			fmt.Fprintf(&buf, "\n%s,", e.name)
+			fmt.Fprintf(&buf, "\n%s: {},", e.name)
 		}
 	}
 	fmt.Fprintf(&buf, "\n}")
+
+	fmt.Fprintf(&buf, "\n\nvar list%sOnce sync.Once", t.name)
+	fmt.Fprintf(&buf, "\nvar list%[1]s []%[1]s", t.name)
 	fmt.Fprintf(&buf, "\n\n// %[1]ss returns a list of all available values for %[1]s", t.name)
 	fmt.Fprintf(&buf, "\nfunc %[1]ss() []%[1]s {", t.name)
-	fmt.Fprintf(&buf, "\nreturn all%ss", t.name)
+	fmt.Fprintf(&buf, "\nlist%sOnce.Do(func() {", t.name)
+	fmt.Fprintf(&buf, "\nlist%[1]s = make([]%[1]s, 0, len(all%[1]ss))", t.name)
+	fmt.Fprintf(&buf, "\nfor v := range all%ss {", t.name)
+	fmt.Fprintf(&buf, "\nlist%[1]s = append(list%[1]s, v)", t.name)
+	fmt.Fprintf(&buf, "\n}")
+	fmt.Fprintf(&buf, "\nsort.Slice(list%s, func(i, j int) bool {", t.name)
+	fmt.Fprintf(&buf, "\nreturn string(list%[1]s[i]) < string(list%[1]s[j])", t.name)
+	fmt.Fprintf(&buf, "\n})")
+	fmt.Fprintf(&buf, "\n})")
+	fmt.Fprintf(&buf, "\nreturn list%s", t.name)
 	fmt.Fprintf(&buf, "\n}")
 
 	fmt.Fprintf(&buf, "\n\n// Accept is used when conversion from values given by")
@@ -435,24 +443,7 @@ func (t typ) Generate() error {
 	fmt.Fprintf(&buf, "\ntmp = %s(s)", t.name)
 	fmt.Fprintf(&buf, "\n}")
 
-	fmt.Fprintf(&buf, "\nswitch tmp {")
-	fmt.Fprintf(&buf, "\ncase ")
-	valids := make([]element, 0, len(t.elements))
-	for _, e := range t.elements {
-		if e.invalid {
-			continue
-		}
-		valids = append(valids, e)
-	}
-
-	for i, e := range valids {
-		fmt.Fprintf(&buf, "%s", e.name)
-		if i < len(valids)-1 {
-			fmt.Fprintf(&buf, ", ")
-		}
-	}
-	fmt.Fprintf(&buf, ":")
-	fmt.Fprintf(&buf, "\ndefault:")
+	fmt.Fprintf(&buf, "\nif _, ok := all%ss[tmp]; !ok {", t.name)
 	fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid jwa.%s value`)", t.name)
 	fmt.Fprintf(&buf, "\n}")
 
@@ -489,19 +480,12 @@ func (t typ) Generate() error {
 		fmt.Fprintf(&buf, "\n}")
 	}
 
-	formatted, err := imports.Process("", buf.Bytes(), nil)
-	if err != nil {
-		os.Stdout.Write(buf.Bytes())
-		return errors.Wrap(err, `failed to format source`)
-	}
-
-	f, err := os.Create(t.filename)
-	if err != nil {
-		return errors.Wrapf(err, `failed to create %s`, t.filename)
+	if err := codegen.WriteFile(t.filename, &buf, codegen.WithFormatCode(true)); err != nil {
+		if cfe, ok := err.(codegen.CodeFormatError); ok {
+			fmt.Fprint(os.Stderr, cfe.Source())
+		}
+		return errors.Wrapf(err, `failed to write to %s`, t.filename)
 	}
-	defer f.Close()
-	f.Write(formatted)
-
 	return nil
 }
 
@@ -619,19 +603,12 @@ func (t typ) GenerateTest() error {
 
 	fmt.Fprintf(&buf, "\n}")
 
-	formatted, err := imports.Process("", buf.Bytes(), nil)
-	if err != nil {
-		os.Stdout.Write(buf.Bytes())
-		return errors.Wrap(err, `failed to format source`)
-	}
-
 	filename := strings.Replace(t.filename, "_gen.go", "_gen_test.go", 1)
-	f, err := os.Create(filename)
-	if err != nil {
-		return errors.Wrapf(err, `failed to create %s`, t.filename)
+	if err := codegen.WriteFile(filename, &buf, codegen.WithFormatCode(true)); err != nil {
+		if cfe, ok := err.(codegen.CodeFormatError); ok {
+			fmt.Fprint(os.Stderr, cfe.Source())
+		}
+		return errors.Wrapf(err, `failed to write to %s`, filename)
 	}
-	defer f.Close()
-	f.Write(formatted)
-
 	return nil
 }
diff --git a/jwa/key_encryption_gen.go b/jwa/key_encryption_gen.go
index 8f01eea0..cecf859f 100644
--- a/jwa/key_encryption_gen.go
+++ b/jwa/key_encryption_gen.go
@@ -4,6 +4,8 @@ package jwa
 
 import (
 	"fmt"
+	"sort"
+	"sync"
 
 	"github.com/pkg/errors"
 )
@@ -32,29 +34,41 @@ const (
 	RSA_OAEP_256       KeyEncryptionAlgorithm = "RSA-OAEP-256"       // RSA-OAEP-SHA256
 )
 
-var allKeyEncryptionAlgorithms = []KeyEncryptionAlgorithm{
-	A128GCMKW,
-	A128KW,
-	A192GCMKW,
-	A192KW,
-	A256GCMKW,
-	A256KW,
-	DIRECT,
-	ECDH_ES,
-	ECDH_ES_A128KW,
-	ECDH_ES_A192KW,
-	ECDH_ES_A256KW,
-	PBES2_HS256_A128KW,
-	PBES2_HS384_A192KW,
-	PBES2_HS512_A256KW,
-	RSA1_5,
-	RSA_OAEP,
-	RSA_OAEP_256,
+var allKeyEncryptionAlgorithms = map[KeyEncryptionAlgorithm]struct{}{
+	A128GCMKW:          {},
+	A128KW:             {},
+	A192GCMKW:          {},
+	A192KW:             {},
+	A256GCMKW:          {},
+	A256KW:             {},
+	DIRECT:             {},
+	ECDH_ES:            {},
+	ECDH_ES_A128KW:     {},
+	ECDH_ES_A192KW:     {},
+	ECDH_ES_A256KW:     {},
+	PBES2_HS256_A128KW: {},
+	PBES2_HS384_A192KW: {},
+	PBES2_HS512_A256KW: {},
+	RSA1_5:             {},
+	RSA_OAEP:           {},
+	RSA_OAEP_256:       {},
 }
 
+var listKeyEncryptionAlgorithmOnce sync.Once
+var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm
+
 // KeyEncryptionAlgorithms returns a list of all available values for KeyEncryptionAlgorithm
 func KeyEncryptionAlgorithms() []KeyEncryptionAlgorithm {
-	return allKeyEncryptionAlgorithms
+	listKeyEncryptionAlgorithmOnce.Do(func() {
+		listKeyEncryptionAlgorithm = make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithms))
+		for v := range allKeyEncryptionAlgorithms {
+			listKeyEncryptionAlgorithm = append(listKeyEncryptionAlgorithm, v)
+		}
+		sort.Slice(listKeyEncryptionAlgorithm, func(i, j int) bool {
+			return string(listKeyEncryptionAlgorithm[i]) < string(listKeyEncryptionAlgorithm[j])
+		})
+	})
+	return listKeyEncryptionAlgorithm
 }
 
 // Accept is used when conversion from values given by
@@ -75,9 +89,7 @@ func (v *KeyEncryptionAlgorithm) Accept(value interface{}) error {
 		}
 		tmp = KeyEncryptionAlgorithm(s)
 	}
-	switch tmp {
-	case A128GCMKW, A128KW, A192GCMKW, A192KW, A256GCMKW, A256KW, DIRECT, ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW, PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW, RSA1_5, RSA_OAEP, RSA_OAEP_256:
-	default:
+	if _, ok := allKeyEncryptionAlgorithms[tmp]; !ok {
 		return errors.Errorf(`invalid jwa.KeyEncryptionAlgorithm value`)
 	}
 
diff --git a/jwa/key_type_gen.go b/jwa/key_type_gen.go
index 6a347bd3..869afd40 100644
--- a/jwa/key_type_gen.go
+++ b/jwa/key_type_gen.go
@@ -4,6 +4,8 @@ package jwa
 
 import (
 	"fmt"
+	"sort"
+	"sync"
 
 	"github.com/pkg/errors"
 )
@@ -20,16 +22,28 @@ const (
 	RSA            KeyType = "RSA" // RSA
 )
 
-var allKeyTypes = []KeyType{
-	EC,
-	OKP,
-	OctetSeq,
-	RSA,
+var allKeyTypes = map[KeyType]struct{}{
+	EC:       {},
+	OKP:      {},
+	OctetSeq: {},
+	RSA:      {},
 }
 
+var listKeyTypeOnce sync.Once
+var listKeyType []KeyType
+
 // KeyTypes returns a list of all available values for KeyType
 func KeyTypes() []KeyType {
-	return allKeyTypes
+	listKeyTypeOnce.Do(func() {
+		listKeyType = make([]KeyType, 0, len(allKeyTypes))
+		for v := range allKeyTypes {
+			listKeyType = append(listKeyType, v)
+		}
+		sort.Slice(listKeyType, func(i, j int) bool {
+			return string(listKeyType[i]) < string(listKeyType[j])
+		})
+	})
+	return listKeyType
 }
 
 // Accept is used when conversion from values given by
@@ -50,9 +64,7 @@ func (v *KeyType) Accept(value interface{}) error {
 		}
 		tmp = KeyType(s)
 	}
-	switch tmp {
-	case EC, OKP, OctetSeq, RSA:
-	default:
+	if _, ok := allKeyTypes[tmp]; !ok {
 		return errors.Errorf(`invalid jwa.KeyType value`)
 	}
 
diff --git a/jwa/secp2561k.go b/jwa/secp2561k.go
new file mode 100644
index 00000000..594f7590
--- /dev/null
+++ b/jwa/secp2561k.go
@@ -0,0 +1,10 @@
+// +build jwx_es256k
+
+package jwa
+
+// This constant is only available if compiled with jwx_es256k build tag
+const Secp256k1 EllipticCurveAlgorithm = "secp256k1"
+
+func init() {
+	allEllipticCurveAlgorithms[Secp256k1] = struct{}{}
+}
diff --git a/jwa/signature_gen.go b/jwa/signature_gen.go
index 5d293918..e4305036 100644
--- a/jwa/signature_gen.go
+++ b/jwa/signature_gen.go
@@ -4,6 +4,8 @@ package jwa
 
 import (
 	"fmt"
+	"sort"
+	"sync"
 
 	"github.com/pkg/errors"
 )
@@ -30,27 +32,39 @@ const (
 	RS512       SignatureAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
 )
 
-var allSignatureAlgorithms = []SignatureAlgorithm{
-	ES256,
-	ES256K,
-	ES384,
-	ES512,
-	EdDSA,
-	HS256,
-	HS384,
-	HS512,
-	NoSignature,
-	PS256,
-	PS384,
-	PS512,
-	RS256,
-	RS384,
-	RS512,
+var allSignatureAlgorithms = map[SignatureAlgorithm]struct{}{
+	ES256:       {},
+	ES256K:      {},
+	ES384:       {},
+	ES512:       {},
+	EdDSA:       {},
+	HS256:       {},
+	HS384:       {},
+	HS512:       {},
+	NoSignature: {},
+	PS256:       {},
+	PS384:       {},
+	PS512:       {},
+	RS256:       {},
+	RS384:       {},
+	RS512:       {},
 }
 
+var listSignatureAlgorithmOnce sync.Once
+var listSignatureAlgorithm []SignatureAlgorithm
+
 // SignatureAlgorithms returns a list of all available values for SignatureAlgorithm
 func SignatureAlgorithms() []SignatureAlgorithm {
-	return allSignatureAlgorithms
+	listSignatureAlgorithmOnce.Do(func() {
+		listSignatureAlgorithm = make([]SignatureAlgorithm, 0, len(allSignatureAlgorithms))
+		for v := range allSignatureAlgorithms {
+			listSignatureAlgorithm = append(listSignatureAlgorithm, v)
+		}
+		sort.Slice(listSignatureAlgorithm, func(i, j int) bool {
+			return string(listSignatureAlgorithm[i]) < string(listSignatureAlgorithm[j])
+		})
+	})
+	return listSignatureAlgorithm
 }
 
 // Accept is used when conversion from values given by
@@ -71,9 +85,7 @@ func (v *SignatureAlgorithm) Accept(value interface{}) error {
 		}
 		tmp = SignatureAlgorithm(s)
 	}
-	switch tmp {
-	case ES256, ES256K, ES384, ES512, EdDSA, HS256, HS384, HS512, NoSignature, PS256, PS384, PS512, RS256, RS384, RS512:
-	default:
+	if _, ok := allSignatureAlgorithms[tmp]; !ok {
 		return errors.Errorf(`invalid jwa.SignatureAlgorithm value`)
 	}
 
diff --git a/jwk/ecdsa.go b/jwk/ecdsa.go
index 4ab6359b..5cf09b31 100644
--- a/jwk/ecdsa.go
+++ b/jwk/ecdsa.go
@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"math/big"
 
-	"github.com/decred/dcrd/dcrec/secp256k1/v3"
 	"github.com/lestrrat-go/jwx/internal/base64"
 	"github.com/lestrrat-go/jwx/internal/blackmagic"
 	"github.com/lestrrat-go/jwx/internal/ecutil"
@@ -16,6 +15,12 @@ import (
 	"github.com/pkg/errors"
 )
 
+func init() {
+	ecutil.RegisterCurve(elliptic.P256(), jwa.P256)
+	ecutil.RegisterCurve(elliptic.P384(), jwa.P384)
+	ecutil.RegisterCurve(elliptic.P521(), jwa.P521)
+}
+
 func (k *ecdsaPublicKey) FromRaw(rawKey *ecdsa.PublicKey) error {
 	k.mu.Lock()
 	defer k.mu.Unlock()
@@ -31,16 +36,9 @@ func (k *ecdsaPublicKey) FromRaw(rawKey *ecdsa.PublicKey) error {
 	copy(k.y, ybuf)
 
 	var crv jwa.EllipticCurveAlgorithm
-	switch rawKey.Curve {
-	case elliptic.P256():
-		crv = jwa.P256
-	case elliptic.P384():
-		crv = jwa.P384
-	case elliptic.P521():
-		crv = jwa.P521
-	case secp256k1.S256():
-		crv = jwa.Secp256k1
-	default:
+	if tmp, ok := ecutil.AlgorithmForCurve(rawKey.Curve); ok {
+		crv = tmp
+	} else {
 		return errors.Errorf(`invalid elliptic curve %s`, rawKey.Curve)
 	}
 	k.crv = &crv
@@ -67,16 +65,9 @@ func (k *ecdsaPrivateKey) FromRaw(rawKey *ecdsa.PrivateKey) error {
 	copy(k.d, dbuf)
 
 	var crv jwa.EllipticCurveAlgorithm
-	switch rawKey.Curve {
-	case elliptic.P256():
-		crv = jwa.P256
-	case elliptic.P384():
-		crv = jwa.P384
-	case elliptic.P521():
-		crv = jwa.P521
-	case secp256k1.S256():
-		crv = jwa.Secp256k1
-	default:
+	if tmp, ok := ecutil.AlgorithmForCurve(rawKey.Curve); ok {
+		crv = tmp
+	} else {
 		return errors.Errorf(`invalid elliptic curve %s`, rawKey.Curve)
 	}
 	k.crv = &crv
@@ -85,17 +76,10 @@ func (k *ecdsaPrivateKey) FromRaw(rawKey *ecdsa.PrivateKey) error {
 }
 
 func buildECDSAPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ecdsa.PublicKey, error) {
-	var curve elliptic.Curve
-	switch alg {
-	case jwa.P256:
-		curve = elliptic.P256()
-	case jwa.P384:
-		curve = elliptic.P384()
-	case jwa.P521:
-		curve = elliptic.P521()
-	case jwa.Secp256k1:
-		curve = secp256k1.S256()
-	default:
+	var crv elliptic.Curve
+	if tmp, ok := ecutil.CurveForAlgorithm(alg); ok {
+		crv = tmp
+	} else {
 		return nil, errors.Errorf(`invalid curve algorithm %s`, alg)
 	}
 
@@ -103,7 +87,7 @@ func buildECDSAPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ec
 	x.SetBytes(xbuf)
 	y.SetBytes(ybuf)
 
-	return &ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}, nil
+	return &ecdsa.PublicKey{Curve: crv, X: &x, Y: &y}, nil
 }
 
 // Raw returns the EC-DSA public key represented by this JWK
diff --git a/jwk/es256k.go b/jwk/es256k.go
new file mode 100644
index 00000000..395ab255
--- /dev/null
+++ b/jwk/es256k.go
@@ -0,0 +1,13 @@
+// +build jwx_es256k
+
+package jwk
+
+import (
+	"github.com/decred/dcrd/dcrec/secp256k1/v3"
+	"github.com/lestrrat-go/jwx/internal/ecutil"
+	"github.com/lestrrat-go/jwx/jwa"
+)
+
+func init() {
+	ecutil.RegisterCurve(secp256k1.S256(), jwa.Secp256k1)
+}


Test patch:
diff --git a/internal/jwxtest/jwxtest.go b/internal/jwxtest/jwxtest.go
index 91aede3e..d1d09673 100644
--- a/internal/jwxtest/jwxtest.go
+++ b/internal/jwxtest/jwxtest.go
@@ -15,7 +15,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/decred/dcrd/dcrec/secp256k1/v3"
+	"github.com/lestrrat-go/jwx/internal/ecutil"
 	"github.com/lestrrat-go/jwx/jwa"
 	"github.com/lestrrat-go/jwx/jwe"
 	"github.com/lestrrat-go/jwx/jwk"
@@ -54,21 +54,14 @@ func GenerateRsaPublicJwk() (jwk.Key, error) {
 }
 
 func GenerateEcdsaKey(alg jwa.EllipticCurveAlgorithm) (*ecdsa.PrivateKey, error) {
-	var curve elliptic.Curve
-	switch alg {
-	case jwa.P256:
-		curve = elliptic.P256()
-	case jwa.P384:
-		curve = elliptic.P384()
-	case jwa.P521:
-		curve = elliptic.P521()
-	case jwa.Secp256k1:
-		curve = secp256k1.S256()
-	default:
+	var crv elliptic.Curve
+	if tmp, ok := ecutil.CurveForAlgorithm(alg); ok {
+		crv = tmp
+	} else {
 		return nil, errors.Errorf(`invalid curve algorithm %s`, alg)
 	}
 
-	return ecdsa.GenerateKey(curve, rand.Reader)
+	return ecdsa.GenerateKey(crv, rand.Reader)
 }
 
 func GenerateEcdsaJwk() (jwk.Key, error) {
diff --git a/jwa/elliptic_gen_test.go b/jwa/elliptic_gen_test.go
index 05734da2..923f2910 100644
--- a/jwa/elliptic_gen_test.go
+++ b/jwa/elliptic_gen_test.go
@@ -191,42 +191,6 @@ func TestEllipticCurveAlgorithm(t *testing.T) {
 			return
 		}
 	})
-	t.Run(`accept jwa constant Secp256k1`, func(t *testing.T) {
-		t.Parallel()
-		var dst jwa.EllipticCurveAlgorithm
-		if !assert.NoError(t, dst.Accept(jwa.Secp256k1), `accept is successful`) {
-			return
-		}
-		if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) {
-			return
-		}
-	})
-	t.Run(`accept the string secp256k1`, func(t *testing.T) {
-		t.Parallel()
-		var dst jwa.EllipticCurveAlgorithm
-		if !assert.NoError(t, dst.Accept("secp256k1"), `accept is successful`) {
-			return
-		}
-		if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) {
-			return
-		}
-	})
-	t.Run(`accept fmt.Stringer for secp256k1`, func(t *testing.T) {
-		t.Parallel()
-		var dst jwa.EllipticCurveAlgorithm
-		if !assert.NoError(t, dst.Accept(stringer{src: "secp256k1"}), `accept is successful`) {
-			return
-		}
-		if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) {
-			return
-		}
-	})
-	t.Run(`stringification for secp256k1`, func(t *testing.T) {
-		t.Parallel()
-		if !assert.Equal(t, "secp256k1", jwa.Secp256k1.String(), `stringified value matches`) {
-			return
-		}
-	})
 	t.Run(`accept jwa constant X25519`, func(t *testing.T) {
 		t.Parallel()
 		var dst jwa.EllipticCurveAlgorithm
diff --git a/jwa/secp2561k_test.go b/jwa/secp2561k_test.go
new file mode 100644
index 00000000..acd9ab50
--- /dev/null
+++ b/jwa/secp2561k_test.go
@@ -0,0 +1,50 @@
+// +build jwx_es256k
+
+package jwa_test
+
+import (
+	"testing"
+
+	"github.com/lestrrat-go/jwx/jwa"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSecp256k1(t *testing.T) {
+	t.Parallel()
+	t.Run(`accept jwa constant Secp256k1`, func(t *testing.T) {
+		t.Parallel()
+		var dst jwa.EllipticCurveAlgorithm
+		if !assert.NoError(t, dst.Accept(jwa.Secp256k1), `accept is successful`) {
+			return
+		}
+		if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) {
+			return
+		}
+	})
+	t.Run(`accept the string secp256k1`, func(t *testing.T) {
+		t.Parallel()
+		var dst jwa.EllipticCurveAlgorithm
+		if !assert.NoError(t, dst.Accept("secp256k1"), `accept is successful`) {
+			return
+		}
+		if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) {
+			return
+		}
+	})
+	t.Run(`accept fmt.Stringer for secp256k1`, func(t *testing.T) {
+		t.Parallel()
+		var dst jwa.EllipticCurveAlgorithm
+		if !assert.NoError(t, dst.Accept(stringer{src: "secp256k1"}), `accept is successful`) {
+			return
+		}
+		if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) {
+			return
+		}
+	})
+	t.Run(`stringification for secp256k1`, func(t *testing.T) {
+		t.Parallel()
+		if !assert.Equal(t, "secp256k1", jwa.Secp256k1.String(), `stringified value matches`) {
+			return
+		}
+	})
+}
diff --git a/jwk/es256k_test.go b/jwk/es256k_test.go
new file mode 100644
index 00000000..934109bb
--- /dev/null
+++ b/jwk/es256k_test.go
@@ -0,0 +1,17 @@
+// +build jwx_es256k
+
+package jwk_test
+
+import (
+	"testing"
+
+	"github.com/lestrrat-go/jwx/internal/ecutil"
+	"github.com/lestrrat-go/jwx/jwa"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestES256K(t *testing.T) {
+	if !assert.True(t, ecutil.IsAvailable(jwa.Secp256k1), `jwa.Secp256k1 should be available`) {
+		return
+	}
+}
diff --git a/jwk/jwk_test.go b/jwk/jwk_test.go
index b721c391..bf5daeba 100644
--- a/jwk/jwk_test.go
+++ b/jwk/jwk_test.go
@@ -12,6 +12,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/lestrrat-go/jwx/internal/ecutil"
 	"github.com/lestrrat-go/jwx/internal/jose"
 	"github.com/lestrrat-go/jwx/internal/json"
 	"github.com/lestrrat-go/jwx/internal/jwxtest"
@@ -1141,12 +1142,10 @@ func TestECDSA(t *testing.T) {
 		})
 	})
 	t.Run("Curve types", func(t *testing.T) {
-		crvs := []jwa.EllipticCurveAlgorithm{jwa.P256, jwa.P384, jwa.P521, jwa.Secp256k1}
-
-		for _, crv := range crvs {
-			crv := crv
-			t.Run(crv.String(), func(t *testing.T) {
-				key, err := jwxtest.GenerateEcdsaKey(crv)
+		for _, alg := range ecutil.AvailableAlgorithms() {
+			alg := alg
+			t.Run(alg.String(), func(t *testing.T) {
+				key, err := jwxtest.GenerateEcdsaKey(alg)
 				if !assert.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) {
 					return
 				}
diff --git a/jws/jws_test.go b/jws/jws_test.go
index 4b16e8d6..4fb7f791 100644
--- a/jws/jws_test.go
+++ b/jws/jws_test.go
@@ -209,20 +209,6 @@ func TestRoundtrip(t *testing.T) {
 			})
 		}
 	})
-	t.Run("ECDSA-256K", func(t *testing.T) {
-		t.Parallel()
-		key, err := jwxtest.GenerateEcdsaKey(jwa.Secp256k1)
-		if !assert.NoError(t, err, "ECDSA key generated") {
-			return
-		}
-		jwkKey, _ := jwk.New(key.PublicKey)
-		keys := map[string]interface{}{
-			"Verify(ecdsa.PublicKey)":  key.PublicKey,
-			"Verify(*ecdsa.PublicKey)": &key.PublicKey,
-			"Verify(jwk.Key)":          jwkKey,
-		}
-		testRoundtrip(t, payload, jwa.ES256K, key, keys)
-	})
 	t.Run("RSA", func(t *testing.T) {
 		t.Parallel()
 		key, err := jwxtest.GenerateRsaKey()
diff --git a/jws/secp256k1_test.go b/jws/secp256k1_test.go
new file mode 100644
index 00000000..5bf186ef
--- /dev/null
+++ b/jws/secp256k1_test.go
@@ -0,0 +1,29 @@
+// +build jwx_es256k
+
+package jws_test
+
+import (
+	"testing"
+
+	"github.com/lestrrat-go/jwx/internal/jwxtest"
+	"github.com/lestrrat-go/jwx/jwa"
+	"github.com/lestrrat-go/jwx/jwk"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestES256K(t *testing.T) {
+	payload := []byte("Hello, World!")
+
+	t.Parallel()
+	key, err := jwxtest.GenerateEcdsaKey(jwa.Secp256k1)
+	if !assert.NoError(t, err, "ECDSA key generated") {
+		return
+	}
+	jwkKey, _ := jwk.New(key.PublicKey)
+	keys := map[string]interface{}{
+		"Verify(ecdsa.PublicKey)":  key.PublicKey,
+		"Verify(*ecdsa.PublicKey)": &key.PublicKey,
+		"Verify(jwk.Key)":          jwkKey,
+	}
+	testRoundtrip(t, payload, jwa.ES256K, key, keys)
+}
diff --git a/jwx_test.go b/jwx_test.go
index bdb0c6a7..49b29986 100644
--- a/jwx_test.go
+++ b/jwx_test.go
@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	"github.com/lestrrat-go/jwx"
+	"github.com/lestrrat-go/jwx/internal/ecutil"
 	"github.com/lestrrat-go/jwx/internal/jose"
 	"github.com/lestrrat-go/jwx/internal/json"
 	"github.com/lestrrat-go/jwx/internal/jwxtest"
@@ -18,8 +19,12 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestJSONEngine(t *testing.T) {
+func TestShowBuildInfo(t *testing.T) {
 	t.Logf("Running tests using JSON backend => %s\n", json.Engine())
+	t.Logf("Available elliptic curves:")
+	for _, alg := range ecutil.AvailableAlgorithms() {
+		t.Logf("  %s", alg)
+	}
 }
 
 type jsonUnmarshalWrapper struct {