Class: PqcAsn1::OID

Inherits:
Object
  • Object
show all
Defined in:
lib/pqc_asn1/oid.rb

Overview

OID value class for post-quantum algorithm identifiers standardised by NIST.

All OID instances are frozen at construction time (initialize calls freeze). Each constant wraps a dotted-decimal string. Pass them directly to DER.build_spki or DER.build_pkcs8; dotted Strings are also accepted by the build methods.

DER.parse_spki and DER.parse_pkcs8 return the OID as a PqcAsn1::OID instance that can be compared directly against these constants using ==.

Sources (verified 2026-03-14):

ML-DSA:  NIST FIPS 204, Table 2; NIST CSOR OID arc 2.16.840.1.101.3.4.3
ML-KEM:  NIST FIPS 203; NIST CSOR OID arc 2.16.840.1.101.3.4.4
SLH-DSA: NIST FIPS 205, Table 2; NIST CSOR OID arc 2.16.840.1.101.3.4.3

Examples:

Using an OID constant

der = PqcAsn1::DER.build_spki(PqcAsn1::OID::ML_DSA_44, public_key_bytes)

Checking the parsed OID

parsed = PqcAsn1::DER.parse_spki(der)
parsed.oid == PqcAsn1::OID::ML_DSA_44  # => true
parsed.oid.name                         # => "ML_DSA_44"

Constant Summary collapse

ML_DSA_44 =

OID 2.16.840.1.101.3.4.3.17 — 128-bit security (NIST level 2)

new("2.16.840.1.101.3.4.3.17")
ML_DSA_65 =

OID 2.16.840.1.101.3.4.3.18 — 192-bit security (NIST level 3)

new("2.16.840.1.101.3.4.3.18")
ML_DSA_87 =

OID 2.16.840.1.101.3.4.3.19 — 256-bit security (NIST level 5)

new("2.16.840.1.101.3.4.3.19")
ML_KEM_512 =

OID 2.16.840.1.101.3.4.4.1 — 128-bit security (NIST level 1)

new("2.16.840.1.101.3.4.4.1")
ML_KEM_768 =

OID 2.16.840.1.101.3.4.4.2 — 192-bit security (NIST level 3)

new("2.16.840.1.101.3.4.4.2")
ML_KEM_1024 =

OID 2.16.840.1.101.3.4.4.3 — 256-bit security (NIST level 5)

new("2.16.840.1.101.3.4.4.3")
SLH_DSA_SHA2_128S =

OID 2.16.840.1.101.3.4.3.20 — SHA-2, 128-bit, small (NIST level 1)

new("2.16.840.1.101.3.4.3.20")
SLH_DSA_SHA2_128F =

OID 2.16.840.1.101.3.4.3.21 — SHA-2, 128-bit, fast (NIST level 1)

new("2.16.840.1.101.3.4.3.21")
SLH_DSA_SHA2_192S =

OID 2.16.840.1.101.3.4.3.22 — SHA-2, 192-bit, small (NIST level 3)

new("2.16.840.1.101.3.4.3.22")
SLH_DSA_SHA2_192F =

OID 2.16.840.1.101.3.4.3.23 — SHA-2, 192-bit, fast (NIST level 3)

new("2.16.840.1.101.3.4.3.23")
SLH_DSA_SHA2_256S =

OID 2.16.840.1.101.3.4.3.24 — SHA-2, 256-bit, small (NIST level 5)

new("2.16.840.1.101.3.4.3.24")
SLH_DSA_SHA2_256F =

OID 2.16.840.1.101.3.4.3.25 — SHA-2, 256-bit, fast (NIST level 5)

new("2.16.840.1.101.3.4.3.25")
SLH_DSA_SHAKE_128S =

OID 2.16.840.1.101.3.4.3.26 — SHAKE, 128-bit, small (NIST level 1)

new("2.16.840.1.101.3.4.3.26")
SLH_DSA_SHAKE_128F =

OID 2.16.840.1.101.3.4.3.27 — SHAKE, 128-bit, fast (NIST level 1)

new("2.16.840.1.101.3.4.3.27")
SLH_DSA_SHAKE_192S =

OID 2.16.840.1.101.3.4.3.28 — SHAKE, 192-bit, small (NIST level 3)

new("2.16.840.1.101.3.4.3.28")
SLH_DSA_SHAKE_192F =

OID 2.16.840.1.101.3.4.3.29 — SHAKE, 192-bit, fast (NIST level 3)

