Class: JWT::PQ::MlDsa Private

Inherits:
Object
  • Object
show all
Defined in:
lib/jwt/pq/ml_dsa.rb

Overview

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

Ruby wrapper around liboqs ML-DSA operations. Handles memory allocation, FFI calls, and cleanup.

Constant Summary collapse

ALGORITHMS =

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

{
  "ML-DSA-44" => { public_key: 1312, secret_key: 2560, signature: 2420, nist_level: 2 },
  "ML-DSA-65" => { public_key: 1952, secret_key: 4032, signature: 3309, nist_level: 3 },
  "ML-DSA-87" => { public_key: 2592, secret_key: 4896, signature: 4627, nist_level: 5 }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(algorithm) ⇒ MlDsa

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.

Returns a new instance of MlDsa.



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/jwt/pq/ml_dsa.rb', line 65

def initialize(algorithm)
  algorithm = algorithm.to_s
  unless ALGORITHMS.key?(algorithm)
    raise UnsupportedAlgorithmError,
          "Unsupported algorithm: #{algorithm}. " \
          "Supported: #{ALGORITHMS.keys.join(", ")}"
  end

  @algorithm = algorithm
  @sizes = ALGORITHMS[algorithm]
end

Instance Attribute Details

#algorithmObject (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.



63
64
65
# File 'lib/jwt/pq/ml_dsa.rb', line 63

def algorithm
  @algorithm
end

Class Method Details

.reset_handles!void

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.

This method returns an undefined value.

Drop the process-wide cache of OQS_SIG handles.

The first call to sign_handle or verify_handle in a process (or after this reset) lazily allocates a fresh handle. The inherited handles in the child are not explicitly freed — doing so can confuse malloc bookkeeping across forked processes. The handles leak until process exit (≤3 algorithms × <1 KB each), which is negligible.

Prefer the public JWT::PQ.reset_handles! wrapper from application code.



58
59
60
61
# File 'lib/jwt/pq/ml_dsa.rb', line 58

def self.reset_handles!
  @sign_handles_mutex.synchronize { @sign_handles.clear }
  @verify_handles_mutex.synchronize { @verify_handles.clear }
end

.sign_handle(algorithm) ⇒ Object

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.



19
20
21
22
23
24
25
26
27
28
# File 'lib/jwt/pq/ml_dsa.rb', line 19

def self.sign_handle(algorithm)
  @sign_handles_mutex.synchronize do
    @sign_handles[algorithm] ||= begin
      h = LibOQS.OQS_SIG_new(algorithm)
      raise LiboqsError, "Failed to initialize #{algorithm}" if h.null?

      h
    end
  end
end

.verify_handle(algorithm) ⇒ Object

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.



33
34
35
36
37
38
39
40
41
42
# File 'lib/jwt/pq/ml_dsa.rb', line 33

def self.verify_handle(algorithm)
  @verify_handles_mutex.synchronize do
    @verify_handles[algorithm] ||= begin
      h = LibOQS.OQS_SIG_new(algorithm)
      raise LiboqsError, "Failed to initialize #{algorithm}" if h.null?

      h
    end
  end
end

Instance Method Details

#keypairObject

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.

Generate a new keypair. Returns [public_key_bytes, secret_key_bytes]



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/jwt/pq/ml_dsa.rb', line 79

def keypair
  sig = LibOQS.OQS_SIG_new(@algorithm)
  raise LiboqsError, "Failed to initialize #{@algorithm}" if sig.null?

  pk = FFI::MemoryPointer.new(:uint8, @sizes[:public_key])
  sk = FFI::MemoryPointer.new(:uint8, @sizes[:secret_key])

  status = LibOQS.OQS_SIG_keypair(sig, pk, sk)
  raise LiboqsError, "Keypair generation failed for #{@algorithm}" unless status == LibOQS::OQS_SUCCESS

  [pk.read_bytes(@sizes[:public_key]), sk.read_bytes(@sizes[:secret_key])]
ensure
  sk&.clear
  LibOQS.OQS_SIG_free(sig) if sig && !sig.null?
end

#public_key_sizeObject

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.

Key sizes for this algorithm



150
151
152
# File 'lib/jwt/pq/ml_dsa.rb', line 150

def public_key_size
  @sizes[:public_key]
end

#secret_key_sizeObject

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.



154
155
156
# File 'lib/jwt/pq/ml_dsa.rb', line 154

def secret_key_size
  @sizes[:secret_key]
end

#sign(message, secret_key) ⇒ Object

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.

Sign a message with a secret key. Returns the signature bytes.



97
98
99
100
101
102
103
104
105
# File 'lib/jwt/pq/ml_dsa.rb', line 97

def sign(message, secret_key)
  validate_key_size!(secret_key, :secret_key)

  sk_buf = FFI::MemoryPointer.new(:uint8, secret_key.bytesize)
  sk_buf.put_bytes(0, secret_key)
  sign_with_sk_buffer(message, sk_buf)
ensure
  sk_buf&.clear
end

#sign_with_sk_buffer(message, sk_buf) ⇒ Object

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.

Faster sign path: takes a pre-populated FFI::MemoryPointer holding the secret key. Caller is responsible for buffer lifecycle (allocation, zeroing). Used by JWT::PQ::Key to avoid re-allocating+copying the secret key on every sign call.

Raises:



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/jwt/pq/ml_dsa.rb', line 111

def sign_with_sk_buffer(message, sk_buf)
  sig = self.class.sign_handle(@algorithm)
  sig_buf = FFI::MemoryPointer.new(:uint8, @sizes[:signature])
  sig_len = FFI::MemoryPointer.new(:size_t)
  msg_buf = FFI::MemoryPointer.from_string(message)

  status = LibOQS.OQS_SIG_sign(sig, sig_buf, sig_len,
                               msg_buf, message.bytesize, sk_buf)
  raise SignatureError, "Signing failed for #{@algorithm}" unless status == LibOQS::OQS_SUCCESS

  sig_buf.read_bytes(sig_len.read(:size_t))
end

#signature_sizeObject

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.



158
159
160
# File 'lib/jwt/pq/ml_dsa.rb', line 158

def signature_size
  @sizes[:signature]
end

#verify(message, signature, public_key) ⇒ Object

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.

Verify a signature against a message and public key. Returns true if valid, false otherwise.



126
127
128
129
130
131
132
# File 'lib/jwt/pq/ml_dsa.rb', line 126

def verify(message, signature, public_key)
  validate_key_size!(public_key, :public_key)

  pk_buf = FFI::MemoryPointer.new(:uint8, public_key.bytesize)
  pk_buf.put_bytes(0, public_key)
  verify_with_pk_buffer(message, signature, pk_buf)
end

#verify_with_pk_buffer(message, signature, pk_buf) ⇒ Object

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.

Faster verify path: takes a pre-populated FFI::MemoryPointer holding the public key. Caller is responsible for buffer lifecycle. Used by JWT::PQ::Key to avoid re-allocating+copying the public key on every verify call.



138
139
140
141
142
143
144
145
146
147
# File 'lib/jwt/pq/ml_dsa.rb', line 138

def verify_with_pk_buffer(message, signature, pk_buf)
  sig = self.class.verify_handle(@algorithm)
  msg_buf = FFI::MemoryPointer.from_string(message)
  sig_buf = FFI::MemoryPointer.new(:uint8, signature.bytesize)
  sig_buf.put_bytes(0, signature)

  status = LibOQS.OQS_SIG_verify(sig, msg_buf, message.bytesize,
                                 sig_buf, signature.bytesize, pk_buf)
  status == LibOQS::OQS_SUCCESS
end