forked from MetaMask/go-did-it
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdid.go
More file actions
162 lines (141 loc) · 3.82 KB
/
did.go
File metadata and controls
162 lines (141 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package did
import (
"fmt"
"net/url"
"strings"
"sync"
)
// Specifications:
// - https://www.w3.org/TR/did-1.0/
// - https://www.w3.org/TR/did-1.1/
const JsonLdContext = "https://www.w3.org/ns/did/v1"
// Decoder is a function decoding a DID string representation ("did:example:foo") into a DID.
type Decoder func(didStr string) (DID, error)
// RegisterMethod registers a DID decoder for a given DID method.
// Method must be the DID method (for example, "key" in did:key).
func RegisterMethod(method string, decoder Decoder) {
decodersMu.Lock()
defer decodersMu.Unlock()
if !checkMethod(method) {
panic("invalid method")
}
if decoders[method] != nil {
panic("did decoder already registered")
}
decoders[method] = decoder
}
// Parse attempts to decode a DID from its string representation.
func Parse(didStr string) (DID, error) {
decodersMu.RLock()
defer decodersMu.RUnlock()
if !strings.HasPrefix(didStr, "did:") {
return nil, fmt.Errorf("%w: must start with \"did:\"", ErrInvalidDid)
}
method, suffix, ok := strings.Cut(didStr[len("did:"):], ":")
if !ok {
return nil, fmt.Errorf("%w: must have a method and an identifier", ErrInvalidDid)
}
if !checkMethodSpecificId(suffix) {
return nil, fmt.Errorf("%w: invalid identifier characters", ErrInvalidDid)
}
decoder, ok := decoders[method]
if !ok {
return nil, fmt.Errorf("%w: %s", ErrMethodNotSupported, method)
}
return decoder(didStr)
}
// MustParse is like Parse but panics instead of returning an error.
func MustParse(didStr string) DID {
did, err := Parse(didStr)
if err != nil {
panic(err)
}
return did
}
// HasValidDIDSyntax tells if the given string representation conforms to DID syntax.
// This does NOT verify that the method is supported by this library.
func HasValidDIDSyntax(didStr string) bool {
if !strings.HasPrefix(didStr, "did:") {
return false
}
method, suffix, ok := strings.Cut(didStr[len("did:"):], ":")
if !ok {
return false
}
return checkMethod(method) && checkMethodSpecificId(suffix)
}
func checkMethod(method string) bool {
if len(method) == 0 {
return false
}
for _, char := range method {
isAlpha := 'a' <= char && char <= 'z'
isDigit := '0' <= char && char <= '9'
if !isAlpha && !isDigit {
return false
}
}
return true
}
func checkMethodSpecificId(suffix string) bool {
if len(suffix) == 0 {
return false
}
segments := strings.Split(suffix, ":")
for i, segment := range segments {
if i == len(segments)-1 && len(segment) == 0 {
// last segment can't be empty
return false
}
var percentExpected int
for _, char := range segment {
if percentExpected > 0 {
switch {
case char >= '0' && char <= '9':
percentExpected--
case char >= 'a' && char <= 'f':
percentExpected--
case char >= 'A' && char <= 'F':
percentExpected--
default:
return false
}
}
switch {
case char >= 'a' && char <= 'z': // nothing to do
case char >= 'A' && char <= 'Z': // nothing to do
case char >= '0' && char <= '9': // nothing to do
case char == '.': // nothing to do
case char == '-': // nothing to do
case char == '_': // nothing to do
case char == '%':
percentExpected = 2
default:
return false
}
}
if percentExpected > 0 {
// unfinished percent encoding
return false
}
}
return true
}
// HasValidDidUrlSyntax tells if the given string representation conforms to DID URL syntax.
// This does NOT verify that the method is supported by this library.
func HasValidDidUrlSyntax(didUrlStr string) bool {
cutPos := strings.IndexAny(didUrlStr, "/#?")
if cutPos == -1 {
return HasValidDIDSyntax(didUrlStr)
}
base, rest := didUrlStr[:cutPos], didUrlStr[cutPos+1:]
if HasValidDIDSyntax(base) == false {
return false
}
_, err := url.Parse("example.com" + rest)
return err == nil
}
var (
decodersMu sync.RWMutex
decoders = map[string]Decoder{}
)