new("2.16.840.1.101.3.4.3.29")
SLH_DSA_SHAKE_256S =

OID 2.16.840.1.101.3.4.3.30 — SHAKE, 256-bit, small (NIST level 5)

new("2.16.840.1.101.3.4.3.30")
SLH_DSA_SHAKE_256F =

OID 2.16.840.1.101.3.4.3.31 — SHAKE, 256-bit, fast (NIST level 5)

new("2.16.840.1.101.3.4.3.31")

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dotted) ⇒ OID

Returns a new instance of OID.

Parameters:

  • dotted (String) —

    dotted-decimal OID string



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/pqc_asn1/oid.rb', line 32

def initialize(dotted)
  @dotted = dotted.frozen? ? dotted : dotted.dup.freeze
  # Pre-compute DER TLV if the C extension is already loaded.
  # Built-in constants (ML_DSA_44, etc.) are defined before the extension
  # loads, so from_dotted is not yet available for them — they fall back
  # to the class-level cache in #der_tlv.  OIDs constructed at runtime
  # (OID.new, OID.register) always get @der_tlv set here, skipping the
  # class-level hash entirely.
  @der_tlv = self.class.respond_to?(:from_dotted) ? self.class.from_dotted(@dotted) : nil
  freeze
end

Class Attribute Details

.custom_registry ⇒ Object (readonly)



193
194
195
# File 'lib/pqc_asn1/oid.rb', line 193

def custom_registry
  @custom_registry
end

