Merge pull request #6 from INFURA/usage
Add examples and refine usage (key agreement in particular)
This commit is contained in:
57
did_test.go
57
did_test.go
@@ -1,11 +1,58 @@
|
||||
package did
|
||||
package did_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
_ "github.com/INFURA/go-did/methods/did-key"
|
||||
"github.com/INFURA/go-did/verifications/x25519"
|
||||
)
|
||||
|
||||
func ExampleSignature() {
|
||||
// errors need to be handled
|
||||
|
||||
// 1) Parse the DID string into a DID object
|
||||
d, _ := did.Parse("did:key:z6MknwcywUtTy2ADJQ8FH1GcSySKPyKDmyzT4rPEE84XREse")
|
||||
|
||||
// 2) Resolve to the DID Document
|
||||
doc, _ := d.Document()
|
||||
|
||||
// 3) Use the appropriate verification method (ex: verify a signature for authentication purpose)
|
||||
sig, _ := base64.StdEncoding.DecodeString("nhpkr5a7juUM2eDpDRSJVdEE++0SYqaZXHtuvyafVFUx8zsOdDSrij+vHmd/ARwUOmi/ysmSD+b3K9WTBtmmBQ==")
|
||||
if ok, method := did.TryAllVerify(doc.Authentication(), []byte("message"), sig); ok {
|
||||
fmt.Println("Signature is valid, verified with method:", method.Type(), method.ID())
|
||||
} else {
|
||||
fmt.Println("Signature is invalid")
|
||||
}
|
||||
|
||||
// Output: Signature is valid, verified with method: Ed25519VerificationKey2020 did:key:z6MknwcywUtTy2ADJQ8FH1GcSySKPyKDmyzT4rPEE84XREse#z6MknwcywUtTy2ADJQ8FH1GcSySKPyKDmyzT4rPEE84XREse
|
||||
}
|
||||
|
||||
func ExampleKeyAgreement() {
|
||||
// errors need to be handled
|
||||
|
||||
// 1) We have a private key for Alice
|
||||
privAliceBytes, _ := base64.StdEncoding.DecodeString("fNOf3xWjFZYGYWixorM5+JR+u/2Udnc9Zw5+9rSvjqo=")
|
||||
privAlice, _ := x25519.PrivateKeyFromBytes(privAliceBytes)
|
||||
|
||||
// 2) We resolve the DID Document for Bob
|
||||
dBob, _ := did.Parse("did:key:z6MkgRNXpJRbEE6FoXhT8KWHwJo4KyzFo1FdSEFpRLh5vuXZ")
|
||||
docBob, _ := dBob.Document()
|
||||
|
||||
// 3) We perform the key agreement
|
||||
key, method, _ := did.FindMatchingKeyAgreement(docBob.KeyAgreement(), privAlice)
|
||||
|
||||
fmt.Println("Shared key:", base64.StdEncoding.EncodeToString(key))
|
||||
fmt.Println("Verification method used:", method.Type(), method.ID())
|
||||
|
||||
// Output: Shared key: 7G1qwS/gn5W1hxBtObHc3F0jA7m2vuXkLJJ32yBuHVQ=
|
||||
// Verification method used: X25519KeyAgreementKey2020 did:key:z6MkgRNXpJRbEE6FoXhT8KWHwJo4KyzFo1FdSEFpRLh5vuXZ#z6LSjeQx2VkXz8yirhrYJv8uicu9BBaeYU3Q1D9sFBovhmPF
|
||||
}
|
||||
|
||||
func TestHasValidDIDSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -38,7 +85,7 @@ func TestHasValidDIDSyntax(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
require.Equal(t, tt.expected, HasValidDIDSyntax(tt.input))
|
||||
require.Equal(t, tt.expected, did.HasValidDIDSyntax(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -46,7 +93,7 @@ func TestHasValidDIDSyntax(t *testing.T) {
|
||||
func BenchmarkHasValidDIDSyntax(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
HasValidDIDSyntax("did:example:abc:def:ghi:jkl%20mno%3Apqr%3Astuv")
|
||||
did.HasValidDIDSyntax("did:example:abc:def:ghi:jkl%20mno%3Apqr%3Astuv")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +125,7 @@ func TestHasValidDidUrlSyntax(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.expected, HasValidDidUrlSyntax(tt.input))
|
||||
require.Equal(t, tt.expected, did.HasValidDidUrlSyntax(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -86,6 +133,6 @@ func TestHasValidDidUrlSyntax(t *testing.T) {
|
||||
func BenchmarkHasValidDidUrlSyntax(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
HasValidDidUrlSyntax("did:example:123456789abcdefghi/path/to/resource?key=value#section1")
|
||||
did.HasValidDidUrlSyntax("did:example:123456789abcdefghi/path/to/resource?key=value#section1")
|
||||
}
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -8,7 +8,6 @@ require (
|
||||
github.com/multiformats/go-multibase v0.2.0
|
||||
github.com/multiformats/go-varint v0.0.7
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
2
go.sum
2
go.sum
@@ -14,8 +14,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package did
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
)
|
||||
@@ -108,5 +109,21 @@ type VerificationMethodSignature interface {
|
||||
type VerificationMethodKeyAgreement interface {
|
||||
VerificationMethod
|
||||
|
||||
// TODO: function for key agreement
|
||||
// PrivateKeyIsCompatible checks that the given PrivateKey is compatible with this method.
|
||||
PrivateKeyIsCompatible(local PrivateKey) bool
|
||||
|
||||
// ECDH computes the shared key using the given PrivateKey.
|
||||
ECDH(local PrivateKey) ([]byte, error)
|
||||
}
|
||||
|
||||
// Below are the interfaces for crypto.PublicKey and crypto.PrivateKey in the go standard library.
|
||||
// They are not defined there for compatibility reasons, so we need to define them here.
|
||||
|
||||
type PublicKey interface {
|
||||
Equal(x crypto.PublicKey) bool
|
||||
}
|
||||
|
||||
type PrivateKey interface {
|
||||
Public() crypto.PublicKey
|
||||
Equal(x crypto.PrivateKey) bool
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type document struct {
|
||||
|
||||
func (d document) MarshalJSON() ([]byte, error) {
|
||||
// It's unclear where the KeyAgreement should be.
|
||||
// Maybe it doesn't matter, but the spec contradict itself.
|
||||
// Maybe it doesn't matter, but the spec contradicts itself.
|
||||
// See https://github.com/w3c-ccg/did-key-spec/issues/71
|
||||
|
||||
return json.Marshal(struct {
|
||||
|
||||
@@ -2,7 +2,6 @@ package didkey
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -20,8 +19,6 @@ func TestDocument(t *testing.T) {
|
||||
bytes, err := json.MarshalIndent(doc, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Println(string(bytes))
|
||||
|
||||
// TODO: https://github.com/w3c-ccg/did-key-spec/issues/71
|
||||
|
||||
const expected = `{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package didkey
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -65,7 +64,7 @@ func Decode(identifier string) (did.DID, error) {
|
||||
return nil, fmt.Errorf("%w: unsupported did:key multicodec: 0x%x", did.ErrInvalidDid, code)
|
||||
}
|
||||
|
||||
func FromPublicKey(pub PublicKey) (did.DID, error) {
|
||||
func FromPublicKey(pub did.PublicKey) (did.DID, error) {
|
||||
var err error
|
||||
switch pub := pub.(type) {
|
||||
case ed25519.PublicKey:
|
||||
@@ -90,8 +89,8 @@ func FromPublicKey(pub PublicKey) (did.DID, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func FromPrivateKey(priv PrivateKey) (did.DID, error) {
|
||||
return FromPublicKey(priv.Public().(PublicKey))
|
||||
func FromPrivateKey(priv did.PrivateKey) (did.DID, error) {
|
||||
return FromPublicKey(priv.Public().(did.PublicKey))
|
||||
}
|
||||
|
||||
func (d DidKey) Method() string {
|
||||
@@ -120,18 +119,3 @@ func (d DidKey) Equal(d2 did.DID) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---------------
|
||||
|
||||
// Below are the interfaces for crypto.PublicKey and crypto.PrivateKey in the go standard library.
|
||||
// They are not actually defined there for compatibility reasons.
|
||||
// They are useful for did:key, it's unclear if it's useful elsewhere.
|
||||
|
||||
type PublicKey interface {
|
||||
Equal(x crypto.PublicKey) bool
|
||||
}
|
||||
|
||||
type PrivateKey interface {
|
||||
Public() crypto.PublicKey
|
||||
Equal(x crypto.PrivateKey) bool
|
||||
}
|
||||
|
||||
@@ -1,14 +1,47 @@
|
||||
package didkey_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
_ "github.com/INFURA/go-did/methods/did-key"
|
||||
didkey "github.com/INFURA/go-did/methods/did-key"
|
||||
"github.com/INFURA/go-did/verifications/ed25519"
|
||||
)
|
||||
|
||||
func ExampleGenerateKeyPair() {
|
||||
// Generate a key pair
|
||||
pub, priv, err := ed25519.GenerateKeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Public key:", ed25519.PublicKeyToMultibase(pub))
|
||||
fmt.Println("Private key:", base64.StdEncoding.EncodeToString(priv))
|
||||
|
||||
// Make the associated did:key
|
||||
dk, err := didkey.FromPrivateKey(priv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Did:", dk.String())
|
||||
|
||||
// Produce a signature
|
||||
msg := []byte("message")
|
||||
sig := ed25519.Sign(priv, msg)
|
||||
fmt.Println("Signature:", base64.StdEncoding.EncodeToString(sig))
|
||||
|
||||
// Resolve the DID and verify a signature
|
||||
doc, err := dk.Document()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ok, _ := did.TryAllVerify(doc.Authentication(), msg, sig)
|
||||
fmt.Println("Signature verified:", ok)
|
||||
}
|
||||
|
||||
func TestParseDIDKey(t *testing.T) {
|
||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
d, err := did.Parse(str)
|
||||
|
||||
30
utilities.go
Normal file
30
utilities.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package did
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TryAllVerify tries to verify the signature with all the methods in the slice.
|
||||
// It returns true if the signature is verified, and the method that verified it.
|
||||
// If no method verifies the signature, it returns false and nil.
|
||||
func TryAllVerify(methods []VerificationMethodSignature, data []byte, sig []byte) (bool, VerificationMethodSignature) {
|
||||
for _, method := range methods {
|
||||
if method.Verify(data, sig) {
|
||||
return true, method
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// FindMatchingKeyAgreement tries to find a matching key agreement method for the given private key type.
|
||||
// It returns the shared key as well as the selected method.
|
||||
// If no matching method is found, it returns an error.
|
||||
func FindMatchingKeyAgreement(methods []VerificationMethodKeyAgreement, priv PrivateKey) ([]byte, VerificationMethodKeyAgreement, error) {
|
||||
for _, method := range methods {
|
||||
if method.PrivateKeyIsCompatible(priv) {
|
||||
key, err := method.ECDH(priv)
|
||||
return key, method, err
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("no matching key agreement found")
|
||||
}
|
||||
@@ -12,12 +12,21 @@ import (
|
||||
type PublicKey = ed25519.PublicKey
|
||||
type PrivateKey = ed25519.PrivateKey
|
||||
|
||||
const PublicKeySize = ed25519.PublicKeySize
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = ed25519.PublicKeySize
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = ed25519.PrivateKeySize
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = ed25519.SignatureSize
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
return ed25519.GenerateKey(rand.Reader)
|
||||
}
|
||||
|
||||
// PublicKeyFromBytes convert a serialized public key to a PublicKey.
|
||||
// It errors if the slice is not the right size.
|
||||
func PublicKeyFromBytes(b []byte) (PublicKey, error) {
|
||||
if len(b) != PublicKeySize {
|
||||
return nil, fmt.Errorf("invalid ed25519 public key size")
|
||||
@@ -57,3 +66,18 @@ func PublicKeyToMultibase(pub PublicKey) string {
|
||||
bytes, _ := mbase.Encode(mbase.Base58BTC, append(varint.ToUvarint(MultibaseCode), pub...))
|
||||
return bytes
|
||||
}
|
||||
|
||||
// PrivateKeyFromBytes convert a serialized public key to a PrivateKey.
|
||||
// It errors if the slice is not the right size.
|
||||
func PrivateKeyFromBytes(b []byte) (PrivateKey, error) {
|
||||
if len(b) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("invalid ed25519 private key size")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature.
|
||||
// It will panic if len(privateKey) is not [PrivateKeySize].
|
||||
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||
return ed25519.Sign(privateKey, message)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,21 @@ func (k KeyAgreementKey2020) JsonLdContext() string {
|
||||
return JsonLdContext
|
||||
}
|
||||
|
||||
// TODO: make it part of did.VerificationMethodKeyAgreement in some way
|
||||
func (k KeyAgreementKey2020) KeyAgreement(priv PrivateKey) ([]byte, error) {
|
||||
return priv.ECDH(k.pubkey)
|
||||
func (k KeyAgreementKey2020) PrivateKeyIsCompatible(local did.PrivateKey) bool {
|
||||
_, ok := local.(PrivateKey)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (k KeyAgreementKey2020) ECDH(local did.PrivateKey) ([]byte, error) {
|
||||
cast, ok := local.(PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("private key type doesn't match the public key type")
|
||||
}
|
||||
if cast == nil {
|
||||
return nil, errors.New("invalid private key")
|
||||
}
|
||||
if k.pubkey.Curve() != cast.Curve() {
|
||||
return nil, errors.New("key curves don't match")
|
||||
}
|
||||
return cast.ECDH(k.pubkey)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,14 @@ import (
|
||||
type PublicKey = *ecdh.PublicKey
|
||||
type PrivateKey = *ecdh.PrivateKey
|
||||
|
||||
const PublicKeySize = 32
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = 32
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = 32
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = 32
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
@@ -25,6 +32,8 @@ func GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
return priv.Public().(PublicKey), priv, nil
|
||||
}
|
||||
|
||||
// PublicKeyFromBytes convert a serialized public key to a PublicKey.
|
||||
// It errors if the slice is not the right size.
|
||||
func PublicKeyFromBytes(b []byte) (PublicKey, error) {
|
||||
return ecdh.X25519().NewPublicKey(b)
|
||||
}
|
||||
@@ -115,6 +124,12 @@ func PublicKeyToMultibase(pub PublicKey) string {
|
||||
return bytes
|
||||
}
|
||||
|
||||
// PrivateKeyFromBytes convert a serialized public key to a PrivateKey.
|
||||
// It errors if len(privateKey) is not [PrivateKeySize].
|
||||
func PrivateKeyFromBytes(b []byte) (PrivateKey, error) {
|
||||
return ecdh.X25519().NewPrivateKey(b)
|
||||
}
|
||||
|
||||
func reverseBytes(b []byte) []byte {
|
||||
r := make([]byte, len(b))
|
||||
for i := 0; i < len(b); i++ {
|
||||
|
||||
Reference in New Issue
Block a user