Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support secp256k1 #89

Merged
merged 2 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -73,6 +73,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