did_resolver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +38 -0
- data/LICENSE +190 -0
- data/README.md +222 -0
- data/lib/did_resolver/cache.rb +73 -0
- data/lib/did_resolver/did_document.rb +186 -0
- data/lib/did_resolver/methods/jwk.rb +175 -0
- data/lib/did_resolver/methods/key.rb +386 -0
- data/lib/did_resolver/methods/web.rb +154 -0
- data/lib/did_resolver/methods.rb +12 -0
- data/lib/did_resolver/resolver.rb +134 -0
- data/lib/did_resolver/version.rb +5 -0
- data/lib/did_resolver.rb +201 -0
- metadata +176 -0
data/lib/did_resolver.rb
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# DID Resolver Library
|
|
4
|
+
#
|
|
5
|
+
# A Ruby implementation inspired by the Decentralized Identity Foundation's
|
|
6
|
+
# did-resolver (https://github.com/decentralized-identity/did-resolver)
|
|
7
|
+
#
|
|
8
|
+
# This library provides a universal interface for resolving Decentralized
|
|
9
|
+
# Identifiers (DIDs) according to the W3C DID Core specification.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# require 'did_resolver'
|
|
13
|
+
#
|
|
14
|
+
# # Create a resolver with method resolvers
|
|
15
|
+
# resolver = DidResolver::Resolver.new(
|
|
16
|
+
# DidResolver::Methods::Web.resolver,
|
|
17
|
+
# DidResolver::Methods::Key.resolver,
|
|
18
|
+
# DidResolver::Methods::Jwk.resolver
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# # Resolve a DID
|
|
22
|
+
# result = resolver.resolve("did:web:example.com")
|
|
23
|
+
# result.did_document # => DIDDocument
|
|
24
|
+
#
|
|
25
|
+
# Method Resolver Implementation:
|
|
26
|
+
# Each method resolver exports a .resolver method returning { method_name: resolve_proc }
|
|
27
|
+
# The resolve_proc receives (did, parsed, resolver, options) and returns a DIDResolutionResult
|
|
28
|
+
#
|
|
29
|
+
module DidResolver
|
|
30
|
+
class Error < StandardError; end
|
|
31
|
+
class InvalidDIDError < Error; end
|
|
32
|
+
class UnsupportedMethodError < Error; end
|
|
33
|
+
class ResolutionError < Error; end
|
|
34
|
+
class NotFoundError < ResolutionError; end
|
|
35
|
+
class NetworkError < ResolutionError; end
|
|
36
|
+
|
|
37
|
+
# DID Resolution Result as per DID Core spec
|
|
38
|
+
# @see https://www.w3.org/TR/did-core/#did-resolution
|
|
39
|
+
class ResolutionResult
|
|
40
|
+
attr_reader :did_resolution_metadata, :did_document, :did_document_metadata
|
|
41
|
+
|
|
42
|
+
def initialize(did_document: nil, did_resolution_metadata: {}, did_document_metadata: {})
|
|
43
|
+
@did_document = did_document
|
|
44
|
+
@did_resolution_metadata = did_resolution_metadata.freeze
|
|
45
|
+
@did_document_metadata = did_document_metadata.freeze
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def error?
|
|
49
|
+
!did_resolution_metadata[:error].nil? && did_resolution_metadata[:error] != ""
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def error
|
|
53
|
+
did_resolution_metadata[:error]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def error_message
|
|
57
|
+
did_resolution_metadata[:error_message]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def content_type
|
|
61
|
+
did_resolution_metadata[:content_type] || "application/did+ld+json"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_h
|
|
65
|
+
{
|
|
66
|
+
did_resolution_metadata: did_resolution_metadata,
|
|
67
|
+
did_document: did_document&.to_h,
|
|
68
|
+
did_document_metadata: did_document_metadata
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Factory methods for common results
|
|
73
|
+
class << self
|
|
74
|
+
def success(did_document, metadata: {}, document_metadata: {})
|
|
75
|
+
new(
|
|
76
|
+
did_document: did_document,
|
|
77
|
+
did_resolution_metadata: { content_type: "application/did+ld+json" }.merge(metadata),
|
|
78
|
+
did_document_metadata: document_metadata
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def error(error_type, message = nil)
|
|
83
|
+
new(
|
|
84
|
+
did_resolution_metadata: {
|
|
85
|
+
error: error_type,
|
|
86
|
+
error_message: message
|
|
87
|
+
}.compact
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def not_found(did)
|
|
92
|
+
error("notFound", "DID not found: #{did}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def method_not_supported(method)
|
|
96
|
+
error("methodNotSupported", "DID method not supported: #{method}")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def invalid_did(did, reason = nil)
|
|
100
|
+
error("invalidDid", reason || "Invalid DID format: #{did}")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Parsed DID components
|
|
106
|
+
# @see https://www.w3.org/TR/did-core/#did-syntax
|
|
107
|
+
class ParsedDID
|
|
108
|
+
attr_reader :did, :method, :id, :path, :query, :fragment, :params
|
|
109
|
+
|
|
110
|
+
# DID Syntax: did:method-name:method-specific-id
|
|
111
|
+
DID_REGEX = /\Adid:([a-z0-9]+):([^#?\/]+)(\/[^#?]*)?(\\?[^#]*)?(#.*)?\z/i.freeze
|
|
112
|
+
|
|
113
|
+
def initialize(did:, method:, id:, path: nil, query: nil, fragment: nil, params: {})
|
|
114
|
+
@did = did
|
|
115
|
+
@method = method
|
|
116
|
+
@id = id
|
|
117
|
+
@path = path
|
|
118
|
+
@query = query
|
|
119
|
+
@fragment = fragment
|
|
120
|
+
@params = params.freeze
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The full DID URL (did + path + query + fragment)
|
|
124
|
+
def did_url
|
|
125
|
+
@did_url ||= "#{did}#{path}#{query}#{fragment}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def to_h
|
|
129
|
+
{
|
|
130
|
+
did: did,
|
|
131
|
+
method: method,
|
|
132
|
+
id: id,
|
|
133
|
+
path: path,
|
|
134
|
+
query: query,
|
|
135
|
+
fragment: fragment,
|
|
136
|
+
params: params
|
|
137
|
+
}.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class << self
|
|
141
|
+
# Parse a DID string into components
|
|
142
|
+
# @param did_string [String] The DID or DID URL to parse
|
|
143
|
+
# @return [ParsedDID]
|
|
144
|
+
# @raise [InvalidDIDError] if the DID format is invalid
|
|
145
|
+
def parse(did_string)
|
|
146
|
+
raise InvalidDIDError, "DID cannot be nil" if did_string.nil?
|
|
147
|
+
raise InvalidDIDError, "DID cannot be empty" if did_string.empty?
|
|
148
|
+
|
|
149
|
+
# Normalize and extract parts
|
|
150
|
+
did_string = did_string.strip
|
|
151
|
+
|
|
152
|
+
match = did_string.match(DID_REGEX)
|
|
153
|
+
raise InvalidDIDError, "Invalid DID format: #{did_string}" unless match
|
|
154
|
+
|
|
155
|
+
method = match[1].downcase
|
|
156
|
+
id = match[2]
|
|
157
|
+
path = match[3]
|
|
158
|
+
query = match[4]
|
|
159
|
+
fragment = match[5]
|
|
160
|
+
|
|
161
|
+
# Extract DID parameters from query if present
|
|
162
|
+
params = parse_params(query)
|
|
163
|
+
|
|
164
|
+
# The base DID (without path, query, fragment)
|
|
165
|
+
base_did = "did:#{method}:#{id}"
|
|
166
|
+
|
|
167
|
+
new(
|
|
168
|
+
did: base_did,
|
|
169
|
+
method: method,
|
|
170
|
+
id: id,
|
|
171
|
+
path: path,
|
|
172
|
+
query: query,
|
|
173
|
+
fragment: fragment,
|
|
174
|
+
params: params
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def parse_params(query_string)
|
|
181
|
+
return {} unless query_string
|
|
182
|
+
|
|
183
|
+
# Remove leading ?
|
|
184
|
+
query_string = query_string[1..] if query_string.start_with?("?")
|
|
185
|
+
return {} if query_string.empty?
|
|
186
|
+
|
|
187
|
+
query_string.split("&").each_with_object({}) do |pair, hash|
|
|
188
|
+
key, value = pair.split("=", 2)
|
|
189
|
+
hash[key] = value
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Load sub-modules
|
|
197
|
+
require_relative "did_resolver/version"
|
|
198
|
+
require_relative "did_resolver/did_document"
|
|
199
|
+
require_relative "did_resolver/resolver"
|
|
200
|
+
require_relative "did_resolver/cache"
|
|
201
|
+
require_relative "did_resolver/methods"
|
metadata
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: did_resolver
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dean De Block
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-02-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: base64
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.2'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: uri
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: bundler
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: webmock
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.0'
|
|
124
|
+
description: |
|
|
125
|
+
A Ruby implementation of a universal DID (Decentralized Identifier) resolver,
|
|
126
|
+
inspired by the Decentralized Identity Foundation's did-resolver.
|
|
127
|
+
|
|
128
|
+
Supports multiple DID methods:
|
|
129
|
+
- did:web - HTTPS domain-based DIDs
|
|
130
|
+
- did:key - Self-describing cryptographic key DIDs
|
|
131
|
+
- did:jwk - JWK-encoded DIDs
|
|
132
|
+
|
|
133
|
+
Includes support for EBSI jwk_jcs-pub format (multicodec 0xeb51).
|
|
134
|
+
email:
|
|
135
|
+
- dean.de.block@gmail.com
|
|
136
|
+
executables: []
|
|
137
|
+
extensions: []
|
|
138
|
+
extra_rdoc_files: []
|
|
139
|
+
files:
|
|
140
|
+
- CHANGELOG.md
|
|
141
|
+
- LICENSE
|
|
142
|
+
- README.md
|
|
143
|
+
- lib/did_resolver.rb
|
|
144
|
+
- lib/did_resolver/cache.rb
|
|
145
|
+
- lib/did_resolver/did_document.rb
|
|
146
|
+
- lib/did_resolver/methods.rb
|
|
147
|
+
- lib/did_resolver/methods/jwk.rb
|
|
148
|
+
- lib/did_resolver/methods/key.rb
|
|
149
|
+
- lib/did_resolver/methods/web.rb
|
|
150
|
+
- lib/did_resolver/resolver.rb
|
|
151
|
+
- lib/did_resolver/version.rb
|
|
152
|
+
homepage: https://github.com/DeanDeBlock/did_resolver
|
|
153
|
+
licenses:
|
|
154
|
+
- Apache-2.0
|
|
155
|
+
metadata:
|
|
156
|
+
homepage_uri: https://github.com/DeanDeBlock/did_resolver
|
|
157
|
+
source_code_uri: https://github.com/DeanDeBlock/did_resolver
|
|
158
|
+
changelog_uri: https://github.com/DeanDeBlock/did_resolver/blob/main/CHANGELOG.md
|
|
159
|
+
rdoc_options: []
|
|
160
|
+
require_paths:
|
|
161
|
+
- lib
|
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: 3.1.0
|
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
|
+
requirements:
|
|
169
|
+
- - ">="
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '0'
|
|
172
|
+
requirements: []
|
|
173
|
+
rubygems_version: 3.6.2
|
|
174
|
+
specification_version: 4
|
|
175
|
+
summary: Universal DID Resolver for Ruby
|
|
176
|
+
test_files: []
|