oydid 0.6.6 → 0.7.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.
@@ -1 +1 @@
1
- [null, "invalid response from https://oydid.ownyourdata.eu/doc/"]
1
+ [null,"invalid path"]
@@ -1 +1 @@
1
- [null, "unsupported key codec"]
1
+ [null,"unknown key codec"]
@@ -1 +1 @@
1
- ["z6Mv8s3VfPPUKRTx7fDKs7MKYDRghzfVxYjk6MdAXBvFNL6o", ""]
1
+ ["z6MkvkE1Gw5shxc8S6wNpfr8YNZWxgiUs6Yp6n45qdgTMoUF",""]
@@ -1 +1 @@
1
- [{"hello":"world","entry-hash":"zQmYGx7Wzqe5prvEsTSzYBQN8xViYUM9qsWJSF5EENLcNmM"}]
1
+ [{"hello":"world","entry-hash":"zQmSvPd3sHK7iWgZuW47fyLy4CaZQe2DwxvRhrJ39VpBVMK"}]
@@ -1 +1 @@
1
- [{"entry-hash": "zQmdEMAFX7jFuURk9AkDTg5EFAKMNxd3Wx5aXdbhcMv43Rq", "hello": "world", "op": 1, "sub-entry-hash": "zQmdEMAFX7jFuURk9AkDTg5EFAKMNxd3Wx5aXdbhcMv43Rq"}]
1
+ [{"hello":"world","op":1,"entry-hash":"zQmemaqk7duDBUQZud3FahCPe83kMC5kxqKxkxgSUpzVhq1","sub-entry-hash":"zQmemaqk7duDBUQZud3FahCPe83kMC5kxqKxkxgSUpzVhq1"}]
@@ -1 +1 @@
1
- [{"did":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","doc":{"doc":{"simple":"example"},"key":"z6MusYB5iT5krCHYsZ76EzBaTdRwGKsaBhMcSbrXaPJgkuRQ:z6Mv7EYihbAat6Wq7GsjNsjcxt58dZT8fmsRjQGTkYamYrjB","log":"zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe"},"log":[{"ts":1641224736,"op":2,"doc":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","sig":"z3Kb5qeReCqr3ftxpf2i5UypUwrzrVkyspMtaDcb6e9YdHVSptcAFgvwbgk3qWqspTcGiKDYKXZZh8g6XyM2WPmNp","previous":[]},{"ts":1641224736,"op":0,"doc":"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS","sig":"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU","previous":[]}],"doc_log_id":0,"termination_log_id":1,"error":0,"message":"","verification":"identifier: zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh\n✅ is hash of DID Document:\n{\n \"doc\": {\n \"simple\": \"example\"\n },\n \"key\": \"z6MusYB5iT5krCHYsZ76EzBaTdRwGKsaBhMcSbrXaPJgkuRQ:z6Mv7EYihbAat6Wq7GsjNsjcxt58dZT8fmsRjQGTkYamYrjB\",\n \"log\": \"zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe\"\n}\n(Details: https://ownyourdata.github.io/oydid/#calculate_hash)\n\n'log' reference in DID Document: zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe\n✅ is hash of TERMINATE log record:\n{\n \"ts\": 1641224736,\n \"op\": 0,\n \"doc\": \"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS\",\n \"sig\": \"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU\",\n \"previous\": [\n\n ]\n}\n(Details: https://ownyourdata.github.io/oydid/#calculate_hash)\n\nRevocation reference in log record: zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS\n✅ cannot find revocation record searching at\n- https://oydid.ownyourdata.eu\n(Details: https://ownyourdata.github.io/oydid/#retrieve_log)\n\n"}, ""]
1
+ [{"did":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","doc":{"doc":{"simple":"example"},"key":"z6MusYB5iT5krCHYsZ76EzBaTdRwGKsaBhMcSbrXaPJgkuRQ:z6Mv7EYihbAat6Wq7GsjNsjcxt58dZT8fmsRjQGTkYamYrjB","log":"zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe"},"log":[{"ts":1641224736,"op":2,"doc":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","sig":"z3Kb5qeReCqr3ftxpf2i5UypUwrzrVkyspMtaDcb6e9YdHVSptcAFgvwbgk3qWqspTcGiKDYKXZZh8g6XyM2WPmNp","previous":[]},{"ts":1641224736,"op":0,"doc":"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS","sig":"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU","previous":[]}],"doc_log_id":0,"termination_log_id":1,"error":0,"message":"","verification":"identifier: zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh\n✅ is hash of DID Document:\n{\n \"doc\": {\n \"simple\": \"example\"\n },\n \"key\": \"z6MusYB5iT5krCHYsZ76EzBaTdRwGKsaBhMcSbrXaPJgkuRQ:z6Mv7EYihbAat6Wq7GsjNsjcxt58dZT8fmsRjQGTkYamYrjB\",\n \"log\": \"zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe\"\n}\n(Details: https://ownyourdata.github.io/oydid/#calculate_hash)\n\n'log' reference in DID Document: zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe\n✅ is hash of TERMINATE log record:\n{\n \"ts\": 1641224736,\n \"op\": 0,\n \"doc\": \"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS\",\n \"sig\": \"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU\",\n \"previous\": []\n}\n(Details: https://ownyourdata.github.io/oydid/#calculate_hash)\n\nRevocation reference in log record: zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS\n✅ cannot find revocation record searching at\n- https://oydid.ownyourdata.eu\n(Details: https://ownyourdata.github.io/oydid/#retrieve_log)\n\n","full_log":[{"ts":1641224736,"op":2,"doc":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","sig":"z3Kb5qeReCqr3ftxpf2i5UypUwrzrVkyspMtaDcb6e9YdHVSptcAFgvwbgk3qWqspTcGiKDYKXZZh8g6XyM2WPmNp","previous":[]},{"ts":1641224736,"op":0,"doc":"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS","sig":"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU","previous":[]}]},""]
data/spec/oydid_spec.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require_relative 'spec_helper'
2
+ require 'tmpdir'
3
+ require 'securerandom'
4
+ require 'digest'
2
5
 
3
6
  describe "OYDID handling" do
4
7
  # basic functions - base58btc encoding
@@ -105,6 +108,133 @@ describe "OYDID handling" do
105
108
  expect(Oydid.multi_hash(data, {digest: "blake2b-64"}).first).to eq expected
106
109
  end
107
110
  end
111
+
112
+ # intermediate BLAKE2b digest sizes (17-23 bytes) ---------------------------
113
+ # Needed so a did:oyd fits into the 50 character URL limit of the EU Digital
114
+ # Product Passport registry: sha2-256 yields a 55 character identifier,
115
+ # blake2b-16 is only 128 bit and therefore too weak for a 30 year lifetime.
116
+ BLAKE2B_EXTRA_SIZES = (17..23).to_a
117
+
118
+ # Backwards compatibility: recorded before the intermediate sizes were added.
119
+ # multi_hash must stay byte-identical for every digest that existed before,
120
+ # otherwise DIDs already in the wild stop resolving.
121
+ legacy_hashes = JSON.parse(File.read(File.expand_path("../fixtures/multi_hash_legacy.json", __FILE__)))
122
+ legacy_hashes.group_by { |e| [e["digest"], e["encode"]] }.each do |(digest, encode), entries|
123
+ it "keeps multi_hash byte-identical for #{digest} in #{encode}" do
124
+ entries.each do |entry|
125
+ message = [entry["message"]].pack("H*")
126
+ expect(Oydid.multi_hash(message, {digest: digest, encode: encode}).first).to eq entry["expected"]
127
+ end
128
+ end
129
+ it "keeps get_digest working for #{digest} in #{encode}" do
130
+ entries.each do |entry|
131
+ expect(Oydid.get_digest(entry["expected"]).first).to eq entry["name"]
132
+ end
133
+ end
134
+ end
135
+
136
+ BLAKE2B_EXTRA_SIZES.each do |size|
137
+ digest = "blake2b-#{size}"
138
+
139
+ it "advertises #{digest} as supported" do
140
+ expect(Oydid::SUPPORTED_DIGESTS).to include digest
141
+ end
142
+
143
+ it "round-trips #{digest} through multi_hash and get_digest" do
144
+ hash = Oydid.multi_hash('{"a":1}', {digest: digest, encode: "base58btc"}).first
145
+ expect(hash).not_to be_nil
146
+ expect(Oydid.get_digest(hash).first).to eq digest
147
+ # the raw digest really has the requested size
148
+ decoded, _ = Oydid.multi_decode(hash)
149
+ expect(decoded.bytesize).to eq size + 2
150
+ expect(decoded[1].ord).to eq size
151
+ end
152
+
153
+ it "encodes #{digest} in every supported encoding" do
154
+ Oydid::SUPPORTED_ENCODINGS.each do |encoding|
155
+ hash = Oydid.multi_hash("payload", {digest: digest, encode: encoding}).first
156
+ expect(hash).not_to be_nil
157
+ expect(Oydid.get_digest(hash).first).to eq digest
158
+ end
159
+ end
160
+ end
161
+
162
+ # base58btc length varies with the leading byte, so pin the maximum over a
163
+ # few hundred random inputs rather than a single sample
164
+ {17 => 27, 18 => 28, 19 => 29, 20 => 31, 21 => 32, 22 => 34, 23 => 35}.each do |size, expected_length|
165
+ it "produces a base58btc identifier of at most #{expected_length} characters for blake2b-#{size}" do
166
+ lengths = 500.times.map do |i|
167
+ Oydid.multi_hash(SecureRandom.hex(24) + i.to_s, {digest: "blake2b-#{size}", encode: "base58btc"}).first.length
168
+ end
169
+ expect(lengths.max).to eq expected_length
170
+ expect("did:oyd:".length + lengths.max).to eq expected_length + 8
171
+ end
172
+ end
173
+
174
+ it "assigns the acceptance size for the DPP use case" do
175
+ identifier = Oydid.multi_hash(Oydid.canonical({"a" => 1}), {digest: "blake2b-18", encode: "base58btc"}).first
176
+ expect(identifier.length).to eq 28
177
+ expect(("did:oyd:" + identifier).length).to eq 36
178
+ end
179
+
180
+ it "assigns collision-free single byte codes to all supported digests" do
181
+ codes = Oydid::SUPPORTED_DIGESTS.map do |digest|
182
+ hash = Oydid.multi_hash("collision check", {digest: digest, encode: "base58btc"}).first
183
+ Oydid.multi_decode(hash).first[0].ord
184
+ end
185
+ expect(codes.length).to eq Oydid::SUPPORTED_DIGESTS.length
186
+ expect(codes.uniq.length).to eq codes.length
187
+ end
188
+
189
+ it "keeps the new codes out of the range used by the pre-existing digests" do
190
+ # 0x02/0x04/0x08 are blake2b-16/-32/-64, 0x12-0x17 are the SHA digests
191
+ expect(Oydid::BLAKE2B_EXTRA_CODES.values.sort).to eq (0x0B..0x11).to_a
192
+ expect(Oydid::BLAKE2B_EXTRA_CODES.keys.sort).to eq BLAKE2B_EXTRA_SIZES
193
+ expect(Oydid::BLAKE2B_EXTRA_CODES.values & [0x02, 0x04, 0x08, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17]).to be_empty
194
+ end
195
+
196
+ it "keeps the defaults unchanged" do
197
+ expect(Oydid::DEFAULT_DIGEST).to eq "sha2-256"
198
+ expect(Oydid::LOG_HASH_OPTIONS).to eq({:digest => "sha2-256", :encode => "base58btc"})
199
+ end
200
+
201
+ BLAKE2B_EXTRA_SIZES.each do |size|
202
+ it "creates and reads a DID with blake2b-#{size}" do
203
+ Dir.mktmpdir do |dir|
204
+ Dir.chdir(dir) do
205
+ result, msg = Oydid.create({"hello" => "dpp"}, {
206
+ digest: "blake2b-#{size}", encode: "base58btc",
207
+ doc_location: "local", location: "local",
208
+ return_secrets: true, key_type: "ed25519", silent: true})
209
+ expect(msg).to eq ""
210
+ expect(result).not_to be_nil
211
+ did = result["did"]
212
+ identifier = did.delete_prefix("did:oyd:").split(Oydid::LOCATION_PREFIX).first
213
+ expect(Oydid.get_digest(identifier).first).to eq "blake2b-#{size}"
214
+
215
+ read_result, read_msg = Oydid.read(did, {doc_location: "local", log_location: "local", silent: true})
216
+ expect(read_msg).to eq ""
217
+ expect(read_result).not_to be_nil
218
+ expect(read_result["doc"]["doc"]).to eq({"hello" => "dpp"})
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ it "does not shadow sha1, which shares code 0x11 with blake2b-23" do
225
+ # 0x11 is unused among the digests oydid produces, but the multicodec
226
+ # registry maps it to sha1. The length byte keeps the two apart: sha1 is
227
+ # always 20 bytes, blake2b-23 always 23.
228
+ sha1_multihash = Oydid.multi_encode([0x11, 20, Digest::SHA1.digest("test")].pack("CCa20"),
229
+ {encode: "base58btc"}).first
230
+ expect(Oydid.get_digest(sha1_multihash).first).to eq "sha1"
231
+ expect(Oydid.get_digest(Oydid.multi_hash("test", {digest: "blake2b-23"}).first).first).to eq "blake2b-23"
232
+ end
233
+
234
+ it "still rejects an unsupported digest" do
235
+ expect(Oydid.multi_hash("data", {digest: "blake2b-24"}).last).to eq "unsupported digest: 'blake2b-24'"
236
+ end
237
+
108
238
  Dir.glob(File.expand_path("../input/basic/*.json", __FILE__)).each do |input|
109
239
  it "converts #{input.split('/').last}" do
110
240
  expected = File.read(input.sub('input', 'output'))
@@ -123,7 +253,9 @@ describe "OYDID handling" do
123
253
  it "generates #{input.split('/').last}" do
124
254
  expected = File.read(input.sub('input', 'output'))
125
255
  data = File.read(input)
126
- expect(Oydid.generate_private_key(data, {}).first).to eq expected
256
+ # signature is (input, method, options) - passing options as the 2nd
257
+ # argument made this resolve the codec {} and always return nil
258
+ expect(Oydid.generate_private_key(data, "ed25519-priv", {}).first).to eq expected
127
259
  end
128
260
  end
129
261
  it "handles unknown key codec" do
data/spec/spec_helper.rb CHANGED
@@ -25,6 +25,9 @@ end
25
25
 
26
26
  require 'oydid'
27
27
 
28
+ # spec/support/http_stubs.rb keeps the suite off the network
29
+ Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |f| require f }
30
+
28
31
  ::RSpec.configure do |c|
29
32
  c.filter_run focus: true
30
33
  c.run_all_when_everything_filtered = true
@@ -0,0 +1,43 @@
1
+ # Keeps the suite off the public internet.
2
+ #
3
+ # Seven examples used to resolve DID documents and logs over HTTP against
4
+ # oydid.ownyourdata.eu and did2.data-container.net. That made the suite depend
5
+ # on production staying reachable and on data from 2022 never changing - a CI
6
+ # failure then said nothing about the code.
7
+ #
8
+ # The recorded responses live in spec/fixtures/http; manifest.json maps each URL
9
+ # to its status, content type and body file. To refresh one, fetch the URL and
10
+ # overwrite the body file - the expectations under spec/output must then be
11
+ # checked, not blindly regenerated.
12
+ #
13
+ # disable_net_connect! makes any unstubbed request fail loudly, so a newly added
14
+ # network call cannot slip in unnoticed.
15
+
16
+ require "json"
17
+ require "webmock/rspec"
18
+
19
+ WebMock.disable_net_connect!(allow_localhost: false)
20
+
21
+ module HttpFixtures
22
+ DIR = File.expand_path("../fixtures/http", __dir__)
23
+
24
+ def self.entries
25
+ @entries ||= JSON.parse(File.read(File.join(DIR, "manifest.json")))
26
+ end
27
+
28
+ def self.body(entry)
29
+ File.read(File.join(DIR, entry["body"]))
30
+ end
31
+ end
32
+
33
+ RSpec.configure do |config|
34
+ config.before(:each) do
35
+ HttpFixtures.entries.each do |entry|
36
+ stub_request(:get, entry["url"]).to_return(
37
+ status: entry["status"],
38
+ body: HttpFixtures.body(entry),
39
+ headers: { "Content-Type" => entry["content_type"] },
40
+ )
41
+ end
42
+ end
43
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oydid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.6
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Fabianek
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-19 00:00:00.000000000 Z
10
+ date: 2026-08-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: simple_dag
@@ -261,6 +261,16 @@ files:
261
261
  - lib/oydid/didcomm.rb
262
262
  - lib/oydid/log.rb
263
263
  - lib/oydid/vc.rb
264
+ - spec/fixtures/http/did2-data-container-doc.json
265
+ - spec/fixtures/http/manifest.json
266
+ - spec/fixtures/http/oydid-doc-did-zQmaBZ.json
267
+ - spec/fixtures/http/oydid-doc-empty.json
268
+ - spec/fixtures/http/oydid-doc-raw-zQmaBZ.json
269
+ - spec/fixtures/http/oydid-doc-zQmaBZ.json
270
+ - spec/fixtures/http/oydid-log-zQmVwM.json
271
+ - spec/fixtures/http/oydid-log-zQmZEP.json
272
+ - spec/fixtures/http/oydid-log-zQmaBZ.json
273
+ - spec/fixtures/multi_hash_legacy.json
264
274
  - spec/input/basic/arrays.json
265
275
  - spec/input/basic/french.json
266
276
  - spec/input/basic/sample2_get_location.doc
@@ -400,6 +410,7 @@ files:
400
410
  - spec/output/main/sample0_read.doc
401
411
  - spec/oydid_spec.rb
402
412
  - spec/spec_helper.rb
413
+ - spec/support/http_stubs.rb
403
414
  homepage: http://github.com/ownyourdata/oydid
404
415
  licenses:
405
416
  - Apache-2.0
@@ -415,7 +426,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
415
426
  requirements:
416
427
  - - ">="
417
428
  - !ruby/object:Gem::Version
418
- version: 3.0.0
429
+ version: 3.2.0
419
430
  required_rubygems_version: !ruby/object:Gem::Requirement
420
431
  requirements:
421
432
  - - ">="
@@ -426,6 +437,16 @@ rubygems_version: 4.0.12
426
437
  specification_version: 4
427
438
  summary: Own Your Decentralized Identifier for Ruby.
428
439
  test_files:
440
+ - spec/fixtures/http/did2-data-container-doc.json
441
+ - spec/fixtures/http/manifest.json
442
+ - spec/fixtures/http/oydid-doc-did-zQmaBZ.json
443
+ - spec/fixtures/http/oydid-doc-empty.json
444
+ - spec/fixtures/http/oydid-doc-raw-zQmaBZ.json
445
+ - spec/fixtures/http/oydid-doc-zQmaBZ.json
446
+ - spec/fixtures/http/oydid-log-zQmVwM.json
447
+ - spec/fixtures/http/oydid-log-zQmZEP.json
448
+ - spec/fixtures/http/oydid-log-zQmaBZ.json
449
+ - spec/fixtures/multi_hash_legacy.json
429
450
  - spec/input/basic/arrays.json
430
451
  - spec/input/basic/french.json
431
452
  - spec/input/basic/sample2_get_location.doc
@@ -565,3 +586,4 @@ test_files:
565
586
  - spec/output/main/sample0_read.doc
566
587
  - spec/oydid_spec.rb
567
588
  - spec/spec_helper.rb
589
+ - spec/support/http_stubs.rb