oydid 0.6.5 → 0.6.6

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: 3e47064181aee5338fa3fde2af77f2955cbcb6e5a4b19f8586882183056f1070
4
+ data.tar.gz: d601a5b9151f57af747e1ea42d464549b115cd36eb25cfc42d77385f9c8222a3
5
5
  SHA512:
6
- metadata.gz: e04e8cd58b7a29bb9e7dabffffcf613c9459c18ece1254b8cc1ad60b4d19d073276f18bd0f9c36b7cd6786340e1f81fb9be320f8964a182d5b861bfab9d5825f
7
- data.tar.gz: 370d28f6c8614238b734a9a0dc8410de4007923c6ac7e464190ad36bd82bbba5f28def3e46d05c74d882b5ba5559f11cadf2a4a26b2ea0045ea87acd8a6503bb
6
+ metadata.gz: f40852d750dd966b86b0c7cc7efac6c6a24feac83b9e581284adaa7ebc5231571f790e9c8fccc18c54316b48e7563d9b4e3ec4ee36442184fcafd6e22ac296b6
7
+ data.tar.gz: 0f145b61b4ddd8794316cfc4a5bb653212b1e5be1332dcd24726d30783fbd2448b726cf0cec3a0e2b8f9893d60a8f9605ea591350993ac609f80df6edc262518
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.5
1
+ 0.6.6
data/lib/oydid/basic.rb CHANGED
@@ -1012,14 +1012,23 @@ class Oydid
1012
1012
  end
1013
1013
  end
1014
1014
 
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 = {})
1015
+ # normalize and validate hex input shared by the hex-based converters below:
1016
+ # strips whitespace and an optional '0x' prefix and rejects anything that is
1017
+ # not an even number of hex digits. Returns [normalized_hex, ""] or [nil, msg].
1018
+ def self.normalize_hex(hex)
1019
1019
  hex = hex.to_s.strip.delete_prefix("0x").delete_prefix("0X")
1020
1020
  unless hex =~ /\A[0-9a-fA-F]+\z/ && hex.length.even?
1021
1021
  return [nil, "invalid hex input"]
1022
1022
  end
1023
+ return [hex, ""]
1024
+ end
1025
+
1026
+ # build an OYDID multibase-encoded private key from a raw hex-encoded key
1027
+ # (e.g. an externally generated P-256 or Ed25519 key). The key type is taken
1028
+ # from options[:key_type] (default 'ed25519').
1029
+ def self.private_key_from_hex(hex, options = {})
1030
+ hex, msg = normalize_hex(hex)
1031
+ return [nil, msg] if hex.nil?
1023
1032
  raw = [hex].pack("H*")
1024
1033
  key_type = options[:key_type].to_s
1025
1034
  key_type = "ed25519" if key_type == ""
@@ -1047,6 +1056,63 @@ class Oydid
1047
1056
  return multi_encode([code, length, raw].pack("SCa#{length}"), options)
1048
1057
  end
1049
1058
 
