oydid 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +5 -0
  5. data/VERSION +1 -0
  6. data/lib/oydid/basic.rb +212 -0
  7. data/lib/oydid/log.rb +391 -0
  8. data/lib/oydid.rb +661 -0
  9. data/spec/input/basic/arrays.json +8 -0
  10. data/spec/input/basic/french.json +6 -0
  11. data/spec/input/basic/sample2_get_location.doc +1 -0
  12. data/spec/input/basic/sample2_retrieve_document.doc +1 -0
  13. data/spec/input/basic/sample3_retrieve_document.doc +1 -0
  14. data/spec/input/basic/sample4_retrieve_document.doc +1 -0
  15. data/spec/input/basic/sample5_retrieve_document.doc +1 -0
  16. data/spec/input/basic/sample_dec.doc +1 -0
  17. data/spec/input/basic/sample_enc.doc +1 -0
  18. data/spec/input/basic/sample_get_location.doc +1 -0
  19. data/spec/input/basic/sample_hash.doc +1 -0
  20. data/spec/input/basic/sample_invalid2_readkey.doc +1 -0
  21. data/spec/input/basic/sample_invalid2_verify.doc +1 -0
  22. data/spec/input/basic/sample_invalid3_readkey.doc +1 -0
  23. data/spec/input/basic/sample_invalid3_verify.doc +1 -0
  24. data/spec/input/basic/sample_invalid_privkey.doc +1 -0
  25. data/spec/input/basic/sample_invalid_readkey.doc +1 -0
  26. data/spec/input/basic/sample_invalid_sign.doc +1 -0
  27. data/spec/input/basic/sample_invalid_verify.doc +1 -0
  28. data/spec/input/basic/sample_key.doc +1 -0
  29. data/spec/input/basic/sample_readkey.doc +1 -0
  30. data/spec/input/basic/sample_retrieve_document.doc +1 -0
  31. data/spec/input/basic/sample_sign.doc +1 -0
  32. data/spec/input/basic/sample_valid_privkey.doc +1 -0
  33. data/spec/input/basic/sample_verify.doc +1 -0
  34. data/spec/input/basic/structures.json +8 -0
  35. data/spec/input/basic/unicode.json +3 -0
  36. data/spec/input/basic/values.json +5 -0
  37. data/spec/input/basic/wierd.json +11 -0
  38. data/spec/input/basic/zQmaBZTghn.doc +1 -0
  39. data/spec/input/log/sample0_dag2array.doc +1 -0
  40. data/spec/input/log/sample0_dag_did.doc +1 -0
  41. data/spec/input/log/sample1_dag_did.doc +1 -0
  42. data/spec/input/log/sample1_dag_update.doc +1 -0
  43. data/spec/input/log/sample2_dag_did.doc +1 -0
  44. data/spec/input/log/sample2_dag_update.doc +1 -0
  45. data/spec/input/log/sample2_retrieve_log.doc +1 -0
  46. data/spec/input/log/sample3_dag_did.doc +1 -0
  47. data/spec/input/log/sample3_dag_update.doc +1 -0
  48. data/spec/input/log/sample3_retrieve_log.doc +1 -0
  49. data/spec/input/log/sample4_dag_did.doc +1 -0
  50. data/spec/input/log/sample4_dag_update.doc +1 -0
  51. data/spec/input/log/sample4_retrieve_log.doc +1 -0
  52. data/spec/input/log/sample5_dag_update.doc +1 -0
  53. data/spec/input/log/sample5_retrieve_log.doc +1 -0
  54. data/spec/input/log/sample6_dag_update.doc +1 -0
  55. data/spec/input/log/sample6_retrieve_log.doc +1 -0
  56. data/spec/input/log/sample7_dag_update.doc +1 -0
  57. data/spec/input/log/sample7_retrieve_log.doc +1 -0
  58. data/spec/input/log/sample8_dag_update.doc +1 -0
  59. data/spec/input/log/sample_addhash.doc +1 -0
  60. data/spec/input/log/sample_dag_update.doc +1 -0
  61. data/spec/input/log/sample_match_log.doc +1 -0
  62. data/spec/input/log/sample_op1_addhash.doc +1 -0
  63. data/spec/input/log/sample_retrieve_log.doc +1 -0
  64. data/spec/input/main/sample0_read.doc +1 -0
  65. data/spec/output/basic/arrays.json +1 -0
  66. data/spec/output/basic/french.json +1 -0
  67. data/spec/output/basic/sample2_get_location.doc +1 -0
  68. data/spec/output/basic/sample2_retrieve_document.doc +1 -0
  69. data/spec/output/basic/sample3_retrieve_document.doc +1 -0
  70. data/spec/output/basic/sample4_retrieve_document.doc +1 -0
  71. data/spec/output/basic/sample5_retrieve_document.doc +1 -0
  72. data/spec/output/basic/sample_dec.doc +1 -0
  73. data/spec/output/basic/sample_enc.doc +1 -0
  74. data/spec/output/basic/sample_get_location.doc +1 -0
  75. data/spec/output/basic/sample_hash.doc +1 -0
  76. data/spec/output/basic/sample_invalid2_readkey.doc +1 -0
  77. data/spec/output/basic/sample_invalid2_verify.doc +1 -0
  78. data/spec/output/basic/sample_invalid3_readkey.doc +1 -0
  79. data/spec/output/basic/sample_invalid3_verify.doc +1 -0
  80. data/spec/output/basic/sample_invalid_privkey.doc +1 -0
  81. data/spec/output/basic/sample_invalid_readkey.doc +1 -0
  82. data/spec/output/basic/sample_invalid_sign.doc +1 -0
  83. data/spec/output/basic/sample_invalid_verify.doc +1 -0
  84. data/spec/output/basic/sample_key.doc +1 -0
  85. data/spec/output/basic/sample_readkey.doc +1 -0
  86. data/spec/output/basic/sample_retrieve_document.doc +1 -0
  87. data/spec/output/basic/sample_sign.doc +1 -0
  88. data/spec/output/basic/sample_valid_privkey.doc +1 -0
  89. data/spec/output/basic/sample_verify.doc +1 -0
  90. data/spec/output/basic/structures.json +1 -0
  91. data/spec/output/basic/unicode.json +1 -0
  92. data/spec/output/basic/values.json +1 -0
  93. data/spec/output/basic/wierd.json +1 -0
  94. data/spec/output/log/sample0_dag2array.doc +1 -0
  95. data/spec/output/log/sample0_dag_did.doc +1 -0
  96. data/spec/output/log/sample1_dag_did.doc +1 -0
  97. data/spec/output/log/sample1_dag_update.doc +1 -0
  98. data/spec/output/log/sample2_dag_did.doc +1 -0
  99. data/spec/output/log/sample2_dag_update.doc +1 -0
  100. data/spec/output/log/sample2_retrieve_log.doc +1 -0
  101. data/spec/output/log/sample3_dag_did.doc +1 -0
  102. data/spec/output/log/sample3_dag_update.doc +1 -0
  103. data/spec/output/log/sample3_retrieve_log.doc +1 -0
  104. data/spec/output/log/sample4_dag_did.doc +1 -0
  105. data/spec/output/log/sample4_dag_update.doc +1 -0
  106. data/spec/output/log/sample4_retrieve_log.doc +1 -0
  107. data/spec/output/log/sample5_dag_update.doc +1 -0
  108. data/spec/output/log/sample5_retrieve_log.doc +1 -0
  109. data/spec/output/log/sample6_dag_update.doc +1 -0
  110. data/spec/output/log/sample6_retrieve_log.doc +1 -0
  111. data/spec/output/log/sample7_dag_update.doc +1 -0
  112. data/spec/output/log/sample7_retrieve_log.doc +1 -0
  113. data/spec/output/log/sample8_dag_update.doc +1 -0
  114. data/spec/output/log/sample_addhash.doc +1 -0
  115. data/spec/output/log/sample_dag_update.doc +1 -0
  116. data/spec/output/log/sample_match_log.doc +1 -0
  117. data/spec/output/log/sample_op1_addhash.doc +1 -0
  118. data/spec/output/log/sample_retrieve_log.doc +1 -0
  119. data/spec/output/main/sample0_read.doc +1 -0
  120. data/spec/oydid_spec.rb +170 -0
  121. data/spec/spec_helper.rb +31 -0
  122. metadata +405 -0
