jwt-pq 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -1
- data/Gemfile +1 -0
- data/bench/fixtures/ml_dsa_65_sk.pem +128 -0
- data/bench/sign_throughput.rb +26 -0
- data/bench/verify_throughput.rb +29 -0
- data/lib/jwt/pq/algorithms/ml_dsa.rb +7 -2
- data/lib/jwt/pq/key.rb +13 -3
- data/lib/jwt/pq/ml_dsa.rb +51 -15
- data/lib/jwt/pq/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fc1d22df85af9d7c753800dbd77e08ce874d7c1bf177aa9afafab54a3ef6aa9
|
|
4
|
+
data.tar.gz: 3f8d5ef4114c28e79eeb609eac5bcff0b5e5bbe0b4869006b20e1e076cf7142f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 333d7706c667a66e7858813a4de3b813d46c097b283822ed228f3fa23bd5a58fe67dfd0b2cc3a0b7a3e71aceac70ff604c3d8084871828585ce633beba6f7199
|
|
7
|
+
data.tar.gz: d4ba329c764b33f0aea5dd37f10d0c9eb9623eb34d999bc003382096a18c58e2b914ad51fdd7e472b0e400a5504690acb21744a922944ded0b67cb5ee9bf9e70
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-04-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Sign-throughput benchmark at `bench/sign_throughput.rb` with a fixed PEM key fixture (`bench/fixtures/ml_dsa_65_sk.pem`), driven by `benchmark-ips`
|
|
15
|
+
- Verify-throughput benchmark at `bench/verify_throughput.rb`
|
|
16
|
+
- NIST ACVP sigVer KAT tests at `spec/jwt/pq/kat_spec.rb` — external interface, pure ML-DSA, empty context; covers ML-DSA-44, ML-DSA-65, and ML-DSA-87 with both passing and known-bad signatures as a canonical correctness gate
|
|
17
|
+
- `JWT::PQ::MlDsa#sign_with_sk_buffer` and `#verify_with_pk_buffer` — fast paths that accept pre-populated FFI buffers. The existing bytes-in `#sign` / `#verify` APIs are unchanged
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **ML-DSA signing throughput: +2.6%** (from 6676 to 6849 sigs/s on Ruby 3.4.6 + liboqs 0.15.0 for ML-DSA-65). Class-level cache of the `OQS_SIG` handle per algorithm avoids `OQS_SIG_new`/`OQS_SIG_free` per call; per-`Key` memoization of the secret-key FFI buffer avoids a 4032-byte allocation + copy per sign
|
|
22
|
+
- **ML-DSA verification throughput: +19.4%** (from 7995 to 9548 verifies/s on the same setup for ML-DSA-65). Class-level cache of the `OQS_SIG` handle for verify; per-`Key` memoization of the public-key FFI buffer; inlined type-check in the JWA verify entry point. `Key#verify` now reaches 93% of the raw `OQS_SIG_verify` ceiling; remaining overhead lives inside `ruby-jwt`
|
|
23
|
+
- `Key#destroy!` now also zeroes the cached secret-key FFI buffer (`@sk_buffer`) in addition to `@private_key`, preserving the secure-erase contract after the buffer memoization
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
- Add `benchmark-ips ~> 2.14` as a development/test dependency (powers the bench harnesses)
|
|
28
|
+
- Bump `ruby/setup-ruby` from 1.299.0 to 1.301.0 (#2)
|
|
29
|
+
|
|
10
30
|
## [0.2.0] - 2026-04-06
|
|
11
31
|
|
|
12
32
|
### Added
|
|
@@ -56,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
56
76
|
- Optional dependency on jwt-eddsa / ed25519
|
|
57
77
|
- Error classes: `LiboqsError`, `KeyError`, `SignatureError`, `MissingDependencyError`
|
|
58
78
|
|
|
59
|
-
[Unreleased]: https://github.com/marcelopazzo/jwt-pq/compare/v0.
|
|
79
|
+
[Unreleased]: https://github.com/marcelopazzo/jwt-pq/compare/v0.3.0...HEAD
|
|
80
|
+
[0.3.0]: https://github.com/marcelopazzo/jwt-pq/compare/v0.2.0...v0.3.0
|
|
60
81
|
[0.2.0]: https://github.com/marcelopazzo/jwt-pq/compare/v0.1.0...v0.2.0
|
|
61
82
|
[0.1.0]: https://github.com/marcelopazzo/jwt-pq/releases/tag/v0.1.0
|
data/Gemfile
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
|
2
|
+
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
|
|
127
|
+
aEqLBCQtoXub0y7W
|
|
128
|
+
-----END PRIVATE KEY-----
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark/ips"
|
|
4
|
+
require "jwt"
|
|
5
|
+
require "jwt/pq"
|
|
6
|
+
|
|
7
|
+
ALG = "ML-DSA-65"
|
|
8
|
+
PAYLOAD = { sub: "user-123", iat: 1_700_000_000, exp: 1_700_003_600 }.freeze
|
|
9
|
+
FIXTURE = File.expand_path("fixtures/ml_dsa_65_sk.pem", __dir__)
|
|
10
|
+
|
|
11
|
+
abort "Missing bench fixture: #{FIXTURE}" unless File.exist?(FIXTURE)
|
|
12
|
+
key = JWT::PQ::Key.from_pem(File.read(FIXTURE))
|
|
13
|
+
|
|
14
|
+
100.times { JWT.encode(PAYLOAD, key, ALG) }
|
|
15
|
+
|
|
16
|
+
report = Benchmark.ips(quiet: true) do |x|
|
|
17
|
+
x.config(time: 5, warmup: 2)
|
|
18
|
+
x.report("sign") { JWT.encode(PAYLOAD, key, ALG) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
entry = report.entries.first
|
|
22
|
+
ips = entry.stats.central_tendency
|
|
23
|
+
us_per_op = 1_000_000.0 / ips
|
|
24
|
+
|
|
25
|
+
puts "METRIC sigs_per_sec=#{ips.round(2)}"
|
|
26
|
+
puts "METRIC us_per_sig=#{us_per_op.round(2)}"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark/ips"
|
|
4
|
+
require "jwt"
|
|
5
|
+
require "jwt/pq"
|
|
6
|
+
|
|
7
|
+
ALG = "ML-DSA-65"
|
|
8
|
+
PAYLOAD = { sub: "user-123", iat: 1_700_000_000, exp: 1_700_003_600 }.freeze
|
|
9
|
+
FIXTURE = File.expand_path("fixtures/ml_dsa_65_sk.pem", __dir__)
|
|
10
|
+
|
|
11
|
+
abort "Missing bench fixture: #{FIXTURE}" unless File.exist?(FIXTURE)
|
|
12
|
+
key = JWT::PQ::Key.from_pem(File.read(FIXTURE))
|
|
13
|
+
pub_key = JWT::PQ::Key.from_public_key(ALG, key.public_key)
|
|
14
|
+
|
|
15
|
+
TOKEN = JWT.encode(PAYLOAD, key, ALG)
|
|
16
|
+
|
|
17
|
+
100.times { JWT.decode(TOKEN, pub_key, true, algorithms: [ALG], verify_expiration: false) }
|
|
18
|
+
|
|
19
|
+
report = Benchmark.ips(quiet: true) do |x|
|
|
20
|
+
x.config(time: 5, warmup: 2)
|
|
21
|
+
x.report("verify") { JWT.decode(TOKEN, pub_key, true, algorithms: [ALG], verify_expiration: false) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
entry = report.entries.first
|
|
25
|
+
ips = entry.stats.central_tendency
|
|
26
|
+
us_per_op = 1_000_000.0 / ips
|
|
27
|
+
|
|
28
|
+
puts "METRIC verifies_per_sec=#{ips.round(2)}"
|
|
29
|
+
puts "METRIC us_per_verify=#{us_per_op.round(2)}"
|
|
@@ -20,8 +20,13 @@ module JWT
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def verify(data:, signature:, verification_key:)
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
unless verification_key.is_a?(JWT::PQ::Key)
|
|
24
|
+
raise_verify_error!(
|
|
25
|
+
"Expected a JWT::PQ::Key, got #{verification_key.class}. " \
|
|
26
|
+
"Use JWT::PQ::Key.generate(:#{alg_symbol}) to create a key."
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
verification_key.verify(data, signature)
|
|
25
30
|
rescue JWT::PQ::Error
|
|
26
31
|
false
|
|
27
32
|
end
|
data/lib/jwt/pq/key.rb
CHANGED
|
@@ -6,7 +6,7 @@ module JWT
|
|
|
6
6
|
module PQ
|
|
7
7
|
# Represents an ML-DSA keypair (public + optional private key).
|
|
8
8
|
# Used as the signing/verification key for JWT operations.
|
|
9
|
-
class Key
|
|
9
|
+
class Key # rubocop:disable Metrics/ClassLength
|
|
10
10
|
ALGORITHM_ALIASES = {
|
|
11
11
|
ml_dsa_44: "ML-DSA-44",
|
|
12
12
|
ml_dsa_65: "ML-DSA-65",
|
|
@@ -50,12 +50,12 @@ module JWT
|
|
|
50
50
|
def sign(data)
|
|
51
51
|
raise KeyError, "Private key not available — cannot sign" unless @private_key
|
|
52
52
|
|
|
53
|
-
@ml_dsa.
|
|
53
|
+
@ml_dsa.sign_with_sk_buffer(data, sk_buffer)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Verify a signature using the public key.
|
|
57
57
|
def verify(data, signature)
|
|
58
|
-
@ml_dsa.
|
|
58
|
+
@ml_dsa.verify_with_pk_buffer(data, signature, pk_buffer)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Whether this key can be used for signing.
|
|
@@ -70,6 +70,8 @@ module JWT
|
|
|
70
70
|
@private_key.replace("\0" * @private_key.bytesize)
|
|
71
71
|
@private_key = nil
|
|
72
72
|
end
|
|
73
|
+
@sk_buffer&.clear
|
|
74
|
+
@sk_buffer = nil
|
|
73
75
|
true
|
|
74
76
|
end
|
|
75
77
|
|
|
@@ -153,6 +155,14 @@ module JWT
|
|
|
153
155
|
|
|
154
156
|
private
|
|
155
157
|
|
|
158
|
+
def sk_buffer
|
|
159
|
+
@sk_buffer ||= FFI::MemoryPointer.new(:uint8, @private_key.bytesize).put_bytes(0, @private_key)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def pk_buffer
|
|
163
|
+
@pk_buffer ||= FFI::MemoryPointer.new(:uint8, @public_key.bytesize).put_bytes(0, @public_key)
|
|
164
|
+
end
|
|
165
|
+
|
|
156
166
|
def resolve_algorithm(algorithm)
|
|
157
167
|
self.class.send(:resolve_algorithm, algorithm)
|
|
158
168
|
end
|
data/lib/jwt/pq/ml_dsa.rb
CHANGED
|
@@ -11,6 +11,34 @@ module JWT
|
|
|
11
11
|
"ML-DSA-87" => { public_key: 2592, secret_key: 4896, signature: 4627, nist_level: 5 }
|
|
12
12
|
}.freeze
|
|
13
13
|
|
|
14
|
+
@sign_handles = {}
|
|
15
|
+
@sign_handles_mutex = Mutex.new
|
|
16
|
+
|
|
17
|
+
def self.sign_handle(algorithm)
|
|
18
|
+
@sign_handles[algorithm] || @sign_handles_mutex.synchronize do
|
|
19
|
+
@sign_handles[algorithm] ||= begin
|
|
20
|
+
h = LibOQS.OQS_SIG_new(algorithm)
|
|
21
|
+
raise LiboqsError, "Failed to initialize #{algorithm}" if h.null?
|
|
22
|
+
|
|
23
|
+
h
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@verify_handles = {}
|
|
29
|
+
@verify_handles_mutex = Mutex.new
|
|
30
|
+
|
|
31
|
+
def self.verify_handle(algorithm)
|
|
32
|
+
@verify_handles[algorithm] || @verify_handles_mutex.synchronize do
|
|
33
|
+
@verify_handles[algorithm] ||= begin
|
|
34
|
+
h = LibOQS.OQS_SIG_new(algorithm)
|
|
35
|
+
raise LiboqsError, "Failed to initialize #{algorithm}" if h.null?
|
|
36
|
+
|
|
37
|
+
h
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
14
42
|
attr_reader :algorithm
|
|
15
43
|
|
|
16
44
|
def initialize(algorithm)
|
|
@@ -48,24 +76,28 @@ module JWT
|
|
|
48
76
|
def sign(message, secret_key)
|
|
49
77
|
validate_key_size!(secret_key, :secret_key)
|
|
50
78
|
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
sk_buf = FFI::MemoryPointer.new(:uint8, secret_key.bytesize)
|
|
80
|
+
sk_buf.put_bytes(0, secret_key)
|
|
81
|
+
sign_with_sk_buffer(message, sk_buf)
|
|
82
|
+
ensure
|
|
83
|
+
sk_buf&.clear
|
|
84
|
+
end
|
|
53
85
|
|
|
86
|
+
# Faster sign path: takes a pre-populated FFI::MemoryPointer holding the
|
|
87
|
+
# secret key. Caller is responsible for buffer lifecycle (allocation,
|
|
88
|
+
# zeroing). Used by JWT::PQ::Key to avoid re-allocating+copying the
|
|
89
|
+
# secret key on every sign call.
|
|
90
|
+
def sign_with_sk_buffer(message, sk_buf)
|
|
91
|
+
sig = self.class.sign_handle(@algorithm)
|
|
54
92
|
sig_buf = FFI::MemoryPointer.new(:uint8, @sizes[:signature])
|
|
55
93
|
sig_len = FFI::MemoryPointer.new(:size_t)
|
|
56
94
|
msg_buf = FFI::MemoryPointer.from_string(message)
|
|
57
|
-
sk_buf = FFI::MemoryPointer.new(:uint8, secret_key.bytesize)
|
|
58
|
-
sk_buf.put_bytes(0, secret_key)
|
|
59
95
|
|
|
60
96
|
status = LibOQS.OQS_SIG_sign(sig, sig_buf, sig_len,
|
|
61
97
|
msg_buf, message.bytesize, sk_buf)
|
|
62
98
|
raise SignatureError, "Signing failed for #{@algorithm}" unless status == LibOQS::OQS_SUCCESS
|
|
63
99
|
|
|
64
|
-
|
|
65
|
-
sig_buf.read_bytes(actual_len)
|
|
66
|
-
ensure
|
|
67
|
-
sk_buf&.clear
|
|
68
|
-
LibOQS.OQS_SIG_free(sig) if sig && !sig.null?
|
|
100
|
+
sig_buf.read_bytes(sig_len.read(:size_t))
|
|
69
101
|
end
|
|
70
102
|
|
|
71
103
|
# Verify a signature against a message and public key.
|
|
@@ -73,20 +105,24 @@ module JWT
|
|
|
73
105
|
def verify(message, signature, public_key)
|
|
74
106
|
validate_key_size!(public_key, :public_key)
|
|
75
107
|
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
pk_buf = FFI::MemoryPointer.new(:uint8, public_key.bytesize)
|
|
109
|
+
pk_buf.put_bytes(0, public_key)
|
|
110
|
+
verify_with_pk_buffer(message, signature, pk_buf)
|
|
111
|
+
end
|
|
78
112
|
|
|
113
|
+
# Faster verify path: takes a pre-populated FFI::MemoryPointer holding
|
|
114
|
+
# the public key. Caller is responsible for buffer lifecycle. Used by
|
|
115
|
+
# JWT::PQ::Key to avoid re-allocating+copying the public key on every
|
|
116
|
+
# verify call.
|
|
117
|
+
def verify_with_pk_buffer(message, signature, pk_buf)
|
|
118
|
+
sig = self.class.verify_handle(@algorithm)
|
|
79
119
|
msg_buf = FFI::MemoryPointer.from_string(message)
|
|
80
120
|
sig_buf = FFI::MemoryPointer.new(:uint8, signature.bytesize)
|
|
81
121
|
sig_buf.put_bytes(0, signature)
|
|
82
|
-
pk_buf = FFI::MemoryPointer.new(:uint8, public_key.bytesize)
|
|
83
|
-
pk_buf.put_bytes(0, public_key)
|
|
84
122
|
|
|
85
123
|
status = LibOQS.OQS_SIG_verify(sig, msg_buf, message.bytesize,
|
|
86
124
|
sig_buf, signature.bytesize, pk_buf)
|
|
87
125
|
status == LibOQS::OQS_SUCCESS
|
|
88
|
-
ensure
|
|
89
|
-
LibOQS.OQS_SIG_free(sig) if sig && !sig.null?
|
|
90
126
|
end
|
|
91
127
|
|
|
92
128
|
# Key sizes for this algorithm
|
data/lib/jwt/pq/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jwt-pq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marcelo Almeida
|
|
@@ -66,6 +66,9 @@ files:
|
|
|
66
66
|
- LICENSE
|
|
67
67
|
- README.md
|
|
68
68
|
- Rakefile
|
|
69
|
+
- bench/fixtures/ml_dsa_65_sk.pem
|
|
70
|
+
- bench/sign_throughput.rb
|
|
71
|
+
- bench/verify_throughput.rb
|
|
69
72
|
- bin/smoke.rb
|
|
70
73
|
- ext/jwt/pq/extconf.rb
|
|
71
74
|
- jwt-pq.gemspec
|