pplcdid 1.2.3

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