oydid 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cd355cc4bdc04a3d149199467cbbe4263ace619676422c722485fc6d459c83b
4
- data.tar.gz: 85f8930282e7cbee45997d65a5cca1be2bea78f853cce44152d6b93808e1a923
3
+ metadata.gz: e8b1fc642be72b8b9a92b137f995360b5d4e68742b5a9744b59b3e88649976bb
4
+ data.tar.gz: 291e766737dec1b5756de3408097f5f243b02d3119ab3be2a29444957e82e9af
5
5
  SHA512:
6
- metadata.gz: e04e8cd58b7a29bb9e7dabffffcf613c9459c18ece1254b8cc1ad60b4d19d073276f18bd0f9c36b7cd6786340e1f81fb9be320f8964a182d5b861bfab9d5825f
7
- data.tar.gz: 370d28f6c8614238b734a9a0dc8410de4007923c6ac7e464190ad36bd82bbba5f28def3e46d05c74d882b5ba5559f11cadf2a4a26b2ea0045ea87acd8a6503bb
6
+ metadata.gz: 04bf2f70fcfe5200ed15417dbcd57734b3fe33603bb36eb4dc5007821d6c27af135a3b37552fd75c42a9a1feb02453411a47106340b9abb836496df679ec7b99
7
+ data.tar.gz: fd8d594757ecf3119c409b0d6169e607a823b1a575fcb1b5599625c2f305f580e51dee886a3112217272695b69e1da9ca65d1b3b1599a5908f916bce12aaacc1
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.5
1
+ 0.6.7
data/lib/oydid/basic.rb CHANGED
@@ -791,9 +791,13 @@ class Oydid
791
791
  rescue
792
792
  return [nil, "cannot read file"]
793
793
  end
794
- key_type = get_keytype(key_encoded) || options[:key_type] rescue options[:key_type]
794
+ # get_keytype raises for anything that is not a valid key, and the
795
+ # inline rescue below only guards the assignment - so key_type can end up
796
+ # nil when the caller passes no :key_type either. Coerce to a string
797
+ # instead of calling String methods on nil (raised NoMethodError before).
798
+ key_type = (get_keytype(key_encoded) || options[:key_type] rescue options[:key_type]).to_s
795
799
  if key_type.include?('-')
796
- key_type = key_type.split('-').first || options[:key_type] rescue options[:key_type]
800
+ key_type = (key_type.split('-').first || options[:key_type]).to_s
797
801
  end
798
802
  if key_type == 'p256'
799
803
  begin
@@ -1012,14 +1016,23 @@ class Oydid
1012
1016
  end
1013
1017
  end
1014
1018
 
1015
- # build an OYDID multibase-encoded private key from a raw hex-encoded key
1016
- # (e.g. an externally generated P-256 or Ed25519 key). The key type is taken
1017
- # from options[:key_type] (default 'ed25519').
1018
- def self.private_key_from_hex(hex, options = {})
1019
+ # normalize and validate hex input shared by the hex-based converters below:
1020
+ # strips whitespace and an optional '0x' prefix and rejects anything that is
1021
+ # not an even number of hex digits. Returns [normalized_hex, ""] or [nil, msg].
1022
+ def self.normalize_hex(hex)
1019
1023
  hex = hex.to_s.strip.delete_prefix("0x").delete_prefix("0X")
1020
1024
  unless hex =~ /\A[0-9a-fA-F]+\z/ && hex.length.even?
1021
1025
  return [nil, "invalid hex input"]
1022
1026
  end
1027
+ return [hex, ""]
1028
+ end
1029
+
1030
+ # build an OYDID multibase-encoded private key from a raw hex-encoded key
1031
+ # (e.g. an externally generated P-256 or Ed25519 key). The key type is taken
1032
+ # from options[:key_type] (default 'ed25519').
1033
+ def self.private_key_from_hex(hex, options = {})
1034
+ hex, msg = normalize_hex(hex)
1035
+ return [nil, msg] if hex.nil?
1023
1036
  raw = [hex].pack("H*")
1024
1037
  key_type = options[:key_type].to_s
1025
1038
  key_type = "ed25519" if key_type == ""
@@ -1047,6 +1060,63 @@ class Oydid
1047
1060
  return multi_encode([code, length, raw].pack("SCa#{length}"), options)
1048
1061
  end
1049
1062
 
