commit 23c2b1f459835795743b9469f2b59874bf505ff7 Author: Alan Shaw Date: Wed Sep 13 22:32:07 2023 +0100 first commit 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") + } +}