.der_tlv_cache ⇒ Object (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



190
191
192
# File 'lib/pqc_asn1/oid.rb', line 190

def der_tlv_cache
  @der_tlv_cache
end

Instance Attribute Details

#dotted ⇒ String (readonly)

Returns the dotted-decimal OID string (e.g. "2.16.840.1.101.3.4.3.17").

Returns:

  • (String) —

    the dotted-decimal OID string (e.g. "2.16.840.1.101.3.4.3.17")



29
30
31
# File 'lib/pqc_asn1/oid.rb', line 29

def dotted
  @dotted
end

Class Method Details

.[](key) ⇒ PqcAsn1::OID?

Look up a built-in or registered OID by name or dotted string.

Built-in constants are found via const_get; registered OIDs are found via the runtime registry populated by register.

Examples:

PqcAsn1::OID["ML_DSA_44"]                  # => #<PqcAsn1::OID 2.16...>
PqcAsn1::OID["2.16.840.1.101.3.4.3.17"]    # => #<PqcAsn1::OID 2.16...>
PqcAsn1::OID["MY_ALGO"]                     # => registered OID or nil

Parameters:

  • key (String) —

    dotted-decimal OID (contains ".") or constant name

Returns:

  • (PqcAsn1::OID, nil) —

    the OID instance, or nil if not found



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/pqc_asn1/oid.rb', line 219

def [](key)
  key = key.to_s
  if key.include?(".")
    # Dotted notation: O(1) hash lookup for built-in, then custom registry.
    builtin_by_dotted[key] || @custom_oid_by_dotted[key]
  else
    # Name: try built-in constant first, then custom registry.
    begin
      val = const_get(key, false)
      return val if val.is_a?(OID)
    rescue NameError
    end
    @custom_oid_by_name[key]
  end
end

.builtin_by_dotted ⇒ Object

Lazily build the built-in OID reverse lookup hash on first access.



196
197
198
199
200
201
202
203
204
205
# File 'lib/pqc_asn1/oid.rb', line 196

def builtin_by_dotted
  @builtin_by_dotted ||= begin
    h = {}
    constants.each do |c|
      val = const_get(c, false)
      h[val.dotted] = val if val.is_a?(OID)
    end
    h.freeze
  end
end

.register(dotted, name, key_sizes: nil) ⇒ PqcAsn1::OID

Register a custom OID so that name_for and [] recognise it and key-size validation works for the new algorithm.

Unlike the previous behaviour, this method does not define a Ruby constant on PqcAsn1::OID. If you want a constant, assign the return value explicitly:

PqcAsn1::OID::MY_ALGO = PqcAsn1::OID.register("1.3.x", "MY_ALGO")

Examples:

oid = PqcAsn1::OID.register("1.3.6.1.4.1.99999.1", "BIKE_L1",
                             key_sizes: {public: 1541, secret: 3110})
PqcAsn1::OID["BIKE_L1"].dotted  # => "1.3.6.1.4.1.99999.1"

Parameters:

  • dotted (String) —

    dotted-decimal OID (e.g. "1.3.6.1.4.1.99999.1")

  • name (String) —

    symbolic name (e.g. "MY_ALGO")

  • key_sizes (Hash, nil) (defaults to: nil) —

    optional {public: N, secret: M} for DER.validate_key_size support

Returns:

Raises:

  • (ArgumentError) —

    if the name or dotted OID is already registered



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/pqc_asn1/oid.rb', line 262

def register(dotted, name, key_sizes: nil)
  name = name.to_s.freeze
  dotted = dotted.to_s.freeze

  @register_mutex.synchronize do
    if @custom_oid_by_name.key?(name)
      raise ArgumentError, "OID name #{name.inspect} is already registered"
    end
    if @custom_oid_by_dotted.key?(dotted)
      raise ArgumentError,
        "OID #{dotted} is already registered as #{@custom_registry[dotted]}"
    end

    # Prevent shadowing built-in OID constants.
    begin
      existing = const_get(name, false)
      if existing.is_a?(OID)
        raise ArgumentError,
          "OID name #{name.inspect} conflicts with built-in constant #{name}"
      end
    rescue NameError
      # name is not a built-in constant — fine to register
    end

    # Prevent registering a dotted string that belongs to a built-in OID.
    if builtin_by_dotted.key?(dotted)
      builtin = builtin_by_dotted[dotted]
      builtin_name = constants.find { |c| const_get(c, false).equal?(builtin) }
      raise ArgumentError,
        "OID #{dotted} is already defined as built-in #{builtin_name}"
    end

    oid = new(dotted)
    @custom_oid_by_dotted[dotted] = oid
    @custom_oid_by_name[name] = oid
    @custom_registry[dotted] = name

    PqcAsn1::DER::REGISTERED_KEY_SIZES[oid] = key_sizes.freeze if key_sizes

    oid
  end
end

.registered ⇒ Hash{String => PqcAsn1::OID}

Enumerate all OIDs registered at runtime via register.

Returns:

  • (Hash{String => PqcAsn1::OID}) —

    name → OID mapping (frozen copy)



238
239
240
# File 'lib/pqc_asn1/oid.rb', line 238

def registered
  @custom_oid_by_name.dup.freeze
end

Instance Method Details

#==(other) ⇒ Boolean

Compare against another OID or a dotted String.

Parameters:

Returns:

  • (Boolean)


47
48
49
50
51
52
53
# File 'lib/pqc_asn1/oid.rb', line 47

def ==(other)
  case other
  when OID then @dotted == other.dotted
  when String then @dotted == other
  else false
  end
end

#der_tlv ⇒ String

DER TLV bytes (tag 0x06 + length + value). For OIDs created after the C extension loads, returns the pre-computed ivar set in initialize (O(1), no hash lookup). For the built-in constants (created at load time before the extension), falls back to the class-level cache on first access. Requires the C extension to be loaded (called at runtime, not load time).

Returns:

  • (String) —

    frozen ASCII-8BIT binary string



84
85
86
87
# File 'lib/pqc_asn1/oid.rb', line 84

def der_tlv
  return @der_tlv if @der_tlv
  OID.der_tlv_cache[@dotted] ||= PqcAsn1::OID.from_dotted(@dotted)
end

#eql?(other) ⇒ Boolean

Strict equality for use in Hash / eql? contexts (OID vs OID only).

Parameters:

  • other (Object)

Returns:

  • (Boolean)


58
59
60
# File 'lib/pqc_asn1/oid.rb', line 58

def eql?(other)
  other.is_a?(OID) && @dotted == other.dotted
end

#hash ⇒ Integer

Returns:

  • (Integer)


63
64
65
# File 'lib/pqc_asn1/oid.rb', line 63

def hash
  @dotted.hash
end

#inspect ⇒ String

Returns:

  • (String)


73
74
75
# File 'lib/pqc_asn1/oid.rb', line 73

def inspect
  "#<PqcAsn1::OID #{@dotted}>"
end

#name ⇒ String?

Human-readable constant name, e.g. "ML_DSA_44", or nil if unknown. Checks both built-in OIDs and user-registered OIDs.

Returns:

  • (String, nil)


92
93
94
# File 'lib/pqc_asn1/oid.rb', line 92

def name
  PqcAsn1::OID.name_for(self)
end

#to_s ⇒ String

Returns the dotted-decimal string representation.

Returns:

  • (String) —

    the dotted-decimal string representation



68
69
70
# File 'lib/pqc_asn1/oid.rb', line 68

def to_s
  @dotted
end