1063
+ # counterpart to private_key_from_hex for public keys.
1064
+ #
1065
+ # NOTE on the p256 length check: multicodec 0x1200 ('p256-pub') is specified
1066
+ # for compressed points, but Oydid.public_key() emits uncompressed ones
1067
+ # (OpenSSL's default point_conversion_form), so both forms are accepted here
1068
+ # to stay compatible with keys this library produces itself.
1069
+ def self.public_key_from_hex(hex, options = {})
1070
+ hex, msg = normalize_hex(hex)
1071
+ return [nil, msg] if hex.nil?
1072
+ raw = [hex].pack("H*")
1073
+ key_type = options[:key_type].to_s
1074
+ key_type = "ed25519" if key_type == ""
1075
+ case key_type
1076
+ when "p256"
1077
+ unless [33, 65].include?(raw.bytesize)
1078
+ return [nil, "p256 public key must be 33 (compressed) or 65 (uncompressed) bytes"]
1079
+ end
1080
+ # the length alone says nothing: verify that the input actually
1081
+ # decodes to a point on prime256v1 (this also rejects a wrong
1082
+ # leading byte), otherwise an unusable key would be encoded happily
1083
+ begin
1084
+ OpenSSL::PKey::EC::Point.new(
1085
+ OpenSSL::PKey::EC::Group.new("prime256v1"),
1086
+ OpenSSL::BN.new(hex, 16))
1087
+ rescue StandardError
1088
+ return [nil, "p256 public key is not a valid point on the curve"]
1089
+ end
1090
+ code = Multicodecs["p256-pub"].code
1091
+ when "ed25519"
1092
+ unless raw.bytesize == 32
1093
+ return [nil, "ed25519 public key must be 32 bytes (64 hex characters)"]
1094
+ end
1095
+ # there is no cheap point validation for ed25519 (Ed25519::VerifyKey
1096
+ # accepts any 32 bytes), and a private key has the same length - so
1097
+ # passing a *private* key with --public silently yields a public key
1098
+ # multibase. Callers have to know what they hand in.
1099
+ code = Multicodecs["ed25519-pub"].code
1100
+ else
1101
+ return [nil, "unsupported key type"]
1102
+ end
1103
+ return multi_encode(Multibases::DecodedByteArray.new((to_varint(code) << raw.bytes).flatten).to_s(Encoding::BINARY), options)
1104
+ end
1105
+
1106
+ # convert raw hex data (no multicodec prefix, e.g. a signature) to Multibase
1107
+ def self.hex_to_multibase(hex, options = {})
1108
+ hex, msg = normalize_hex(hex)
1109
+ return [nil, msg] if hex.nil?
1110
+ # the multibases gem cannot represent an all-zero byte string: packing
1111
+ # raises NoMethodError and unpacking the correct base58btc form ("z1111")
1112
+ # raises RangeError. Leading zero bytes in otherwise non-zero input are
1113
+ # handled correctly, so only the all-zero case has to be rejected.
1114
+ if hex.delete("0") == ""
1115
+ return [nil, "all-zero input is not supported by the multibase encoder"]
1116
+ end
1117
+ return multi_encode([hex].pack("H*"), options)
1118
+ end
1119
+
1050
1120
  # reverse of private_key_from_hex / public_key encoding:
1051
1121
  # decode an OYDID Multibase-encoded key (private or public) back to raw hex.
1052
1122
  def self.key_to_hex(key_encoded, options = {})
