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