jwt-pq 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fc1d22df85af9d7c753800dbd77e08ce874d7c1bf177aa9afafab54a3ef6aa9
4
- data.tar.gz: 3f8d5ef4114c28e79eeb609eac5bcff0b5e5bbe0b4869006b20e1e076cf7142f
3
+ metadata.gz: d662c64af196cc33ed4b288a641442317fe49881eef85bfadc2385c0bee7e63f
4
+ data.tar.gz: 599a753241ae95a168461d1a7f61b2a2b2147da13f1456479758873657fb18f5
5
5
  SHA512:
6
- metadata.gz: 333d7706c667a66e7858813a4de3b813d46c097b283822ed228f3fa23bd5a58fe67dfd0b2cc3a0b7a3e71aceac70ff604c3d8084871828585ce633beba6f7199
7
- data.tar.gz: d4ba329c764b33f0aea5dd37f10d0c9eb9623eb34d999bc003382096a18c58e2b914ad51fdd7e472b0e400a5504690acb21744a922944ded0b67cb5ee9bf9e70
6
+ metadata.gz: c360bb8b3b5a8fdad530e3cba2f6d3630999d635669d85e5ac57854f746bbf8250559073d258ca2aa95f0f443208aded8c90b68a15f75f58a00370963fa5c943
7
+ data.tar.gz: e57d739e3567413f845a685caf55305d7704120fee29adf31ab09cbb0a85cbc2b6c0713ad4e85db73c65c1238d323ab04ab5e079adef06f2918a2114fb06aa96
data/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-04-19
11
+
12
+ ### Added
13
+
14
+ - Hybrid-sign throughput benchmark at `bench/hybrid_sign_throughput.rb`
15
+ - Hybrid-verify throughput benchmark at `bench/hybrid_verify_throughput.rb`
16
+ - Parameterized `bench/sign_throughput.rb` and `bench/verify_throughput.rb` via `ALG` env var — previously hardcoded to `ML-DSA-65`, now supports all three security levels
17
+ - PEM key fixtures for ML-DSA-44 and ML-DSA-87 under `bench/fixtures/`
18
+ - `bench/generate_fixtures.rb` to regenerate bench fixtures idempotently
19
+ - Cross-implementation interop CI against `dilithium-py` (independent pure-Python ML-DSA / FIPS 204 implementation) — runs on push, PR, and weekly
20
+
21
+ ### Changed
22
+
23
+ - **Hybrid EdDSA+ML-DSA-65 sign throughput: +12.1%** (5200 → 5831 sigs/s on Ruby 3.4.6 + liboqs 0.15.0). Inline type-check in `HybridEdDsa#sign` (+1.6%) plus cached frozen header hash and precomputed `ml_dsa_algorithm` at init (+10.4%) — `#header` is called once per `JWT.encode`, so eliminating the per-call Hash allocation and `String#sub` compounds noticeably.
24
+ - **Hybrid EdDSA+ML-DSA-65 verify throughput: +2.3%** (4812 → 4923 verifies/s). Inline type-check in `HybridEdDsa#verify`, mirroring the sign-side pattern.
25
+ - `bench/` directory no longer packaged into the published gem (smaller install footprint).
26
+
27
+ ### Benchmarks
28
+
29
+ Throughput on Ruby 3.4.6, macOS x86_64, liboqs 0.15.0 (benchmark-ips, 2s warmup + 5s measurement):
30
+
31
+ | Algorithm | Sign | Verify |
32
+ |------------|----------:|-----------:|
33
+ | ML-DSA-44 | 9678 ops/s | 12650 ops/s |
34
+ | ML-DSA-65 | 6236 ops/s | 8567 ops/s |
35
+ | ML-DSA-87 | 3591 ops/s | 6510 ops/s |
36
+
10
37
  ## [0.3.0] - 2026-04-19
11
38
 
12
39
  ### Added
@@ -76,7 +103,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
76
103
  - Optional dependency on jwt-eddsa / ed25519
77
104
  - Error classes: `LiboqsError`, `KeyError`, `SignatureError`, `MissingDependencyError`
78
105
 
79
- [Unreleased]: https://github.com/marcelopazzo/jwt-pq/compare/v0.3.0...HEAD
106
+ [Unreleased]: https://github.com/marcelopazzo/jwt-pq/compare/v0.4.0...HEAD
107
+ [0.4.0]: https://github.com/marcelopazzo/jwt-pq/compare/v0.3.0...v0.4.0
80
108
  [0.3.0]: https://github.com/marcelopazzo/jwt-pq/compare/v0.2.0...v0.3.0
81
109
  [0.2.0]: https://github.com/marcelopazzo/jwt-pq/compare/v0.1.0...v0.2.0