data/lib/oydid.rb ADDED
@@ -0,0 +1,661 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'dag'
5
+ require 'rbnacl'
6
+ require 'ed25519'
7
+ require 'httparty'
8
+ require 'multibases'
9
+ require 'multihashes'
10
+ require 'multicodecs'
11
+ require 'json/canonicalization'
12
+ require 'oydid/basic'
13
+ require 'oydid/log'
14
+
15
+ class Oydid
16
+
17
+ LOCATION_PREFIX = "@"
18
+ DEFAULT_LOCATION = "https://oydid.ownyourdata.eu"
19
+
20
+
21
+ # expected DID format: did:oyd:123
22
+ def self.read(did, options)
23
+ # setup
24
+ currentDID = {
25
+ "did": did,
26
+ "doc": "",
27
+ "log": [],
28
+ "doc_log_id": nil,
29
+ "termination_log_id": nil,
30
+ "error": 0,
31
+ "message": "",
32
+ "verification": ""
33
+ }.transform_keys(&:to_s)
34
+ did_hash = did.delete_prefix("did:oyd:")
35
+ did10 = did_hash[0,10]
36
+
37
+ # get did location
38
+ did_location = ""
39
+ if !options[:doc_location].nil?
40
+ did_location = options[:doc_location]
41
+ end
42
+ if did_location.to_s == ""
43
+ if !options[:location].nil?
44
+ did_location = options[:location]
45
+ end
46
+ end
47
+ if did_location.to_s == ""
48
+ if did.include?(LOCATION_PREFIX)
49
+ tmp = did.split(LOCATION_PREFIX)
50
+ did = tmp[0]
51
+ did_location = tmp[1]
52
+ end
53
+ end
54
+ if did_location == ""
55
+ did_location = DEFAULT_LOCATION
56
+ end
57
+
58
+ # retrieve DID document
59
+ did_document = retrieve_document(did, did10 + ".doc", did_location, options)
60
+ if did_document.first.nil?
61
+ return [nil, did_document.last]
62
+ end
63
+ did_document = did_document.first
64
+ currentDID["doc"] = did_document
65
+ if options[:trace]
66
+ puts " .. DID document retrieved"
67
+ end
68
+
69
+ # get log location
70
+ log_hash = did_document["log"]
71
+ log_location = ""
72
+ if !options[:log_location].nil?
73
+ log_location = options[:log_location]
74
+ end
75
+ if log_location.to_s == ""
76
+ if !options[:location].nil?
77
+ log_location = options[:location]
78
+ end
79
+ end
80
+ if log_location.to_s == ""
81
+ if log_hash.include?(LOCATION_PREFIX)
82
+ hash_split = log_hash.split(LOCATION_PREFIX)
83
+ log_hash = hash_split[0]
84
+ log_location = hash_split[1]
85
+ end
86
+ end
87
+ if log_location == ""
88
+ log_location = DEFAULT_LOCATION
89
+ end
90
+
91
+ # retrieve and traverse log to get current DID state
92
+ log_array, msg = retrieve_log(log_hash, did10 + ".log", log_location, options)
93
+ if log_array.nil?
94
+ return [nil, msg]
95
+ else
96
+ if options[:trace]
97
+ puts " .. Log retrieved"
98
+ end
99
+ dag, create_index, terminate_index, msg = dag_did(log_array, options)
100
+ if dag.nil?
101
+ return [nil, msg]
102
+ end
103
+ if options[:trace]
104
+ puts " .. DAG with " + dag.vertices.length.to_s + " vertices and " + dag.edges.length.to_s + " edges, CREATE index: " + create_index.to_s
105
+ end
106
+ ordered_log_array = dag2array(dag, log_array, create_index, [], options)
107
+ ordered_log_array << log_array[terminate_index]
108
+ currentDID["log"] = ordered_log_array
109
+ if options[:trace]
110
+ if options[:silent].nil? || !options[:silent]
111
+ puts " vertex " + terminate_index.to_s + " at " + log_array[terminate_index]["ts"].to_s + " op: " + log_array[terminate_index]["op"].to_s + " doc: " + log_array[terminate_index]["doc"].to_s
112
+ end
113
+ end
114
+ currentDID["log"] = ordered_log_array
115
+ if options[:trace]
116
+ if options[:silent].nil? || !options[:silent]
117
+ dag.edges.each do |e|
118
+ puts " edge " + e.origin[:id].to_s + " <- " + e.destination[:id].to_s
119
+ end
120
+ end
121
+ end
122
+ currentDID = dag_update(currentDID, options)
123
+ if options[:log_complete]
124
+ currentDID["log"] = log_array
125
+ end
126
+
127
+ return [currentDID, ""]
128
+ end
129
+
130
+ end
131
+
132
+ def self.create(content, options)
133
+ return write(content, nil, "create", options)
134
+ end
135
+
136
+ def self.update(content, did, options)
137
+ return write(content, did, "update", options)
138
+ end
139
+
140
+ def self.generate_base(content, did, mode, options)
141
+ # input validation
142
+ did_doc = JSON.parse(content.to_json) rescue {}
143
+ if did_doc == {}
144
+ return [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "empty or invalid payload"]
145
+ end
146
+ did_old = nil
147
+ log_old = nil
148
+ prev_hash = []
149
+ revoc_log = nil
150
+ doc_location = options[:doc_location]
151
+ if options[:ts].nil?
152
+ ts = Time.now.to_i
153
+ else
154
+ ts = options[:ts]
155
+ end
156
+
157
+ if mode == "create" || mode == "clone"
158
+ operation_mode = 2 # CREATE
159
+ if options[:doc_key].nil?
160
+ if options[:doc_enc].nil?
161
+ privateKey, msg = generate_private_key(options[:doc_pwd].to_s, 'ed25519-priv')
162
+ else
163
+ privateKey, msg = decode_private_key(options[:doc_enc].to_s)
164
+ end
165
+ else
166
+ privateKey, msg = read_private_key(options[:doc_key].to_s)
167
+ if privateKey.nil?
168
+ return [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "private document key not found"]
169
+ end
170
+ end
171
+ if options[:rev_key].nil?
172
+ if options[:rev_enc].nil?
173
+ revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv')
174
+ else
175
+ revocationKey, msg = decode_private_key(options[:rev_enc].to_s)
176
+ end
177
+ else
178
+ revocationKey, msg = read_private_key(options[:rev_key].to_s)
179
+ if revocationKey.nil?
180
+ return [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "private revocation key not found"]
181
+ end
182
+ end
183
+ else # mode == "update" => read information
184
+ did_info, msg = read(did, options)
185
+ if did_info.nil?
186
+ return [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "cannot resolve DID (on updating DID)"]
187
+ end
188
+ if did_info["error"] != 0
189
+ return [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, did_info["message"].to_s]
190
+ end
191
+
192
+ did = did_info["did"]
193
+ did_hash = did.delete_prefix("did:oyd:")
194
+ did10 = did_hash[0,10]
195
+ if doc_location.to_s == ""
196
+ if did_hash.include?(LOCATION_PREFIX)
197
+ hash_split = did_hash.split(LOCATION_PREFIX)
198
+ did_hash = hash_split[0]
199
+ doc_location = hash_split[1]
200
+ end
201
+ end
202
+ operation_mode = 3 # UPDATE
203
+
204
+ # collect relevant information from previous did
205
+ did_old = did.dup
206
+ did10_old = did10.dup
207
+ log_old = did_info["log"]
208
+ privateKey_old = read_private_storage(did10_old + "_private_key.b58")
209
+ revocationKey_old = read_private_storage(did10_old + "_revocation_key.b58")
210
+
211
+ # key management
212
+ if options[:doc_key].nil?
213
+ if options[:doc_enc].nil?
214
+ privateKey, msg = generate_private_key(options[:doc_pwd].to_s, 'ed25519-priv')
215
+ else
216
+ privateKey, msg = decode_private_key(options[:doc_enc].to_s)
217
+ end
218
+ else
219
+ privateKey, msg = read_private_key(options[:doc_key].to_s)
220
+ end
221
+ if options[:rev_key].nil? && options[:rev_pwd].nil?
222
+ revocationKey, msg = generate_private_key("", 'ed25519-priv')
223
+ revocationLog = read_private_storage(did10 + "_revocation.json")
224
+ else
225
+ if options[:rev_key].nil?
226
+ if options[:rev_enc].nil?
227
+ revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv')
228
+ else
229
+ revocationKey, msg = decode_private_key(options[:rev_enc].to_s)
230
+ end
231
+ else
232
+ revocationKey, msg = read_private_key(options[:rev_key].to_s)
233
+ end
234
+
235
+ # re-build revocation document
236
+ did_old_doc = did_info["doc"]["doc"]
237
+ ts_old = did_info["log"].last["ts"]
238
+ publicKey_old = public_key(privateKey_old).first
239
+ pubRevoKey_old = public_key(revocationKey_old).first
240
+ did_key_old = publicKey_old + ":" + pubRevoKey_old
241
+ subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
242
+ subDidHash = hash(subDid)
243
+ signedSubDidHash = sign(subDidHash, revocationKey_old).first
244
+ revocationLog = {
245
+ "ts": ts_old,
246
+ "op": 1, # REVOKE
247
+ "doc": subDidHash,
248
+ "sig": signedSubDidHash }.transform_keys(&:to_s).to_json
249
+ end
250
+ revoc_log = JSON.parse(revocationLog)
251
+ revoc_log["previous"] = [
252
+ hash(canonical(log_old[did_info["doc_log_id"].to_i])),
253
+ hash(canonical(log_old[did_info["termination_log_id"].to_i]))
254
+ ]
255
+ prev_hash = [hash(canonical(revoc_log))]
256
+ end
257
+
258
+ publicKey = public_key(privateKey).first
259
+ pubRevoKey = public_key(revocationKey).first
260
+ did_key = publicKey + ":" + pubRevoKey
261
+
262
+ # build new revocation document
263
+ subDid = {"doc": did_doc, "key": did_key}.to_json
264
+ subDidHash = hash(canonical(subDid))
265
+ signedSubDidHash = sign(subDidHash, revocationKey).first
266
+ r1 = { "ts": ts,
267
+ "op": 1, # REVOKE
268
+ "doc": subDidHash,
269
+ "sig": signedSubDidHash }.transform_keys(&:to_s)
270
+
271
+ # build termination log entry
272
+ l2_doc = hash(canonical(r1))
273
+ if !doc_location.nil?
274
+ l2_doc += LOCATION_PREFIX + doc_location.to_s
275
+ end
276
+ l2 = { "ts": ts,
277
+ "op": 0, # TERMINATE
278
+ "doc": l2_doc,
279
+ "sig": sign(l2_doc, privateKey).first,
280
+ "previous": [] }.transform_keys(&:to_s)
281
+
282
+ # build actual DID document
283
+ log_str = hash(canonical(l2))
284
+ if !doc_location.nil?
285
+ log_str += LOCATION_PREFIX + doc_location.to_s
286
+ end
287
+ didDocument = { "doc": did_doc,
288
+ "key": did_key,
289
+ "log": log_str }.transform_keys(&:to_s)
290
+
291
+ # create DID
292
+ l1_doc = hash(canonical(didDocument))
293
+ if !doc_location.nil?
294
+ l1_doc += LOCATION_PREFIX + doc_location.to_s
295
+ end
296
+ did = "did:oyd:" + l1_doc
297
+ did10 = l1_doc[0,10]
298
+
299
+ if mode == "clone"
300
+ # create log entry for source DID
301
+ new_log = {
302
+ "ts": ts,
303
+ "op": 4, # CLONE
304
+ "doc": l1_doc,
305
+ "sig": sign(l1_doc, privateKey).first,
306
+ "previous": [options[:previous_clone].to_s]
307
+ }
308
+ retVal = HTTParty.post(options[:source_location] + "/log/" + options[:source_did],
309
+ headers: { 'Content-Type' => 'application/json' },
310
+ body: {"log": new_log}.to_json )
311
+ prev_hash = [hash(canonical(new_log))]
312
+ end
313
+
314
+ # build creation log entry
315
+ if operation_mode == 3 # UPDATE
316
+ l1 = { "ts": ts,
317
+ "op": operation_mode, # UPDATE
318
+ "doc": l1_doc,
319
+ "sig": sign(l1_doc, privateKey_old).first,
320
+ "previous": prev_hash }.transform_keys(&:to_s)
321
+ else
322
+ l1 = { "ts": ts,
323
+ "op": operation_mode, # CREATE
324
+ "doc": l1_doc,
325
+ "sig": sign(l1_doc, privateKey).first,
326
+ "previous": prev_hash }.transform_keys(&:to_s)
327
+ end
328
+
329
+ return [did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, ""]
330
+ end
331
+
332
+ def self.publish(did, didDocument, logs, options)
333
+ did_hash = did.delete_prefix("did:oyd:")
334
+ did10 = did_hash[0,10]
335
+
336
+ doc_location = options[:doc_location]
337
+ if doc_location.to_s == ""
338
+ if did_hash.include?(LOCATION_PREFIX)
339
+ hash_split = did_hash.split(LOCATION_PREFIX)
340
+ did_hash = hash_split[0]
341
+ doc_location = hash_split[1]
342
+ else
343
+ doc_location = DEFAULT_LOCATION
344
+ end
345
+ end
346
+
347
+ # wirte data based on location
348
+ case doc_location.to_s
349
+ when /^http/
350
+ # build object to post
351
+ did_data = {
352
+ "did": did,
353
+ "did-document": didDocument,
354
+ "logs": logs
355
+ }
356
+ oydid_url = doc_location.to_s + "/doc"
357
+ retVal = HTTParty.post(oydid_url,
358
+ headers: { 'Content-Type' => 'application/json' },
359
+ body: did_data.to_json )
360
+ if retVal.code != 200
361
+ err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc"
362
+ return [false, err_msg]
363
+ end
364
+ else
365
+ # write files to disk
366
+ write_private_storage(logs.to_json, did10 + ".log")
367
+ write_private_storage(didDocument.to_json, did10 + ".doc")
368
+ write_private_storage(did, did10 + ".did")
369
+ end
370
+ return [true, ""]
371
+
372
+ end
373
+
374
+ def self.write(content, did, mode, options)
375
+ did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, msg = generate_base(content, did, mode, options)
376
+ if msg != ""
377
+ return [nil, msg]
378
+ end
379
+
380
+ did_hash = did.delete_prefix("did:oyd:")
381
+ did10 = did_hash[0,10]
382
+ did_old_hash = did_old.delete_prefix("did:oyd:") rescue nil
383
+ did10_old = did_old_hash[0,10] rescue nil
384
+
385
+ doc_location = options[:doc_location]
386
+ if doc_location.to_s == ""
387
+ if did_hash.include?(LOCATION_PREFIX)
388
+ hash_split = did_hash.split(LOCATION_PREFIX)
389
+ did_hash = hash_split[0]
390
+ doc_location = hash_split[1]
391
+ else
392
+ doc_location = DEFAULT_LOCATION
393
+ end
394
+ end
395
+
396
+ case doc_location.to_s
397
+ when /^http/
398
+ logs = [revoc_log, l1, l2].flatten.compact
399
+ else
400
+ logs = [log_old, revoc_log, l1, l2].flatten.compact
401
+ if !did_old.nil?
402
+ write_private_storage([log_old, revoc_log, l1, l2].flatten.compact.to_json, did10_old + ".log")
403
+ end
404
+ end
405
+ success, msg = publish(did, didDocument, logs, options)
406
+
407
+ if success
408
+ w3c_input = {
409
+ "did" => did,
410
+ "doc" => didDocument
411
+ }
412
+ retVal = {
413
+ "did" => did,
414
+ "doc" => didDocument,
415
+ "doc_w3c" => w3c(w3c_input, options),
416
+ "log" => logs
417
+ }
418
+ if options[:return_secrets]
419
+ retVal["private_key"] = privateKey
420
+ retVal["revocation_key"] = revocationKey
421
+ retVal["revocation_log"] = r1
422
+ else
423
+ write_private_storage(privateKey, did10 + "_private_key.b58")
424
+ write_private_storage(revocationKey, did10 + "_revocation_key.b58")
425
+ write_private_storage(r1.to_json, did10 + "_revocation.json")
426
+ end
427
+
428
+ return [retVal, ""]
429
+ else
430
+ return [nil, msg]
431
+ end
432
+ end
433
+
434
+ def self.revoke(did, options)
435
+ did_orig = did.dup
436
+ doc_location = options[:doc_location]
437
+ if options[:ts].nil?
438
+ ts = Time.now.to_i
439
+ else
440
+ ts = options[:ts]
441
+ end
442
+ did_info, msg = read(did, options)
443
+ if did_info.nil?
444
+ return [nil, "cannot resolve DID (on revoking DID)"]
445
+ end
446
+ if did_info["error"] != 0
447
+ return [nil, did_info["message"].to_s]
448
+ end
449
+
450
+ did = did_info["did"]
451
+ did_hash = did.delete_prefix("did:oyd:")
452
+ did10 = did_hash[0,10]
453
+ if doc_location.to_s == ""
454
+ if did_hash.include?(LOCATION_PREFIX)
455
+ hash_split = did_hash.split(LOCATION_PREFIX)
456
+ did_hash = hash_split[0]
457
+ doc_location = hash_split[1]
458
+ end
459
+ end
460
+
461
+ # collect relevant information from previous did
462
+ did_old = did.dup
463
+ did10_old = did10.dup
464
+ log_old = did_info["log"]
465
+ privateKey_old = read_private_storage(did10_old + "_private_key.b58")
466
+ revocationKey_old = read_private_storage(did10_old + "_revocation_key.b58")
467
+
468
+ if options[:doc_key].nil?
469
+ if options[:doc_pwd].nil?
470
+ if options[:doc_enc].nil?
471
+ privateKey, msg = read_private_key(did10 + "_private_key.b58")
472
+ else
473
+ privateKey, msg = decode_private_key(options[:doc_enc].to_s)
474
+ end
475
+ else
476
+ privateKey, msg = generate_private_key(options[:doc_pwd].to_s, 'ed25519-priv')
477
+ end
478
+ else
479
+ privateKey, msg = read_private_key(options[:doc_key].to_s)
480
+ end
481
+ if privateKey.nil?
482
+ return [nil, "private key not found"]
483
+ end
484
+ if options[:rev_key].nil? && options[:rev_pwd].nil? && options[:rev_enc].nil?
485
+ revocationKey, msg = read_private_key(did10 + "_revocation_key.b58")
486
+ revocationLog = read_private_storage(did10 + "_revocation.json")
487
+ else
488
+ if options[:rev_pwd].nil?
489
+ if options[:rev_enc].nil?
490
+ revocationKey, msg = read_private_key(options[:rev_key].to_s)
491
+ else
492
+ revocationKey, msg = decode_private_key(options[:rev_enc].to_s)
493
+ end
494
+ else
495
+ revocationKey, msg = generate_private_key(options[:rev_pwd].to_s, 'ed25519-priv')
496
+ end
497
+ # re-build revocation document
498
+ did_old_doc = did_info["doc"]["doc"]
499
+ ts_old = did_info["log"].last["ts"]
500
+ publicKey_old = public_key(privateKey_old).first
501
+ pubRevoKey_old = public_key(revocationKey_old).first
502
+ did_key_old = publicKey_old + ":" + pubRevoKey_old
503
+ subDid = {"doc": did_old_doc, "key": did_key_old}.to_json
504
+ subDidHash = hash(subDid)
505
+ signedSubDidHash = sign(subDidHash, revocationKey).first
506
+ revocationLog = {
507
+ "ts": ts_old,
508
+ "op": 1, # REVOKE
509
+ "doc": subDidHash,
510
+ "sig": signedSubDidHash }.transform_keys(&:to_s).to_json
511
+ end
512
+
513
+ if revocationLog.nil?
514
+ return [nil, "private revocation key not found"]
515
+ end
516
+
517
+ revoc_log = JSON.parse(revocationLog)
518
+ revoc_log["previous"] = [
519
+ hash(canonical(log_old[did_info["doc_log_id"].to_i])),
520
+ hash(canonical(log_old[did_info["termination_log_id"].to_i]))
521
+ ]
522
+
523
+ if doc_location.to_s == ""
524
+ doc_location = DEFAULT_LOCATION
525
+ end
526
+
527
+ # publish revocation log based on location
528
+ case doc_location.to_s
529
+ when /^http/
530
+ retVal = HTTParty.post(doc_location.to_s + "/log/" + did.to_s,
531
+ headers: { 'Content-Type' => 'application/json' },
532
+ body: {"log": revoc_log}.to_json )
533
+ if retVal.code != 200
534
+ msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/log/" + did.to_s
535
+ return [nil, msg]
536
+ end
537
+ else
538
+ File.write(did10 + ".log", [log_old, revoc_log].flatten.compact.to_json)
539
+ if !did_old.nil?
540
+ File.write(did10_old + ".log", [log_old, revoc_log].flatten.compact.to_json)
541
+ end
542
+ end
543
+
544
+ return [did_orig, ""]
545
+
546
+ end
547
+
548
+ def self.clone(did, options)
549
+ # check if locations differ
550
+ target_location = options[:doc_location]
551
+ if target_location.to_s == ""
552
+ target_location = DEFAULT_LOCATION
553
+ end
554
+ if did.include?(LOCATION_PREFIX)
555
+ hash_split = did.split(LOCATION_PREFIX)
556
+ did = hash_split[0]
557
+ source_location = hash_split[1]
558
+ end
559
+ if source_location.to_s == ""
560
+ source_location = DEFAULT_LOCATION
561
+ end
562
+ if target_location == source_location
563
+ return [nil, "cannot clone to same location (" + target_location.to_s + ")"]
564
+ end
565
+
566
+ # get original did info
567
+ options[:doc_location] = source_location
568
+ options[:log_location] = source_location
569
+ source_did, msg = read(did, options)
570
+ if source_did.nil?
571
+ return [nil, "cannot resolve DID (on cloning DID)"]
572
+ end
573
+ if source_did["error"] != 0
574
+ return [nil, source_did["message"].to_s]
575
+ end
576
+ if source_did["doc_log_id"].nil?
577
+ return [nil, "cannot parse DID log"]
578
+ end
579
+ source_log = source_did["log"].first(source_did["doc_log_id"] + 1).last.to_json
580
+
581
+ # write did to new location
582
+ options[:doc_location] = target_location
583
+ options[:log_location] = target_location
584
+ options[:previous_clone] = hash(canonical(source_log)) + LOCATION_PREFIX + source_location
585
+ options[:source_location] = source_location
586
+ options[:source_did] = source_did["did"]
587
+ retVal, msg = write(source_did["doc"]["doc"], nil, "clone", options)
588
+ return [retVal, msg]
589
+ end
590
+
591
+ def self.w3c(did_info, options)
592
+ did = did_info["did"]
593
+ if !did.start_with?("did:oyd:")
594
+ did = "did:oyd:" + did
595
+ end
596
+
597
+ didDoc = did_info.transform_keys(&:to_s)["doc"]
598
+ pubDocKey = didDoc["key"].split(":")[0] rescue ""
599
+ pubRevKey = didDoc["key"].split(":")[1] rescue ""
600
+
601
+ wd = {}
602
+ wd["@context"] = "https://www.w3.org/ns/did/v1"
603
+ wd["id"] = did
604
+ wd["verificationMethod"] = [{
605
+ "id": did,
606
+ "type": "Ed25519VerificationKey2020",
607
+ "controller": did,
608
+ "publicKeyBase58": pubDocKey
609
+ }]
610
+ wd["keyAgreement"] = [{
611
+ "id": did,
612
+ "type": "Ed25519VerificationKey2020",
613
+ "controller": did,
614
+ "publicKeyBase58": pubRevKey
615
+ }]
616
+
617
+ if didDoc["@context"].to_s == "https://www.w3.org/ns/did/v1"
618
+ didDoc.delete("@context")
619
+ end
620
+ if didDoc["doc"].to_s != ""
621
+ didDoc = didDoc["doc"]
622
+ end
623
+ newDidDoc = []
624
+ if didDoc.is_a?(Hash)
625
+ if didDoc["authentication"].to_s != ""
626
+ wd["authentication"] = didDoc["authentication"]
627
+ didDoc.delete("authentication")
628
+ end
629
+ if didDoc["service"].to_s != ""
630
+ if didDoc["service"].is_a?(Array)
631
+ newDidDoc = didDoc.dup
632
+ newDidDoc.delete("service")
633
+ if newDidDoc == {}
634
+ newDidDoc = []
635
+ else
636
+ if !newDidDoc.is_a?(Array)
637
+ newDidDoc=[newDidDoc]
638
+ end
639
+ end
640
+ newDidDoc << didDoc["service"]
641
+ newDidDoc = newDidDoc.flatten
642
+ end
643
+ else
644
+ newDidDoc = didDoc
645
+ end
646
+ else
647
+ newDidDoc = didDoc
648
+ end
649
+ wd["service"] = newDidDoc
650
+ return wd
651
+ end
652
+
653
+ def self.fromW3C(didDocument, options)
654
+ didDocument = didDocument.transform_keys(&:to_s)
655
+ if didDocument["@context"].to_s == "https://www.w3.org/ns/did/v1"
656
+ didDocument.delete("@context")
657
+ end
658
+ didDocument
659
+ end
660
+
661
+ end
@@ -0,0 +1,8 @@
1
+ [
2
+ 56,
3
+ {
4
+ "d": true,
5
+ "10": null,
6
+ "1": [ ]
7
+ }
8
+ ]
@@ -0,0 +1,6 @@
1
+ {
2
+ "peach": "This sorting order",
3
+ "péché": "is wrong according to French",
4
+ "pêche": "but canonicalization MUST",
5
+ "sin": "ignore locale"
6
+ }
@@ -0,0 +1 @@
1
+ did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W%40did2.data-container.net
@@ -0,0 +1 @@
1
+ {"doc_hash": "did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W", "doc_file": "", "doc_location": "did2.data-container.net", "options":{"trace":true, "ts": 0, "doc-pwd": "pwd1", "rev-pwd": "pwd2"}}
@@ -0,0 +1 @@
1
+ {"doc_hash": "", "doc_file": "", "doc_location": "", "options":{"trace":false, "ts": 0, "doc-pwd": "pwd1", "rev-pwd": "pwd2"}}
@@ -0,0 +1 @@
1
+ {"doc_hash": "123", "doc_file": "123", "doc_location": "local", "options":{"trace":false, "ts": 0, "doc-pwd": "pwd1", "rev-pwd": "pwd2"}}
@@ -0,0 +1 @@
1
+ {"doc_hash": "zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh", "doc_file": "./spec/input/basic/zQmaBZTghn.doc", "doc_location": "local", "options":{"trace":false, "ts": 0, "doc-pwd": "pwd1", "rev-pwd": "pwd2"}}
@@ -0,0 +1 @@
1
+ zStV1DL6CwTryKyV