sidetree 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/.github/workflows/main.yml +22 -0
- data/.gitignore +12 -0
- data/.prettierignore +9 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/ion +34 -0
- data/lib/sidetree/did.rb +91 -0
- data/lib/sidetree/key.rb +181 -0
- data/lib/sidetree/model/delta.rb +28 -0
- data/lib/sidetree/model/document.rb +40 -0
- data/lib/sidetree/model/service.rb +50 -0
- data/lib/sidetree/model/suffix.rb +34 -0
- data/lib/sidetree/model.rb +8 -0
- data/lib/sidetree/op/base.rb +11 -0
- data/lib/sidetree/op/create.rb +69 -0
- data/lib/sidetree/op.rb +37 -0
- data/lib/sidetree/validator.rb +250 -0
- data/lib/sidetree/version.rb +5 -0
- data/lib/sidetree.rb +69 -0
- data/sidetree.gemspec +44 -0
- metadata +174 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Sidetree
|
|
2
|
+
module Model
|
|
3
|
+
class Service
|
|
4
|
+
MAX_TYPE_LENGTH = 30
|
|
5
|
+
|
|
6
|
+
attr_reader :id # String
|
|
7
|
+
attr_reader :type # String
|
|
8
|
+
attr_reader :endpoint # URI string or JSON object
|
|
9
|
+
|
|
10
|
+
# @raise [Sidetree::Error]
|
|
11
|
+
def initialize(id, type, endpoint)
|
|
12
|
+
Sidetree::Validator.validate_id!(id)
|
|
13
|
+
raise Error, 'type should be String.' unless type.is_a?(String)
|
|
14
|
+
if type.length > MAX_TYPE_LENGTH
|
|
15
|
+
raise Error,
|
|
16
|
+
"Service endpoint type length #{type.length} exceeds max allowed length of #{MAX_TYPE_LENGTH}."
|
|
17
|
+
end
|
|
18
|
+
if endpoint.is_a?(Array)
|
|
19
|
+
raise Error, 'Service endpoint value cannot be an array.'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Sidetree::Validator.validate_uri!(endpoint) if endpoint.is_a?(String)
|
|
23
|
+
@id = id
|
|
24
|
+
@type = type
|
|
25
|
+
@endpoint = endpoint
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Generate service from json object.
|
|
29
|
+
# @param [Hash] data Hash params.
|
|
30
|
+
# @option data [String] :id id
|
|
31
|
+
# @option data [String] :type type
|
|
32
|
+
# @option data [String || Object] :endpoint endpoint url
|
|
33
|
+
# @raise [Sidetree::Error]
|
|
34
|
+
# @return [Sidetree::Model::Service]
|
|
35
|
+
def self.from_hash(data)
|
|
36
|
+
Service.new(data['id'], data['type'], data['serviceEndpoint'])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Convert data to Hash object.
|
|
40
|
+
# @return [Hash]
|
|
41
|
+
def to_h
|
|
42
|
+
hash = {}
|
|
43
|
+
hash['id'] = id if id
|
|
44
|
+
hash['type'] = type if type
|
|
45
|
+
hash['serviceEndpoint'] = endpoint if endpoint
|
|
46
|
+
hash
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Sidetree
|
|
2
|
+
module Model
|
|
3
|
+
class Suffix
|
|
4
|
+
attr_reader :delta_hash, :recovery_commitment
|
|
5
|
+
|
|
6
|
+
# @param [String] delta_hash Base64 encoded delta hash.
|
|
7
|
+
# @param [String] recovery_commitment Base64 encoded recovery commitment.
|
|
8
|
+
def initialize(delta_hash, recovery_commitment)
|
|
9
|
+
@delta_hash = delta_hash
|
|
10
|
+
@recovery_commitment = recovery_commitment
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Generate Suffix object from Hash object.
|
|
14
|
+
# @return [Sidetree::Model::Suffix]
|
|
15
|
+
# @raise [Sidetree::Error]
|
|
16
|
+
def self.parse(object)
|
|
17
|
+
Sidetree::Validator.validate_suffix_data!(object)
|
|
18
|
+
Suffix.new(object[:deltaHash], object[:recoveryCommitment])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Convert data to Hash object.
|
|
22
|
+
# @return [Hash]
|
|
23
|
+
def to_h
|
|
24
|
+
{ deltaHash: delta_hash, recoveryCommitment: recovery_commitment }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Calculate unique suffix
|
|
28
|
+
# @return [String] unique suffix
|
|
29
|
+
def unique_suffix
|
|
30
|
+
Sidetree.to_hash(to_h)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Sidetree
|
|
2
|
+
module OP
|
|
3
|
+
# Create operation class.
|
|
4
|
+
class Create < Base
|
|
5
|
+
attr_reader :suffix, :delta
|
|
6
|
+
|
|
7
|
+
# @param [Sidetree::Model::Suffix] suffix
|
|
8
|
+
# @param [Sidetree::Model::Delta] delta
|
|
9
|
+
def initialize(suffix, delta)
|
|
10
|
+
@delta = delta
|
|
11
|
+
@suffix = suffix
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
Sidetree::OP::Type::CREATE
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Check whether suffix's delta_hash equal to hash of delta.
|
|
19
|
+
# @return [Boolean] result
|
|
20
|
+
def match_delta_hash?
|
|
21
|
+
suffix.delta_hash == delta.to_hash
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Sidetree::OP::Create] create operation.
|
|
25
|
+
def self.from_base64(base64_str)
|
|
26
|
+
jcs = Base64.urlsafe_decode64(base64_str)
|
|
27
|
+
begin
|
|
28
|
+
json = JSON.parse(jcs, symbolize_names: true)
|
|
29
|
+
|
|
30
|
+
# validate jcs
|
|
31
|
+
expected_base64 =
|
|
32
|
+
Base64.urlsafe_encode64(json.to_json_c14n, padding: false)
|
|
33
|
+
unless expected_base64 == base64_str
|
|
34
|
+
raise Error, 'Initial state object and JCS string mismatch.'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Create.new(
|
|
38
|
+
Sidetree::Model::Suffix.parse(json[:suffixData]),
|
|
39
|
+
Sidetree::Model::Delta.parse(json[:delta])
|
|
40
|
+
)
|
|
41
|
+
rescue JSON::ParserError
|
|
42
|
+
raise Error, 'Long form initial state should be encoded jcs.'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_h
|
|
47
|
+
{ suffixData: suffix.to_h, delta: delta.to_h }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Generate long_suffix for DID.
|
|
51
|
+
# @return [String] Base64 encoded long_suffix.
|
|
52
|
+
def long_suffix
|
|
53
|
+
Base64.urlsafe_encode64(to_h.to_json_c14n, padding: false)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Generate DID
|
|
57
|
+
# @param [String] method DID method.
|
|
58
|
+
# @param [Boolean] include_long
|
|
59
|
+
# @return [String] DID
|
|
60
|
+
def did(method: Sidetree::Params::DEFAULT_METHOD, include_long: false)
|
|
61
|
+
did = "did:#{method}"
|
|
62
|
+
did += ":#{Sidetree::Params.network}" if Sidetree::Params.testnet?
|
|
63
|
+
did += ":#{suffix.unique_suffix}"
|
|
64
|
+
did += ":#{long_suffix}" if include_long
|
|
65
|
+
did
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/sidetree/op.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Sidetree
|
|
2
|
+
module OP
|
|
3
|
+
module Type
|
|
4
|
+
CREATE = 'create'
|
|
5
|
+
UPDATE = 'update'
|
|
6
|
+
RECOVER = 'recover'
|
|
7
|
+
DEACTIVATE = 'deactivate'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Sidetree patch actions. These are the valid values in the action property of a patch.
|
|
11
|
+
module PatchAction
|
|
12
|
+
REPLACE = 'replace'
|
|
13
|
+
ADD_PUBLIC_KEYS = 'add-public-keys'
|
|
14
|
+
REMOVE_PUBLIC_KEYS = 'remove-public-keys'
|
|
15
|
+
ADD_SERVICES = 'add-services'
|
|
16
|
+
REMOVE_SERVICES = 'remove-services'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# DID Document public key purpose.
|
|
20
|
+
module PublicKeyPurpose
|
|
21
|
+
AUTHENTICATION = 'authentication'
|
|
22
|
+
ASSERTION_METHOD = 'assertionMethod'
|
|
23
|
+
CAPABILITY_INVOCATION = 'capabilityInvocation'
|
|
24
|
+
CAPABILITY_DELEGATION = 'capabilityDelegation'
|
|
25
|
+
KEY_AGREEMENT = 'keyAgreement'
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
def values
|
|
30
|
+
PublicKeyPurpose.constants.map { |c| PublicKeyPurpose.const_get(c) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
autoload :Base, 'sidetree/op/base'
|
|
35
|
+
autoload :Create, 'sidetree/op/create'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
module Sidetree
|
|
2
|
+
module Validator
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
# @param [Hash] delta delta object.
|
|
6
|
+
# @return [Sidetree::Error]
|
|
7
|
+
def validate_delta!(delta)
|
|
8
|
+
raise Error, 'Delta does not defined.' unless delta
|
|
9
|
+
delta_size = delta.to_json_c14n.bytesize
|
|
10
|
+
if delta_size > Sidetree::Params::MAX_DELTA_SIZE
|
|
11
|
+
raise Error,
|
|
12
|
+
"#{delta_size} bytes of 'delta' exceeded limit of #{Sidetree::Params::MAX_DELTA_SIZE} bytes."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if delta.instance_of?(Array)
|
|
16
|
+
raise Error, 'Delta object cannot be an array.'
|
|
17
|
+
end
|
|
18
|
+
delta.keys.each do |k|
|
|
19
|
+
unless %w[patches updateCommitment].include?(k.to_s)
|
|
20
|
+
raise Error, "Property '#{k}' is not allowed in delta object."
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
unless delta[:patches].instance_of?(Array)
|
|
25
|
+
raise Error, 'Patches object in delta must be an array.'
|
|
26
|
+
end
|
|
27
|
+
delta[:patches].each { |p| validate_patch!(p) }
|
|
28
|
+
|
|
29
|
+
validate_encoded_multi_hash!(delta[:updateCommitment], 'updateCommitment')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param [Hash] patch patch object.
|
|
33
|
+
# @raise [Sidetree::Error]
|
|
34
|
+
def validate_patch!(patch)
|
|
35
|
+
case patch[:action]
|
|
36
|
+
when OP::PatchAction::REPLACE
|
|
37
|
+
validate_document!(patch[:document])
|
|
38
|
+
when OP::PatchAction::ADD_PUBLIC_KEYS
|
|
39
|
+
validate_add_public_keys_patch!(patch)
|
|
40
|
+
when OP::PatchAction::REMOVE_PUBLIC_KEYS
|
|
41
|
+
validate_remove_public_keys_patch!(patch)
|
|
42
|
+
when OP::PatchAction::ADD_SERVICES
|
|
43
|
+
validate_add_services_patch!(patch)
|
|
44
|
+
when OP::PatchAction::REMOVE_SERVICES
|
|
45
|
+
validate_remove_services_patch!(patch)
|
|
46
|
+
else
|
|
47
|
+
raise Error, "#{patch[:action]} is unknown patch action."
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_document!(document)
|
|
52
|
+
raise Error, 'Document object missing in patch object' unless document
|
|
53
|
+
document.keys.each do |k|
|
|
54
|
+
unless %w[publicKeys services].include?(k.to_s)
|
|
55
|
+
raise Error, "Property '#{k}' is not allowed in document object."
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
validate_public_keys!(document[:publicKeys]) if document[:publicKeys]
|
|
59
|
+
validate_services!(document[:services]) if document[:services]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_add_public_keys_patch!(patch)
|
|
63
|
+
unless patch.keys.length == 2
|
|
64
|
+
raise Error, 'Patch missing or unknown property.'
|
|
65
|
+
end
|
|
66
|
+
validate_public_keys!(patch[:publicKeys])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_remove_public_keys_patch!(patch)
|
|
70
|
+
patch.keys.each do |k|
|
|
71
|
+
unless %w[action ids].include?(k.to_s)
|
|
72
|
+
raise Error, "Unexpected property '#{k}' in remove-public-keys patch."
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
unless patch[:ids].instance_of?(Array)
|
|
76
|
+
raise Error, 'Patch public key ids not an array.'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
patch[:ids].each { |id| validate_id!(id) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def validate_add_services_patch!(patch)
|
|
83
|
+
unless patch.keys.length == 2
|
|
84
|
+
raise Error, 'Patch missing or unknown property.'
|
|
85
|
+
end
|
|
86
|
+
unless patch[:services].instance_of?(Array)
|
|
87
|
+
raise Error, 'Patch services not an array.'
|
|
88
|
+
end
|
|
89
|
+
validate_services!(patch[:services])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_remove_services_patch!(patch)
|
|
93
|
+
patch.keys.each do |k|
|
|
94
|
+
unless %w[action ids].include?(k.to_s)
|
|
95
|
+
raise Error, "Unexpected property '#{k}' in remove-services patch."
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
unless patch[:ids].instance_of?(Array)
|
|
99
|
+
raise Error, 'Patch service ids not an array.'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
patch[:ids].each { |id| validate_id!(id) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def validate_public_keys!(public_keys)
|
|
106
|
+
unless public_keys.instance_of?(Array)
|
|
107
|
+
raise Error, 'publicKeys must be an array.'
|
|
108
|
+
end
|
|
109
|
+
pubkey_ids = []
|
|
110
|
+
public_keys.each do |public_key|
|
|
111
|
+
public_key.keys.each do |k|
|
|
112
|
+
unless %w[id type purposes publicKeyJwk].include?(k.to_s)
|
|
113
|
+
raise Error, "Property '#{k}' is not allowed in publicKeys object."
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
if public_key[:publicKeyJwk].instance_of?(Array)
|
|
117
|
+
raise Error, 'publicKeyJwk object cannot be an array.'
|
|
118
|
+
end
|
|
119
|
+
if public_key[:type] && !public_key[:type].is_a?(String)
|
|
120
|
+
raise Error, "Public key type #{public_key[:type]} is incorrect."
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
validate_id!(public_key[:id])
|
|
124
|
+
|
|
125
|
+
if pubkey_ids.include?(public_key[:id])
|
|
126
|
+
raise Error, 'Public key id is duplicated.'
|
|
127
|
+
end
|
|
128
|
+
pubkey_ids << public_key[:id]
|
|
129
|
+
|
|
130
|
+
if public_key[:purposes]
|
|
131
|
+
unless public_key[:purposes].instance_of?(Array)
|
|
132
|
+
raise Error, 'purposes must be an array.'
|
|
133
|
+
end
|
|
134
|
+
unless public_key[:purposes].count == public_key[:purposes].uniq.count
|
|
135
|
+
raise Error, 'purpose is duplicated.'
|
|
136
|
+
end
|
|
137
|
+
public_key[:purposes].each do |purpose|
|
|
138
|
+
unless OP::PublicKeyPurpose.values.include?(purpose)
|
|
139
|
+
raise Error, "purpose #{} is invalid."
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def validate_services!(services)
|
|
147
|
+
unless services.instance_of?(Array)
|
|
148
|
+
raise Error, 'services must be an array.'
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
service_ids = []
|
|
152
|
+
services.each do |service|
|
|
153
|
+
unless service.keys.length == 3
|
|
154
|
+
raise Error, 'Service has missing or unknown property.'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
validate_id!(service[:id])
|
|
158
|
+
|
|
159
|
+
if service_ids.include?(service[:id])
|
|
160
|
+
raise Error, 'Service id has to be unique.'
|
|
161
|
+
end
|
|
162
|
+
service_ids << service[:id]
|
|
163
|
+
|
|
164
|
+
unless service[:type].is_a?(String)
|
|
165
|
+
raise Error, "Service type #{service[:type]} is incorrect."
|
|
166
|
+
end
|
|
167
|
+
raise Error, 'Service type too long.' if service[:type].length > 30
|
|
168
|
+
|
|
169
|
+
endpoint = service[:serviceEndpoint]
|
|
170
|
+
if endpoint.instance_of?(String)
|
|
171
|
+
validate_uri!(endpoint)
|
|
172
|
+
elsif endpoint.instance_of?(Hash)
|
|
173
|
+
|
|
174
|
+
else
|
|
175
|
+
raise Error, 'ServiceEndpoint must be string or object.'
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def valid_base64_encoding?(base64)
|
|
181
|
+
/^[A-Za-z0-9_-]+$/.match?(base64)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Validate uri
|
|
185
|
+
# @param [String] uri uri
|
|
186
|
+
# @return [Sidetree::Error] Occurs if it is an incorrect URI
|
|
187
|
+
def validate_uri!(uri)
|
|
188
|
+
begin
|
|
189
|
+
URI.parse(uri)
|
|
190
|
+
unless uri =~ /\A#{URI.regexp(%w[http https])}\z/
|
|
191
|
+
raise Error, "URI string '#{uri}' is not a valid URI."
|
|
192
|
+
end
|
|
193
|
+
rescue StandardError
|
|
194
|
+
raise Error, "URI string '#{uri}' is not a valid URI."
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def validate_id!(id)
|
|
199
|
+
raise Error, 'id does not string.' unless id.instance_of?(String)
|
|
200
|
+
raise Error, 'id is too long.' if id.length > 50
|
|
201
|
+
unless valid_base64_encoding?(id)
|
|
202
|
+
raise Error, 'id does not use base64url character set.'
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def validate_encoded_multi_hash!(multi_hash, target)
|
|
207
|
+
begin
|
|
208
|
+
decoded = Multihashes.decode(Base64.urlsafe_decode64(multi_hash))
|
|
209
|
+
unless Params::HASH_ALGORITHM.include?(decoded[:code])
|
|
210
|
+
raise Error,
|
|
211
|
+
"Given #{target} uses unsupported multihash algorithm with code #{decoded[:code]}."
|
|
212
|
+
end
|
|
213
|
+
rescue StandardError
|
|
214
|
+
raise Error,
|
|
215
|
+
"Given #{target} string '#{multi_hash}' is not a multihash."
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def validate_did_type!(type)
|
|
220
|
+
return unless type
|
|
221
|
+
raise Error, 'DID type must be a string.' unless type.instance_of?(String)
|
|
222
|
+
if type.length > 4
|
|
223
|
+
raise Error,
|
|
224
|
+
"DID type string '#{type}' cannot be longer than 4 characters."
|
|
225
|
+
end
|
|
226
|
+
unless valid_base64_encoding?(type)
|
|
227
|
+
raise Error,
|
|
228
|
+
"DID type string '#{type}' contains a non-Base64URL character."
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def validate_suffix_data!(suffix)
|
|
233
|
+
if suffix.instance_of?(Array)
|
|
234
|
+
raise Error, 'Suffix data can not be an array.'
|
|
235
|
+
end
|
|
236
|
+
suffix.keys.each do |k|
|
|
237
|
+
unless %w[deltaHash recoveryCommitment type].include?(k.to_s)
|
|
238
|
+
raise Error, "Property '#{k}' is not allowed in publicKeys object."
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
validate_encoded_multi_hash!(suffix[:deltaHash], 'delta hash')
|
|
242
|
+
validate_encoded_multi_hash!(
|
|
243
|
+
suffix[:recoveryCommitment],
|
|
244
|
+
'recovery commitment'
|
|
245
|
+
)
|
|
246
|
+
validate_encoded_multi_hash!(suffix[:deltaHash], 'delta hash')
|
|
247
|
+
validate_did_type!(suffix[:type])
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
data/lib/sidetree.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'sidetree/version'
|
|
4
|
+
require 'ecdsa'
|
|
5
|
+
require 'json/jwt'
|
|
6
|
+
require 'base64'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'json/canonicalization'
|
|
9
|
+
require 'uri'
|
|
10
|
+
require 'multihashes'
|
|
11
|
+
|
|
12
|
+
module Sidetree
|
|
13
|
+
class Error < StandardError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
autoload :Key, 'sidetree/key'
|
|
17
|
+
autoload :DID, 'sidetree/did'
|
|
18
|
+
autoload :Model, 'sidetree/model'
|
|
19
|
+
autoload :OP, 'sidetree/op'
|
|
20
|
+
autoload :Validator, 'sidetree/validator'
|
|
21
|
+
|
|
22
|
+
module Params
|
|
23
|
+
# Algorithm for generating hashes of protocol-related values. 0x12 = sha2-256
|
|
24
|
+
HASH_ALGORITHM = [0x12]
|
|
25
|
+
HASH_ALGORITH_STRING = 'sha2-256'
|
|
26
|
+
|
|
27
|
+
# Maximum canonicalized operation delta buffer size.
|
|
28
|
+
MAX_DELTA_SIZE = 1000
|
|
29
|
+
|
|
30
|
+
# Default DID method
|
|
31
|
+
DEFAULT_METHOD = 'sidetree'
|
|
32
|
+
|
|
33
|
+
# Supported did methods.
|
|
34
|
+
METHODS = { default: DEFAULT_METHOD, ion: 'ion' }
|
|
35
|
+
|
|
36
|
+
@network = nil
|
|
37
|
+
|
|
38
|
+
def self.network=(network)
|
|
39
|
+
@network = network
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.network
|
|
43
|
+
@network
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.testnet?
|
|
47
|
+
@network == Network::TESTNET
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module Network
|
|
51
|
+
MAINNET = 'mainnet'
|
|
52
|
+
TESTNET = 'test'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
module_function
|
|
57
|
+
|
|
58
|
+
# Calculate Base64 encoded hash of hash object.
|
|
59
|
+
# @param [Hash] data
|
|
60
|
+
# @return [String] Base64 encoded hash value
|
|
61
|
+
def to_hash(data)
|
|
62
|
+
digest = Digest::SHA256.digest(data.is_a?(Hash) ? data.to_json_c14n : data)
|
|
63
|
+
hash = Multihashes.encode(digest, Params::HASH_ALGORITH_STRING)
|
|
64
|
+
|
|
65
|
+
# TODO Need to decide on what hash algorithm to use when hashing suffix data
|
|
66
|
+
# - https://github.com/decentralized-identity/sidetree/issues/965
|
|
67
|
+
Base64.urlsafe_encode64(hash, padding: false)
|
|
68
|
+
end
|
|
69
|
+
end
|
data/sidetree.gemspec
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/sidetree/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'sidetree'
|
|
7
|
+
spec.version = Sidetree::VERSION
|
|
8
|
+
spec.authors = ['azuchi']
|
|
9
|
+
spec.email = ['azuchi@chaintope.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Ruby implementation for Sidetree protocol.'
|
|
12
|
+
spec.description = 'Ruby implementation for Sidetree protocol.'
|
|
13
|
+
spec.homepage = 'https://github.com/azuchi/sidetreerb'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 2.6.0'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['changelog_uri'] = spec.homepage
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files =
|
|
24
|
+
Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
+
f.match(%r{\A(?:test|spec|features)/})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = 'exe'
|
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
31
|
+
spec.require_paths = ['lib']
|
|
32
|
+
|
|
33
|
+
# Uncomment to register a new dependency of your gem
|
|
34
|
+
spec.add_dependency 'ecdsa', '~> 1.2.0'
|
|
35
|
+
spec.add_dependency 'json-jwt', '~> 1.13.0'
|
|
36
|
+
spec.add_dependency 'json-canonicalization', '~> 0.3.0'
|
|
37
|
+
spec.add_dependency 'multihashes', '~> 0.2.0'
|
|
38
|
+
spec.add_runtime_dependency 'thor'
|
|
39
|
+
|
|
40
|
+
# For more information and examples about making a new gem, checkout our
|
|
41
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
42
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
43
|
+
spec.add_development_dependency 'prettier'
|
|
44
|
+
end
|