82
110
  [0.1.0]: https://github.com/marcelopazzo/jwt-pq/releases/tag/v0.1.0
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/jwt-pq.svg)](https://rubygems.org/gems/jwt-pq)
4
4
  [![CI](https://github.com/marcelopazzo/jwt-pq/actions/workflows/ci.yml/badge.svg)](https://github.com/marcelopazzo/jwt-pq/actions/workflows/ci.yml)
5
+ [![Cross-interop](https://github.com/marcelopazzo/jwt-pq/actions/workflows/interop.yml/badge.svg)](https://github.com/marcelopazzo/jwt-pq/actions/workflows/interop.yml)
5
6
  [![codecov](https://codecov.io/gh/marcelopazzo/jwt-pq/graph/badge.svg)](https://codecov.io/gh/marcelopazzo/jwt-pq)
6
7
 
7
8
  Post-quantum JWT signatures for Ruby. Adds **ML-DSA** (FIPS 204) support to the [ruby-jwt](https://github.com/jwt/ruby-jwt) ecosystem, with an optional **hybrid EdDSA + ML-DSA** mode.
data/jwt-pq.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
 
37
37
  spec.files = Dir.chdir(__dir__) do
38
38
  `git ls-files -z`.split("\x0").reject do |f|
39
- f.start_with?("spec/", "vendor/", ".github/") ||
39
+ f.start_with?("spec/", "vendor/", ".github/", "bench/") ||
40
40
  f.match?(/\A(?:\.git|\.rspec|\.rubocop|jwt-pq-plan)/)
41
41
  end
42
42
  end
@@ -17,24 +17,36 @@ module JWT
17
17
 
18
18
  def initialize(alg)
19
19
  @alg = alg
20
+ @ml_dsa_algorithm = alg.sub("EdDSA+", "")
21
+ @header = { "alg" => alg, "pq_alg" => @ml_dsa_algorithm }.freeze
20
22
  end
21
23
 
22
24
  def header(*)
23
- { "alg" => alg, "pq_alg" => ml_dsa_algorithm }
25
+ @header
24
26
  end
25
27
 
26
28
  def sign(data:, signing_key:)
27
- key = resolve_signing_key(signing_key)
29
+ unless signing_key.is_a?(JWT::PQ::HybridKey)
30
+ raise_sign_error!(
31
+ "Expected a JWT::PQ::HybridKey, got #{signing_key.class}. " \
32
+ "Use JWT::PQ::HybridKey.generate to create a hybrid key."
33
+ )
34
+ end
35
+ raise_sign_error!("Both Ed25519 and ML-DSA private keys required") unless signing_key.private?
28
36
 
29
- ed_sig = key.ed25519_signing_key.sign(data)
30
- ml_sig = key.ml_dsa_key.sign(data)
37
+ ed_sig = signing_key.ed25519_signing_key.sign(data)
38
+ ml_sig = signing_key.ml_dsa_key.sign(data)
31
39
 
32
40
  # Concatenate: Ed25519 (64 bytes) || ML-DSA (variable)
33
41
  ed_sig + ml_sig
34
42
  end
35
43
 
36
44
  def verify(data:, signature:, verification_key:)
37
- key = resolve_verification_key(verification_key)
45
+ unless verification_key.is_a?(JWT::PQ::HybridKey)
46
+ raise_verify_error!(
47
+ "Expected a JWT::PQ::HybridKey, got #{verification_key.class}."
48
+ )
49
+ end
38
50
 
39
51
  return false if signature.bytesize <= ED25519_SIG_SIZE
40
52
 
@@ -42,13 +54,13 @@ module JWT
42
54
  ml_sig = signature.byteslice(ED25519_SIG_SIZE..)
43
55
 
44
56
  ed_valid = begin
45
- key.ed25519_verify_key.verify(ed_sig, data)
57
+ verification_key.ed25519_verify_key.verify(ed_sig, data)
46
58
  true
47
59
  rescue Ed25519::VerifyError
48
60
  false
49
61
  end
50
62
 
51
- ml_valid = key.ml_dsa_key.verify(data, ml_sig)
63
+ ml_valid = verification_key.ml_dsa_key.verify(data, ml_sig)
52
64
 
53
65
  ed_valid && ml_valid
54
66
  rescue JWT::PQ::Error
@@ -57,9 +69,7 @@ module JWT
57
69
 
58
70
  private
59
71
 
60
- def ml_dsa_algorithm
61
- alg.sub("EdDSA+", "")
62
- end
72
+ attr_reader :ml_dsa_algorithm
63
73
 
64
74
  def resolve_signing_key(key)
65
75
  case key
@@ -2,6 +2,6 @@
2
2
 
3
3
  module JWT
4
4
  module PQ
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcelo Almeida
@@ -66,9 +66,6 @@ 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
72
69
  - bin/smoke.rb
73
70
  - ext/jwt/pq/extconf.rb
74
71
  - jwt-pq.gemspec
@@ -1,128 +0,0 @@
1
- -----BEGIN PRIVATE KEY-----
2
- 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
- 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
- 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
- 4vv5jl9WXVUnUlIWN3UzNigURhMGERghEwBgIhFkYUdDU4OBRBh3AHOAAHJUCART
6
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 3NiImMTx048qXQpfBSKUjs1WNQJw4B/2sI6SxI8dK6/HxT9QjJaP8D0g6jAFPCg0
60
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- QEwhBnfgq6t07uIH8Pf5HwtmXimSRXnAXWvCUOQ0e8JbG6muTxHiQmn+QFLAEQ16
92
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 1Jdkc7G3q9jk0an9B5K/VMvVFAU2qVRt0rCVIpm+rZE9lHMBJq0ojveIyFA8nciI
116
- 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
- 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
- 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
- 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
- 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
- 2kWnfLvWEFBCDrK5Op5LEXatZLPeqDgNcNN26iPuCpwLY8YTdoGIo/j6f5dFrXpL
122
- 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
- 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
- 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
- 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
- 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
127
- aEqLBCQtoXub0y7W
128
- -----END PRIVATE KEY-----
@@ -1,26 +0,0 @@
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)}"
@@ -1,29 +0,0 @@
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)}"