Skip to content

Commit

Permalink
Support secp256k1 (#89)
Browse files Browse the repository at this point in the history
* Support secp256k1

* enable compile flag in workflow
  • Loading branch information
reinkrul authored Oct 31, 2023
1 parent f4f4558 commit 4c1e711
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 11 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ jobs:
- name: Display Go version
run: go version
- name: Build
run: go build -v ./...
run: go build --tags=jwx_es256k -v ./...
- name: Test
run: go test -v ./...
run: go test --tags=jwx_es256k -v ./...
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ Outputs:
The library supports parsing of Verifiable Credentials and Verifiable Presentations in JSON-LD, and JWT proof format.
Use `ParseVerifiableCredential(raw string)` and `ParseVerifiablePresentation(raw string)`.

## Supported key types

- `JsonWebKey2020`
- `Ed25519VerificationKey2018`
- `EcdsaSecp256k1VerificationKey2019` (pass build tag to enable: `--tags=jwx_es256k`)

## Installation
```
go get github.com/nuts-foundation/go-did
Expand Down
37 changes: 36 additions & 1 deletion did/document.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package did

import (
"context"
"crypto"
"crypto/ed25519"
"encoding/json"
"errors"
"fmt"
"github.com/lestrrat-go/jwx/jwa"
"github.com/multiformats/go-multibase"

"github.com/nuts-foundation/go-did"

"github.com/lestrrat-go/jwx/jwk"
Expand All @@ -16,6 +17,9 @@ import (
"github.com/nuts-foundation/go-did/internal/marshal"
)

// lestrrat-go/jwx requires secp256k1 support to be enabled compile-time
var errP256k1NotSupported = errors.New("secp256k1 support is not enabled")

// ParseDocument parses a DID Document from a string.
func ParseDocument(raw string) (*Document, error) {
type Alias Document
Expand Down Expand Up @@ -351,6 +355,20 @@ func NewVerificationMethod(id DID, keyType ssi.KeyType, controller DID, key cryp

vm.PublicKeyJwk = keyAsMap
}
if keyType == ssi.ECDSASECP256K1VerificationKey2019 {
if !secp256k1Supported() {
return nil, errP256k1NotSupported
}
keyAsJWK, err := jwk.New(key)
if err != nil {
return nil, err
}
jwkAsMap, err := keyAsJWK.AsMap(context.Background())
if err != nil {
return nil, err
}
vm.PublicKeyJwk = jwkAsMap
}
if keyType == ssi.ED25519VerificationKey2018 {
ed25519Key, ok := key.(ed25519.PublicKey)
if !ok {
Expand Down Expand Up @@ -399,6 +417,14 @@ func (v VerificationMethod) PublicKey() (crypto.PublicKey, error) {
return nil, errors.New("expected either publicKeyMultibase or publicKeyBase58 to be set")
}
return ed25519.PublicKey(keyBytes), err
case ssi.ECDSASECP256K1VerificationKey2019:
if !secp256k1Supported() {
return nil, errP256k1NotSupported
}
if v.PublicKeyJwk == nil {
return nil, errors.New("missing publicKeyJwk")
}
fallthrough
case ssi.JsonWebKey2020:
keyAsJWK, err := v.JWK()
if err != nil {
Expand Down Expand Up @@ -497,3 +523,12 @@ func resolveVerificationRelationship(reference DID, methods []*VerificationMetho
}
return nil
}

func secp256k1Supported() bool {
for _, alg := range jwa.EllipticCurveAlgorithms() {
if alg.String() == "secp256k1" {
return true
}
}
return false
}
56 changes: 48 additions & 8 deletions did/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"encoding/json"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
ssi "github.com/nuts-foundation/go-did"
"github.com/stretchr/testify/require"
"testing"
Expand Down Expand Up @@ -124,19 +125,58 @@ func Test_Document(t *testing.T) {
t.Logf("resulting json:\n%s", didJson)
})

t.Run("it can add assertionMethods with ED25519VerificationKey2018", func(t *testing.T) {
t.Run("ED25519VerificationKey2018", func(t *testing.T) {
id := actual.ID
id.Fragment = "added-assertion-method-1"
id.Fragment = "1"

pubKey, _, _ := ed25519.GenerateKey(rand.Reader)
vm, err := NewVerificationMethod(id, ssi.ED25519VerificationKey2018, actual.ID, pubKey)
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)

actual.AddAssertionMethod(vm)
didJson, _ := json.MarshalIndent(actual, "", " ")
t.Logf("resulting json:\n%s", didJson)
publicKey, err := vm.PublicKey()
require.NoError(t, err)
require.NotNil(t, publicKey)
})

t.Run("ECDSASECP256K1VerificationKey2019", func(t *testing.T) {
t.Run("generated key", func(t *testing.T) {
id := actual.ID
id.Fragment = "1"
privateKey, err := secp256k1.GeneratePrivateKey()
require.NoError(t, err)

vm, err := NewVerificationMethod(id, ssi.ECDSASECP256K1VerificationKey2019, actual.ID, privateKey.ToECDSA())
require.NoError(t, err)

publicKey, err := vm.PublicKey()
require.NoError(t, err)
require.NotNil(t, publicKey)
asJWK, err := vm.JWK()
require.NoError(t, err)
require.NotNil(t, asJWK)
})
t.Run("static", func(t *testing.T) {
// copied from an online did:web source
const asJSON = `{
"controller": "did:web:example.com",
"id": "did:web:example.com#xCPeUKv-0t4TPSlRnk61AqIK-DtH-riOvyx_Udk65XA",
"publicKeyJwk": {
"kty": "EC",
"x": "P_kEHfyV27kg5oxn1pzTTDAkNKqsH9QdfdADcKLlr4Y",
"y": "GE0tU43W30xT-DKmF75uWCWSnXid3kKhnYZbdWhiguE",
"crv": "secp256k1",
"kid": "did:web:example.com#xCPeUKv-0t4TPSlRnk61AqIK-DtH-riOvyx_Udk65XA"
},
"type": "EcdsaSecp256k1VerificationKey2019"
}`
vm := VerificationMethod{}
err := json.Unmarshal([]byte(asJSON), &vm)
require.NoError(t, err)

publicKey, err := vm.PublicKey()
require.NoError(t, err)
require.NotNil(t, publicKey)
})
})

t.Run("it can parse a jwk in a verification method", func(t *testing.T) {
Expand Down

0 comments on commit 4c1e711

Please sign in to comment.