From eca71e594d9f4878c2aa61747ffbc7f2ed8ff62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Wed, 9 Apr 2025 14:16:59 +0200 Subject: [PATCH] dev guidelines, some cleanup --- design.md | 36 +++++++++++++++++++ interfaces.go | 9 ++++- {did-key => methods/did-key}/document.go | 5 ++- {did-key => methods/did-key}/document_test.go | 2 ++ {did-key => methods/did-key}/key.go | 28 ++++++++++----- {did-key => methods/did-key}/key_test.go | 14 +------- .../ed25519/VerificationKey2020_test.go | 2 +- 7 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 design.md rename {did-key => methods/did-key}/document.go (96%) rename {did-key => methods/did-key}/document_test.go (97%) rename {did-key => methods/did-key}/key.go (75%) rename {did-key => methods/did-key}/key_test.go (73%) diff --git a/design.md b/design.md new file mode 100644 index 0000000..e28af8b --- /dev/null +++ b/design.md @@ -0,0 +1,36 @@ +## Development guidelines + +See [Readme.md](Readme.md) as well. + +General: +- coding style should be clean, straightforward and documented, in a similar fashion as go-ucan. +- keep the dependencies minimal, favor the standard go libraries +- code should be decently tested and profiled +- specifications and test vectors used MUST be referenced in a comment +- if something differs from a specification, it should be documented and explained +- generally, follow the existing structure of did:key, ed25519 or x25519 +- consider how an average user will read and understand your code, rather than how you read it + +DIDs: +- DID and document structs are minimal/lightweight and get expanded into the relevant interface (DID, Document). +- They get flattened when marshalling into JSON but not otherwise. DID Documents are for out-of-process communication, not the normal path. +- this library should also have a generic Document struct, to accept arbitrary DID documents in JSON format + +Crypto: +- each type of crypto handling should be self-contained in the relevant verification method package (e.g. everything ed25519 is in /verifications/ed25519). This includes the JSON (un)marshalling of the VerificationMethod. +- a user of the library shouldn't have to know or care about the underlying crypto to use it "server side" (signature verification, key agreement). Thus, it should be abstracted behind the VerificationMethod interfaces. +- for the same reason, each of those packages should expose or alias the relevant types (ex: PublicKey/PrivateKey in /verifications/ed25519) to expose a regular way to work with crypto primitives, as well as allowing behind the scene upgrades. +- for each, we should expose some generally useful functions to handle private keys (generation, marshalling...) + +## Minimal target features + +Methods: +- did:key +- did:pkh + +Verification Methods: +- ed25519 +- x25519 +- secp256k1 +- p256 +- p384 \ No newline at end of file diff --git a/interfaces.go b/interfaces.go index 9cc4418..8af60fc 100644 --- a/interfaces.go +++ b/interfaces.go @@ -5,9 +5,11 @@ import ( "net/url" ) -// DID is a decoded (i.e. from a string) Decentralized Identifiers. +// DID is a decoded (i.e. from a string) Decentralized Identifier. type DID interface { Method() string + + // TODO: below might be only for DID URLs, is it relevant here? Path() string Query() url.Values Fragment() string @@ -15,6 +17,11 @@ type DID interface { Document() (Document, error) String() string // return the full DID URL, with path, query, fragment + // ResolutionIsExpensive returns true if resolving to a Document is an expensive operation, e.g. requiring + // an external HTTP request. By contrast, a self-contained DID (e.g. did:key) can be resolved cheaply without + // an external call. This can be an indication whether to cache the resolved state. + ResolutionIsExpensive() bool + Equal(DID) bool } diff --git a/did-key/document.go b/methods/did-key/document.go similarity index 96% rename from did-key/document.go rename to methods/did-key/document.go index 6aac9c5..2f2bf6e 100644 --- a/did-key/document.go +++ b/methods/did-key/document.go @@ -35,7 +35,6 @@ func (d document) MarshalJSON() ([]byte, error) { ), ID: d.id.String(), AlsoKnownAs: nil, - Controller: d.id.String(), VerificationMethod: []did.VerificationMethod{d.signature, d.keyAgreement}, Authentication: []string{d.signature.ID()}, AssertionMethod: []string{d.signature.ID()}, @@ -50,8 +49,8 @@ func (d document) ID() did.DID { } func (d document) Controllers() []did.DID { - // no external controller possible for did:key - return []did.DID{d.id} + // no controller for did:key, no changes are possible + return nil } func (d document) AlsoKnownAs() []url.URL { diff --git a/did-key/document_test.go b/methods/did-key/document_test.go similarity index 97% rename from did-key/document_test.go rename to methods/did-key/document_test.go index 404447d..ee7172d 100644 --- a/did-key/document_test.go +++ b/methods/did-key/document_test.go @@ -23,6 +23,8 @@ func TestDocument(t *testing.T) { fmt.Println(string(bytes)) + // TODO: https://github.com/w3c-ccg/did-key-spec/issues/71 + const expected = `{ "@context": [ "https://www.w3.org/ns/did/v1", diff --git a/did-key/key.go b/methods/did-key/key.go similarity index 75% rename from did-key/key.go rename to methods/did-key/key.go index a50e0d6..c6bc053 100644 --- a/did-key/key.go +++ b/methods/did-key/key.go @@ -23,7 +23,7 @@ func init() { var _ did.DID = &DidKey{} type DidKey struct { - identifier string // cached value + msi string // method-specific identifier, i.e. "12345" in "did:key:12345" signature did.VerificationMethodSignature keyAgreement did.VerificationMethodKeyAgreement } @@ -35,7 +35,9 @@ func Decode(identifier string) (did.DID, error) { return nil, fmt.Errorf("must start with 'did:key'") } - baseCodec, bytes, err := mbase.Decode(identifier[len(keyPrefix):]) + msi := identifier[len(keyPrefix):] + + baseCodec, bytes, err := mbase.Decode(msi) if err != nil { return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) } @@ -48,11 +50,11 @@ func Decode(identifier string) (did.DID, error) { return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) } - d := DidKey{identifier: identifier} + d := DidKey{msi: msi} switch code { case ed25519.MultibaseCode: - d.signature, err = ed25519.NewVerificationKey2020(d.identifier, bytes[read:], d) + d.signature, err = ed25519.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", msi, msi), bytes[read:], d) if err != nil { return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) } @@ -60,7 +62,7 @@ func Decode(identifier string) (did.DID, error) { if err != nil { return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) } - d.keyAgreement, err = x25519.NewKeyAgreementKey2020(d.identifier, xpub, d) + d.keyAgreement, err = x25519.NewKeyAgreementKey2020("TODO", xpub, d) if err != nil { return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) } @@ -80,9 +82,9 @@ func FromPublicKey(pub PublicKey) (did.DID, error) { switch pub := pub.(type) { case ed25519.PublicKey: d := DidKey{ - identifier: ed25519.PublicKeyToMultibase(pub), + msi: ed25519.PublicKeyToMultibase(pub), } - d.signature, err = ed25519.NewVerificationKey2020(d.identifier, pub, d) + d.signature, err = ed25519.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d) if err != nil { return nil, err } @@ -122,18 +124,26 @@ func (d DidKey) Document() (did.Document, error) { } func (d DidKey) String() string { - return d.identifier + return fmt.Sprintf("did:key:%s", d.msi) +} + +func (d DidKey) ResolutionIsExpensive() bool { + return false } func (d DidKey) Equal(d2 did.DID) bool { if d2, ok := d2.(DidKey); ok { - return d.identifier == d2.identifier + return d.msi == d2.msi } 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 } diff --git a/did-key/key_test.go b/methods/did-key/key_test.go similarity index 73% rename from did-key/key_test.go rename to methods/did-key/key_test.go index afb9e0e..3458af7 100644 --- a/did-key/key_test.go +++ b/methods/did-key/key_test.go @@ -1,14 +1,12 @@ package didkey_test import ( - "fmt" "testing" - mbase "github.com/multiformats/go-multibase" "github.com/stretchr/testify/require" "github.com/INFURA/go-did" - _ "github.com/INFURA/go-did/did-key" + _ "github.com/INFURA/go-did/methods/did-key" ) func TestParseDIDKey(t *testing.T) { @@ -41,13 +39,3 @@ func TestEquivalence(t *testing.T) { require.True(t, did0A.Equal(did0B)) require.False(t, did0A.Equal(did1)) } - -func TestFoo(t *testing.T) { - _, bytes, err := mbase.Decode("z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p") - require.NoError(t, err) - fmt.Println(bytes) - - _, bytes, err = mbase.Decode("z6LSinnAscp9HxuNE9QCpqiwmSMP7oHiTzLepseUb4y6LHFB") - require.NoError(t, err) - fmt.Println(bytes) -} diff --git a/verifications/ed25519/VerificationKey2020_test.go b/verifications/ed25519/VerificationKey2020_test.go index b98ec32..7c15e97 100644 --- a/verifications/ed25519/VerificationKey2020_test.go +++ b/verifications/ed25519/VerificationKey2020_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - _ "github.com/INFURA/go-did/did-key" + _ "github.com/INFURA/go-did/methods/did-key" "github.com/INFURA/go-did/verifications/ed25519" )