1059
+ # counterpart to private_key_from_hex for public keys.
1060
+ #
1061
+ # NOTE on the p256 length check: multicodec 0x1200 ('p256-pub') is specified
1062
+ # for compressed points, but Oydid.public_key() emits uncompressed ones
1063
+ # (OpenSSL's default point_conversion_form), so both forms are accepted here
1064
+ # to stay compatible with keys this library produces itself.
1065
+ def self.public_key_from_hex(hex, options = {})
1066
+ hex, msg = normalize_hex(hex)
1067
+ return [nil, msg] if hex.nil?
1068
+ raw = [hex].pack("H*")
1069
+ key_type = options[:key_type].to_s
1070
+ key_type = "ed25519" if key_type == ""
1071
+ case key_type
1072
+ when "p256"
1073
+ unless [33, 65].include?(raw.bytesize)
1074
+ return [nil, "p256 public key must be 33 (compressed) or 65 (uncompressed) bytes"]
1075
+ end
1076
+ # the length alone says nothing: verify that the input actually
1077
+ # decodes to a point on prime256v1 (this also rejects a wrong
1078
+ # leading byte), otherwise an unusable key would be encoded happily
1079
+ begin
1080
+ OpenSSL::PKey::EC::Point.new(
1081
+ OpenSSL::PKey::EC::Group.new("prime256v1"),
1082
+ OpenSSL::BN.new(hex, 16))
1083
+ rescue StandardError
1084
+ return [nil, "p256 public key is not a valid point on the curve"]
1085
+ end
1086
+ code = Multicodecs["p256-pub"].code
1087
+ when "ed25519"
1088
+ unless raw.bytesize == 32
1089
+ return [nil, "ed25519 public key must be 32 bytes (64 hex characters)"]
1090
+ end
1091
+ # there is no cheap point validation for ed25519 (Ed25519::VerifyKey
1092
+ # accepts any 32 bytes), and a private key has the same length - so
1093
+ # passing a *private* key with --public silently yields a public key
1094
+ # multibase. Callers have to know what they hand in.
1095
+ code = Multicodecs["ed25519-pub"].code
1096
+ else
1097
+ return [nil, "unsupported key type"]
1098
+ end
1099
+ return multi_encode(Multibases::DecodedByteArray.new((to_varint(code) << raw.bytes).flatten).to_s(Encoding::BINARY), options)
1100
+ end
1101
+
1102
+ # convert raw hex data (no multicodec prefix, e.g. a signature) to Multibase
1103
+ def self.hex_to_multibase(hex, options = {})
1104
+ hex, msg = normalize_hex(hex)
1105
+ return [nil, msg] if hex.nil?
1106
+ # the multibases gem cannot represent an all-zero byte string: packing
1107
+ # raises NoMethodError and unpacking the correct base58btc form ("z1111")
1108
+ # raises RangeError. Leading zero bytes in otherwise non-zero input are
1109
+ # handled correctly, so only the all-zero case has to be rejected.
1110
+ if hex.delete("0") == ""
1111
+ return [nil, "all-zero input is not supported by the multibase encoder"]
1112
+ end
1113
+ return multi_encode([hex].pack("H*"), options)
1114
+ end
1115
+
1050
1116
  # reverse of private_key_from_hex / public_key encoding:
1051
1117
  # decode an OYDID Multibase-encoded key (private or public) back to raw hex.
1052
1118
  def self.key_to_hex(key_encoded, options = {})
data/spec/oydid_spec.rb CHANGED
@@ -238,6 +238,82 @@ describe "OYDID handling" do
238
238
  expect(Oydid.key_to_hex("not-a-key", {}).first).to be_nil
239
239
  end
240
240
 