@@ -0,0 +1 @@
1
+ {"doc":{"location":"non-default"},"key":"z6MutdYiDqv5kiJ79KXhcSyD38RZTRRhHYkhBo16891QsVmV:z6Mv8NqXsiUXeHVgJGCeX3JSnGwD17JKxMHep47SfSEy4u8Y","log":"zQmQwzGTZk1Me6GKWe6egJXGtRmoR3dfymVci4yqePqS8SN@https://did2.data-container.net"}
@@ -0,0 +1,56 @@
1
+ [
2
+ {
3
+ "url": "https://did2.data-container.net/doc/did:oyd:zQmNauTUUdkpi5TcrTZ2524SKM8dJAzuuw4xfW13iHrtY1W",
4
+ "status": 200,
5
+ "content_type": "application/json; charset=utf-8",
6
+ "body": "did2-data-container-doc.json"
7
+ },
8
+ {
9
+ "url": "https://oydid.ownyourdata.eu/doc/",
10
+ "status": 404,
11
+ "content_type": "application/json; charset=utf-8",
12
+ "body": "oydid-doc-empty.json"
13
+ },
14
+ {
15
+ "url": "https://oydid.ownyourdata.eu/doc/did:oyd:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
16
+ "status": 200,
17
+ "content_type": "application/json; charset=utf-8",
18
+ "body": "oydid-doc-did-zQmaBZ.json"
19
+ },
20
+ {
21
+ "url": "https://oydid.ownyourdata.eu/log/zQmZEP95SPr699GjBeN82cspsZww8RKKHLR9UChbZxKw3H7",
22
+ "status": 200,
23
+ "content_type": "application/json; charset=utf-8",
24
+ "body": "oydid-log-zQmZEP.json"
25
+ },
26
+ {
27
+ "url": "https://www.orf.at/log/zQmZEP95SPr699GjBeN82cspsZww8RKKHLR9UChbZxKw3H",
28
+ "status": 404,
29
+ "content_type": "text/html;charset=utf-8",
30
+ "body": "orf-at-log.html"
31
+ },
32
+ {
33
+ "url": "https://oydid.ownyourdata.eu/doc/zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
34
+ "status": 200,
35
+ "content_type": "application/json; charset=utf-8",
36
+ "body": "oydid-doc-zQmaBZ.json"
37
+ },
38
+ {
39
+ "url": "https://oydid.ownyourdata.eu/log/zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe",
40
+ "status": 200,
41
+ "content_type": "application/json; charset=utf-8",
42
+ "body": "oydid-log-zQmVwM.json"
43
+ },
44
+ {
45
+ "url": "https://oydid.ownyourdata.eu/doc_raw/zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
46
+ "status": 200,
47
+ "content_type": "application/json; charset=utf-8",
48
+ "body": "oydid-doc-raw-zQmaBZ.json"
49
+ },
50
+ {
51
+ "url": "https://oydid.ownyourdata.eu/log/zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh",
52
+ "status": 200,
53
+ "content_type": "application/json; charset=utf-8",
54
+ "body": "oydid-log-zQmaBZ.json"
55
+ }
56
+ ]
@@ -0,0 +1 @@
1
+ {"doc":{"simple":"example"},"key":"z6MusYB5iT5krCHYsZ76EzBaTdRwGKsaBhMcSbrXaPJgkuRQ:z6Mv7EYihbAat6Wq7GsjNsjcxt58dZT8fmsRjQGTkYamYrjB","log":"zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe"}
@@ -0,0 +1 @@
1
+ {"error":"invalid path"}
@@ -0,0 +1 @@
1
+ {"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":[]}]}
@@ -0,0 +1 @@
1
+ {"doc":{"simple":"example"},"key":"z6MusYB5iT5krCHYsZ76EzBaTdRwGKsaBhMcSbrXaPJgkuRQ:z6Mv7EYihbAat6Wq7GsjNsjcxt58dZT8fmsRjQGTkYamYrjB","log":"zQmVwMvovLy5KNYHHVHQ1wv8J7y9L6UPE8eyU4tzypFWtYe"}
@@ -0,0 +1 @@
1
+ [{"ts":1641224736,"op":2,"doc":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","sig":"z3Kb5qeReCqr3ftxpf2i5UypUwrzrVkyspMtaDcb6e9YdHVSptcAFgvwbgk3qWqspTcGiKDYKXZZh8g6XyM2WPmNp","previous":[]},{"ts":1641224736,"op":0,"doc":"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS","sig":"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU","previous":[]}]
@@ -0,0 +1 @@
1
+ [{"ts":0,"op":2,"doc":"zQmZEP95SPr699GjBeN82cspsZww8RKKHLR9UChbZxKw3H7","sig":"z2vESePysjyywnUpVZbftZzh34j4GwPknfvtZms5BqmE4j7v72KHpBPrW5QxK22kWVoVbcxHanL2qwVd32MFRzDfM","previous":[]},{"ts":0,"op":0,"doc":"zQmReRus2QnsapBdVBqhYRddh2PyTRF1hDbUfJS44izyiex","sig":"zsxdDbaakXCj3tWLMf8m9UsYLNuisUD2jErcBdfSgjkLpA4CxZirJQpJahiC2Yxu5oqoTG2G7vvv9EPJhozVF4jE","previous":[]}]
@@ -0,0 +1 @@
1
+ [{"ts":1641224736,"op":2,"doc":"zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh","sig":"z3Kb5qeReCqr3ftxpf2i5UypUwrzrVkyspMtaDcb6e9YdHVSptcAFgvwbgk3qWqspTcGiKDYKXZZh8g6XyM2WPmNp","previous":[]},{"ts":1641224736,"op":0,"doc":"zQmT8SG7a238bF7wdV7LdrEAQpimqhKGor7CQsjtCYdZdTS","sig":"z63hu8LseptBrvB2kEDwhPP35sBj7JDDJsEDW85cjRkrjjac9ZV3HxPW9NVKewHcQYwrVLVsnDCcm1RjbEARE5rJU","previous":[]}]
@@ -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
@@ -123,7 +123,9 @@ describe "OYDID handling" do
123
123
  it "generates #{input.split('/').last}" do
124
124
  expected = File.read(input.sub('input', 'output'))
125
125
  data = File.read(input)
126
- expect(Oydid.generate_private_key(data, {}).first).to eq expected
126
+ # signature is (input, method, options) - passing options as the 2nd
127
+ # argument made this resolve the codec {} and always return nil
128
+ expect(Oydid.generate_private_key(data, "ed25519-priv", {}).first).to eq expected
127
129
  end
128
130
  end
129
131
  it "handles unknown key codec" do
@@ -238,6 +240,82 @@ describe "OYDID handling" do
238
240
  expect(Oydid.key_to_hex("not-a-key", {}).first).to be_nil
239
241
  end
240
242
 
243
+ # hex2mb: public keys (--public)
244
+ it "encodes an ed25519 public key from hex identically to public_key()" do
245
+ privkey, _ = Oydid.private_key_from_hex("aa" * 32, {key_type: "ed25519"})
246
+ pub_mb, _ = Oydid.public_key(privkey, {})
247
+ pub_hex, _ = Oydid.key_to_hex(pub_mb, {})
248
+ from_hex, msg = Oydid.public_key_from_hex(pub_hex, {key_type: "ed25519"})
249
+ expect(msg).to eq ""
250
+ expect(from_hex).to eq pub_mb
251
+ expect(Oydid.get_keytype(from_hex)).to eq "ed25519-pub"
252
+ end
253
+ it "encodes a p256 public key from hex identically to public_key()" do
254
+ privkey, _ = Oydid.private_key_from_hex(
255
+ "96fe0f41947d645c7a1858c48c7a0560e7e5bd3d45125b57a611a3a9a103626b",
256
+ {key_type: "p256"})
257
+ pub_mb, _ = Oydid.public_key(privkey, {})
258
+ pub_hex, _ = Oydid.key_to_hex(pub_mb, {})
259
+ from_hex, msg = Oydid.public_key_from_hex(pub_hex, {key_type: "p256"})
260
+ expect(msg).to eq ""
261
+ expect(from_hex).to eq pub_mb
262
+ expect(Oydid.get_keytype(from_hex)).to eq "p256-pub"
263
+ end
264
+ it "round-trips a public key hex -> mb -> hex" do
265
+ hex = "e734ea6c2b6257de72355e472aa05a4c487e6b463c029ed306df2f01b5636b58"
266
+ mb, _ = Oydid.public_key_from_hex(hex, {key_type: "ed25519"})
267
+ back, _ = Oydid.key_to_hex(mb, {})
268
+ expect(back).to eq hex
269
+ end
270
+ it "accepts a compressed p256 public key" do
271
+ hex = "03bcad0c43ac859d0552d95b639156073f9c1c4fb1aa9490f3639a8cf0a2aaadaa"
272
+ mb, msg = Oydid.public_key_from_hex(hex, {key_type: "p256"})
273
+ expect(msg).to eq ""
274
+ expect(Oydid.get_keytype(mb)).to eq "p256-pub"
275
+ end
276
+ it "rejects a p256 public key that is not a point on the curve" do
277
+ mb, msg = Oydid.public_key_from_hex("04" + "11" * 64, {key_type: "p256"})
278
+ expect(mb).to be_nil
279
+ expect(msg).to match(/not a valid point/)
280
+ end
281
+ it "rejects malformed public key hex" do
282
+ expect(Oydid.public_key_from_hex("xyz", {key_type: "ed25519"}).first).to be_nil
283
+ expect(Oydid.public_key_from_hex("aa" * 31, {key_type: "ed25519"}).first).to be_nil
284
+ expect(Oydid.public_key_from_hex("aa" * 32, {key_type: "p256"}).first).to be_nil
285
+ expect(Oydid.public_key_from_hex("aa" * 32, {key_type: "secp256k1"}).first).to be_nil
286
+ end
287
+ it "defaults to ed25519 when no key type is given for a public key" do
288
+ hex = "e734ea6c2b6257de72355e472aa05a4c487e6b463c029ed306df2f01b5636b58"
289
+ expect(Oydid.get_keytype(Oydid.public_key_from_hex(hex, {}).first)).to eq "ed25519-pub"
290
+ end
291
+
292
+ # hex2mb: raw data (--raw)
293
+ it "encodes raw hex data to multibase without a multicodec prefix" do
294
+ mb, msg = Oydid.hex_to_multibase("deadbeef", {})
295
+ expect(msg).to eq ""
296
+ expect(Oydid.multi_decode(mb).first.unpack1("H*")).to eq "deadbeef"
297
+ # no multicodec prefix -> deliberately not readable back as a key
298
+ expect(Oydid.key_to_hex(mb, {}).first).to be_nil
299
+ end
300
+ it "preserves leading zero bytes in raw hex data" do
301
+ mb, _ = Oydid.hex_to_multibase("00deadbeef", {})
302
+ expect(Oydid.multi_decode(mb).first.unpack1("H*")).to eq "00deadbeef"
303
+ end
304
+ it "accepts a 0x prefix and surrounding whitespace in raw hex data" do
305
+ expect(Oydid.hex_to_multibase(" 0xdeadbeef\n", {}).first).to \
306
+ eq Oydid.hex_to_multibase("deadbeef", {}).first
307
+ end
308
+ it "rejects all-zero raw hex data with a specific message" do
309
+ mb, msg = Oydid.hex_to_multibase("00" * 4, {})
310
+ expect(mb).to be_nil
311
+ expect(msg).to match(/all-zero/)
312
+ end
313
+ it "rejects malformed raw hex data" do
314
+ expect(Oydid.hex_to_multibase("xyz", {}).first).to be_nil
315
+ expect(Oydid.hex_to_multibase("abc", {}).first).to be_nil
316
+ expect(Oydid.hex_to_multibase("", {}).first).to be_nil
317
+ end
318
+
241
319
  # storage functions
242
320
  it "should create 'filename' and put/read 'text'" do
243
321
  @buffer = StringIO.new()
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.5
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Fabianek
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-18 00:00:00.000000000 Z
10
+ date: 2026-08-19 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: simple_dag
@@ -261,6 +261,15 @@ 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
264
273
  - spec/input/basic/arrays.json
265
274
  - spec/input/basic/french.json
266
275
  - spec/input/basic/sample2_get_location.doc
@@ -400,6 +409,7 @@ files:
400
409
  - spec/output/main/sample0_read.doc
401
410
  - spec/oydid_spec.rb
402
411
  - spec/spec_helper.rb
412
+ - spec/support/http_stubs.rb
403
413
  homepage: http://github.com/ownyourdata/oydid
404
414
  licenses:
405
415
  - Apache-2.0
@@ -415,17 +425,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
415
425
  requirements:
416
426
  - - ">="
417
427
  - !ruby/object:Gem::Version
418
- version: 3.0.0
428
+ version: 3.2.0
419
429
  required_rubygems_version: !ruby/object:Gem::Requirement
420
430
  requirements:
421
431
  - - ">="
422
432
  - !ruby/object:Gem::Version
423
433
  version: '0'
424
434
  requirements: []
425
- rubygems_version: 4.0.15
435
+ rubygems_version: 4.0.12
426
436
  specification_version: 4
427
437
  summary: Own Your Decentralized Identifier for Ruby.
428
438
  test_files:
439
+ - spec/fixtures/http/did2-data-container-doc.json
440
+ - spec/fixtures/http/manifest.json
441
+ - spec/fixtures/http/oydid-doc-did-zQmaBZ.json
442
+ - spec/fixtures/http/oydid-doc-empty.json
443
+ - spec/fixtures/http/oydid-doc-raw-zQmaBZ.json
444
+ - spec/fixtures/http/oydid-doc-zQmaBZ.json
445
+ - spec/fixtures/http/oydid-log-zQmVwM.json
446
+ - spec/fixtures/http/oydid-log-zQmZEP.json
447
+ - spec/fixtures/http/oydid-log-zQmaBZ.json
429
448
  - spec/input/basic/arrays.json
430
449
  - spec/input/basic/french.json
431
450
  - spec/input/basic/sample2_get_location.doc
@@ -565,3 +584,4 @@ test_files:
565
584
  - spec/output/main/sample0_read.doc
566
585
  - spec/oydid_spec.rb
567
586
  - spec/spec_helper.rb
587
+ - spec/support/http_stubs.rb