github.com/storacha/go-ucanto@v0.7.2/did/did.go (about) 1 package did 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 8 mbase "github.com/multiformats/go-multibase" 9 varint "github.com/multiformats/go-varint" 10 ) 11 12 const Prefix = "did:" 13 const KeyPrefix = "did:key:" 14 15 const DIDCore = 0x0d1d 16 const Ed25519 = 0xed 17 const RSA = 0x1205 18 19 var MethodOffset = varint.UvarintSize(uint64(DIDCore)) 20 21 type DID struct { 22 key bool 23 str string 24 } 25 26 // Undef can be used to represent a nil or undefined DID, using DID{} 27 // directly is also acceptable. 28 var Undef = DID{} 29 30 func (d DID) Defined() bool { 31 return d.str != "" 32 } 33 34 func (d DID) Bytes() []byte { 35 if !d.Defined() { 36 return nil 37 } 38 return []byte(d.str) 39 } 40 41 func (d DID) DID() DID { 42 return d 43 } 44 45 // String formats the decentralized identity document (DID) as a string. 46 func (d DID) String() string { 47 if d.key { 48 key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str)) 49 return "did:key:" + key 50 } 51 return "did:" + d.str[MethodOffset:] 52 } 53 54 // GoString formats the decentralized identity document (DID) as a string. 55 func (d DID) GoString() string { 56 return d.String() 57 } 58 59 func (d DID) MarshalJSON() ([]byte, error) { 60 if d == Undef { 61 return json.Marshal(nil) 62 } 63 return json.Marshal(d.String()) 64 } 65 66 func (d *DID) UnmarshalJSON(b []byte) error { 67 var str string 68 err := json.Unmarshal(b, &str) 69 if err != nil { 70 return fmt.Errorf("parsing string: %w", err) 71 } 72 if str == "" { 73 return nil 74 } 75 parsed, err := Parse(str) 76 if err != nil { 77 return fmt.Errorf("parsing DID: %w", err) 78 } 79 *d = parsed 80 return nil 81 } 82 83 func Decode(bytes []byte) (DID, error) { 84 code, _, err := varint.FromUvarint(bytes) 85 if err != nil { 86 return Undef, err 87 } 88 if code == Ed25519 || code == RSA { 89 return DID{str: string(bytes), key: true}, nil 90 } else if code == DIDCore { 91 return DID{str: string(bytes)}, nil 92 } 93 return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code) 94 } 95 96 func Parse(str string) (DID, error) { 97 if !strings.HasPrefix(str, Prefix) { 98 return Undef, fmt.Errorf("must start with 'did:'") 99 } 100 101 if strings.HasPrefix(str, KeyPrefix) { 102 code, bytes, err := mbase.Decode(str[len(KeyPrefix):]) 103 if err != nil { 104 return Undef, err 105 } 106 if code != mbase.Base58BTC { 107 return Undef, fmt.Errorf("not Base58BTC encoded") 108 } 109 return Decode(bytes) 110 } 111 112 buf := make([]byte, MethodOffset) 113 varint.PutUvarint(buf, DIDCore) 114 suffix, _ := strings.CutPrefix(str, Prefix) 115 buf = append(buf, suffix...) 116 return DID{str: string(buf)}, nil 117 }