241
+ # hex2mb: public keys (--public)
242
+ it "encodes an ed25519 public key from hex identically to public_key()" do
243
+ privkey, _ = Oydid.private_key_from_hex("aa" * 32, {key_type: "ed25519"})
244
+ pub_mb, _ = Oydid.public_key(privkey, {})
245
+ pub_hex, _ = Oydid.key_to_hex(pub_mb, {})
246
+ from_hex, msg = Oydid.public_key_from_hex(pub_hex, {key_type: "ed25519"})
247
+ expect(msg).to eq ""
248
+ expect(from_hex).to eq pub_mb
249
+ expect(Oydid.get_keytype(from_hex)).to eq "ed25519-pub"
250
+ end
251
+ it "encodes a p256 public key from hex identically to public_key()" do
252
+ privkey, _ = Oydid.private_key_from_hex(
253
+ "96fe0f41947d645c7a1858c48c7a0560e7e5bd3d45125b57a611a3a9a103626b",
254
+ {key_type: "p256"})
255
+ pub_mb, _ = Oydid.public_key(privkey, {})
256
+ pub_hex, _ = Oydid.key_to_hex(pub_mb, {})
257
+ from_hex, msg = Oydid.public_key_from_hex(pub_hex, {key_type: "p256"})
258
+ expect(msg).to eq ""
259
+ expect(from_hex).to eq pub_mb
260
+ expect(Oydid.get_keytype(from_hex)).to eq "p256-pub"
261
+ end
262
+ it "round-trips a public key hex -> mb -> hex" do
263
+ hex = "e734ea6c2b6257de72355e472aa05a4c487e6b463c029ed306df2f01b5636b58"
264
+ mb, _ = Oydid.public_key_from_hex(hex, {key_type: "ed25519"})
265
+ back, _ = Oydid.key_to_hex(mb, {})
266
+ expect(back).to eq hex
267
+ end
268
+ it "accepts a compressed p256 public key" do
269
+ hex = "03bcad0c43ac859d0552d95b639156073f9c1c4fb1aa9490f3639a8cf0a2aaadaa"
270
+ mb, msg = Oydid.public_key_from_hex(hex, {key_type: "p256"})
271
+ expect(msg).to eq ""
272
+ expect(Oydid.get_keytype(mb)).to eq "p256-pub"
273
+ end
274
+ it "rejects a p256 public key that is not a point on the curve" do
275
+ mb, msg = Oydid.public_key_from_hex("04" + "11" * 64, {key_type: "p256"})
276
+ expect(mb).to be_nil
277
+ expect(msg).to match(/not a valid point/)
278
+ end
279
+ it "rejects malformed public key hex" do
280
+ expect(Oydid.public_key_from_hex("xyz", {key_type: "ed25519"}).first).to be_nil
281
+ expect(Oydid.public_key_from_hex("aa" * 31, {key_type: "ed25519"}).first).to be_nil
282
+ expect(Oydid.public_key_from_hex("aa" * 32, {key_type: "p256"}).first).to be_nil
283
+ expect(Oydid.public_key_from_hex("aa" * 32, {key_type: "secp256k1"}).first).to be_nil
284
+ end
285
+ it "defaults to ed25519 when no key type is given for a public key" do
286
+ hex = "e734ea6c2b6257de72355e472aa05a4c487e6b463c029ed306df2f01b5636b58"
287
+ expect(Oydid.get_keytype(Oydid.public_key_from_hex(hex, {}).first)).to eq "ed25519-pub"
288
+ end
289
+
290
+ # hex2mb: raw data (--raw)
291
+ it "encodes raw hex data to multibase without a multicodec prefix" do
292
+ mb, msg = Oydid.hex_to_multibase("deadbeef", {})
293
+ expect(msg).to eq ""
294
+ expect(Oydid.multi_decode(mb).first.unpack1("H*")).to eq "deadbeef"
295
+ # no multicodec prefix -> deliberately not readable back as a key
296
+ expect(Oydid.key_to_hex(mb, {}).first).to be_nil
297
+ end
298
+ it "preserves leading zero bytes in raw hex data" do
299
+ mb, _ = Oydid.hex_to_multibase("00deadbeef", {})
300
+ expect(Oydid.multi_decode(mb).first.unpack1("H*")).to eq "00deadbeef"
301
+ end
302
+ it "accepts a 0x prefix and surrounding whitespace in raw hex data" do
303
+ expect(Oydid.hex_to_multibase(" 0xdeadbeef\n", {}).first).to \
304
+ eq Oydid.hex_to_multibase("deadbeef", {}).first
305
+ end
306
+ it "rejects all-zero raw hex data with a specific message" do
307
+ mb, msg = Oydid.hex_to_multibase("00" * 4, {})
308
+ expect(mb).to be_nil
309
+ expect(msg).to match(/all-zero/)
310
+ end
311
+ it "rejects malformed raw hex data" do
312
+ expect(Oydid.hex_to_multibase("xyz", {}).first).to be_nil
313
+ expect(Oydid.hex_to_multibase("abc", {}).first).to be_nil
314
+ expect(Oydid.hex_to_multibase("", {}).first).to be_nil
315
+ end
316
+
241
317
  # storage functions
242
318
  it "should create 'filename' and put/read 'text'" do
243
319
  @buffer = StringIO.new()
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.6
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
@@ -422,7 +422,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
422
422
  - !ruby/object:Gem::Version
423
423
  version: '0'
424
424
  requirements: []
425
- rubygems_version: 4.0.15
425
+ rubygems_version: 4.0.12
426
426
  specification_version: 4
427
427
  summary: Own Your Decentralized Identifier for Ruby.
428
428
  test_files: