From 23c2b1f459835795743b9469f2b59874bf505ff7 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 13 Sep 2023 22:32:07 +0100 Subject: [PATCH 1/4] first commit --- did.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ did_test.go | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 did.go create mode 100644 did_test.go diff --git a/did.go b/did.go new file mode 100644 index 0000000..b093ce4 --- /dev/null +++ b/did.go @@ -0,0 +1,72 @@ +package did + +import ( + "fmt" + "strings" + + mbase "github.com/multiformats/go-multibase" + varint "github.com/multiformats/go-varint" +) + +const Prefix = "did:" +const KeyPrefix = "did:key:" + +const Ed25519 = 0xed + +type DID struct { + str string +} + +// Undef can be used to represent a nil or undefined DID, using DID{} +// directly is also acceptable. +var Undef = DID{} + +func (d DID) Defined() bool { + return d.str != "" +} + +func (d DID) Bytes() []byte { + if !d.Defined() { + return nil + } + return []byte(d.str) +} + +func (d DID) DID() DID { + return d +} + +func (d DID) String() string { + key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str)) + return "did:key:" + key +} + +func Decode(bytes []byte) (DID, error) { + code, _, err := varint.FromUvarint(bytes) + if err != nil { + return Undef, err + } + if code == Ed25519 { + return DID{str: string(bytes)}, nil + } + return Undef, fmt.Errorf("decoding DID: unsupported DID encoding: 0x%x", code) +} + +func Parse(str string) (DID, error) { + if !strings.HasPrefix(str, Prefix) { + return Undef, fmt.Errorf("parsing DID: must start with 'did:'") + } + + if strings.HasPrefix(str, KeyPrefix) { + code, bytes, err := mbase.Decode(str[len(KeyPrefix):]) + if err != nil { + return Undef, err + } + if code != mbase.Base58BTC { + return Undef, fmt.Errorf("parsing DID key: not Base58BTC encoded") + } + return Decode(bytes) + } + + return Undef, fmt.Errorf("parsing DID: unsupported method") +} diff --git a/did_test.go b/did_test.go new file mode 100644 index 0000000..720e733 --- /dev/null +++ b/did_test.go @@ -0,0 +1,53 @@ +package did + +import ( + "testing" +) + +func TestParseDIDKey(t *testing.T) { + str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z" + d, err := Parse(str) + if err != nil { + t.Fatalf("%v", err) + } + if d.String() != str { + t.Fatalf("expected %v to equal %v", d.String(), str) + } +} + +func TestDecodeDIDKey(t *testing.T) { + str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z" + d0, err := Parse(str) + if err != nil { + t.Fatalf("%v", err) + } + d1, err := Decode(d0.Bytes()) + if err != nil { + t.Fatalf("%v", err) + } + if d1.String() != str { + t.Fatalf("expected %v to equal %v", d1.String(), str) + } +} + +func TestEquivalence(t *testing.T) { + u0 := DID{} + u1 := Undef + if u0 != u1 { + t.Fatalf("undef DID not equivalent") + } + + d0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z") + if err != nil { + t.Fatalf("%v", err) + } + + d1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z") + if err != nil { + t.Fatalf("%v", err) + } + + if d0 != d1 { + t.Fatalf("two equivalent DID not equivalent") + } +} From 78838e451827203860d4eabc91ee90d2354e42b9 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 19 Sep 2023 22:52:23 +0100 Subject: [PATCH 2/4] feat: support for non did:key --- did.go | 19 ++++++++++++++++--- did_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/did.go b/did.go index b093ce4..2c49953 100644 --- a/did.go +++ b/did.go @@ -11,9 +11,13 @@ import ( const Prefix = "did:" const KeyPrefix = "did:key:" +const DIDCore = 0x0d1d const Ed25519 = 0xed +var MethodOffset = varint.UvarintSize(uint64(DIDCore)) + type DID struct { + key bool str string } @@ -37,8 +41,11 @@ func (d DID) DID() DID { } func (d DID) String() string { - key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str)) - return "did:key:" + key + if d.key { + key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str)) + return "did:key:" + key + } + return "did:" + d.str[MethodOffset:] } func Decode(bytes []byte) (DID, error) { @@ -47,6 +54,8 @@ func Decode(bytes []byte) (DID, error) { return Undef, err } if code == Ed25519 { + return DID{str: string(bytes), key: true}, nil + } else if code == DIDCore { return DID{str: string(bytes)}, nil } return Undef, fmt.Errorf("decoding DID: unsupported DID encoding: 0x%x", code) @@ -68,5 +77,9 @@ func Parse(str string) (DID, error) { return Decode(bytes) } - return Undef, fmt.Errorf("parsing DID: unsupported method") + buf := make([]byte, MethodOffset) + varint.PutUvarint(buf, DIDCore) + suffix, _ := strings.CutPrefix(str, Prefix) + buf = append(buf, suffix...) + return DID{str: string(buf)}, nil } diff --git a/did_test.go b/did_test.go index 720e733..47cfe96 100644 --- a/did_test.go +++ b/did_test.go @@ -30,6 +30,32 @@ func TestDecodeDIDKey(t *testing.T) { } } +func TestParseDIDWeb(t *testing.T) { + str := "did:web:up.web3.storage" + d, err := Parse(str) + if err != nil { + t.Fatalf("%v", err) + } + if d.String() != str { + t.Fatalf("expected %v to equal %v", d.String(), str) + } +} + +func TestDecodeDIDWeb(t *testing.T) { + str := "did:web:up.web3.storage" + d0, err := Parse(str) + if err != nil { + t.Fatalf("%v", err) + } + d1, err := Decode(d0.Bytes()) + if err != nil { + t.Fatalf("%v", err) + } + if d1.String() != str { + t.Fatalf("expected %v to equal %v", d1.String(), str) + } +} + func TestEquivalence(t *testing.T) { u0 := DID{} u1 := Undef From 59ac9493225bfd3fa1c8f42dbbdd98cafdfcf33a Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 22 Sep 2023 22:53:47 +0100 Subject: [PATCH 3/4] feat: sign and verify --- did.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/did.go b/did.go index 2c49953..b8cf52a 100644 --- a/did.go +++ b/did.go @@ -58,12 +58,12 @@ func Decode(bytes []byte) (DID, error) { } else if code == DIDCore { return DID{str: string(bytes)}, nil } - return Undef, fmt.Errorf("decoding DID: unsupported DID encoding: 0x%x", code) + return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code) } func Parse(str string) (DID, error) { if !strings.HasPrefix(str, Prefix) { - return Undef, fmt.Errorf("parsing DID: must start with 'did:'") + return Undef, fmt.Errorf("must start with 'did:'") } if strings.HasPrefix(str, KeyPrefix) { @@ -72,7 +72,7 @@ func Parse(str string) (DID, error) { return Undef, err } if code != mbase.Base58BTC { - return Undef, fmt.Errorf("parsing DID key: not Base58BTC encoded") + return Undef, fmt.Errorf("not Base58BTC encoded") } return Decode(bytes) } From bd9f680456fff2515614e9d7ed11e5423478b8d5 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 1 Dec 2023 17:32:34 +0000 Subject: [PATCH 4/4] fix: many fixes --- did.go | 1 + 1 file changed, 1 insertion(+) diff --git a/did.go b/did.go index b8cf52a..483485c 100644 --- a/did.go +++ b/did.go @@ -40,6 +40,7 @@ func (d DID) DID() DID { return d } +// String formats the decentralized identity document (DID) as a string. func (d DID) String() string { if d.key { key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))