dev guidelines, some cleanup

This commit is contained in:
Michael Muré
2025-04-09 14:16:59 +02:00
parent da87ebd5e8
commit eca71e594d
7 changed files with 69 additions and 27 deletions

36
design.md Normal file
View File

@@ -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

View File

@@ -5,9 +5,11 @@ import (
"net/url" "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 { type DID interface {
Method() string Method() string
// TODO: below might be only for DID URLs, is it relevant here?
Path() string Path() string
Query() url.Values Query() url.Values
Fragment() string Fragment() string
@@ -15,6 +17,11 @@ type DID interface {
Document() (Document, error) Document() (Document, error)
String() string // return the full DID URL, with path, query, fragment 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 Equal(DID) bool
} }

View File

@@ -35,7 +35,6 @@ func (d document) MarshalJSON() ([]byte, error) {
), ),
ID: d.id.String(), ID: d.id.String(),
AlsoKnownAs: nil, AlsoKnownAs: nil,
Controller: d.id.String(),
VerificationMethod: []did.VerificationMethod{d.signature, d.keyAgreement}, VerificationMethod: []did.VerificationMethod{d.signature, d.keyAgreement},
Authentication: []string{d.signature.ID()}, Authentication: []string{d.signature.ID()},
AssertionMethod: []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 { func (d document) Controllers() []did.DID {
// no external controller possible for did:key // no controller for did:key, no changes are possible
return []did.DID{d.id} return nil
} }
func (d document) AlsoKnownAs() []url.URL { func (d document) AlsoKnownAs() []url.URL {

View File

@@ -23,6 +23,8 @@ func TestDocument(t *testing.T) {
fmt.Println(string(bytes)) fmt.Println(string(bytes))
// TODO: https://github.com/w3c-ccg/did-key-spec/issues/71
const expected = `{ const expected = `{
"@context": [ "@context": [
"https://www.w3.org/ns/did/v1", "https://www.w3.org/ns/did/v1",

View File

@@ -23,7 +23,7 @@ func init() {
var _ did.DID = &DidKey{} var _ did.DID = &DidKey{}
type DidKey struct { type DidKey struct {
identifier string // cached value msi string // method-specific identifier, i.e. "12345" in "did:key:12345"
signature did.VerificationMethodSignature signature did.VerificationMethodSignature
keyAgreement did.VerificationMethodKeyAgreement keyAgreement did.VerificationMethodKeyAgreement
} }
@@ -35,7 +35,9 @@ func Decode(identifier string) (did.DID, error) {
return nil, fmt.Errorf("must start with 'did:key'") 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 { if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) 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) return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
} }
d := DidKey{identifier: identifier} d := DidKey{msi: msi}
switch code { switch code {
case ed25519.MultibaseCode: 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 { if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
} }
@@ -60,7 +62,7 @@ func Decode(identifier string) (did.DID, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) 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 { if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err) return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
} }
@@ -80,9 +82,9 @@ func FromPublicKey(pub PublicKey) (did.DID, error) {
switch pub := pub.(type) { switch pub := pub.(type) {
case ed25519.PublicKey: case ed25519.PublicKey:
d := DidKey{ 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 { if err != nil {
return nil, err return nil, err
} }
@@ -122,18 +124,26 @@ func (d DidKey) Document() (did.Document, error) {
} }
func (d DidKey) String() string { 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 { func (d DidKey) Equal(d2 did.DID) bool {
if d2, ok := d2.(DidKey); ok { if d2, ok := d2.(DidKey); ok {
return d.identifier == d2.identifier return d.msi == d2.msi
} }
return false 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 { type PublicKey interface {
Equal(x crypto.PublicKey) bool Equal(x crypto.PublicKey) bool
} }

View File

@@ -1,14 +1,12 @@
package didkey_test package didkey_test
import ( import (
"fmt"
"testing" "testing"
mbase "github.com/multiformats/go-multibase"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/INFURA/go-did" "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) { func TestParseDIDKey(t *testing.T) {
@@ -41,13 +39,3 @@ func TestEquivalence(t *testing.T) {
require.True(t, did0A.Equal(did0B)) require.True(t, did0A.Equal(did0B))
require.False(t, did0A.Equal(did1)) 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)
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/require" "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" "github.com/INFURA/go-did/verifications/ed25519"
) )