Module: MlDsa
- Defined in:
- lib/ml_dsa.rb,
lib/ml_dsa/config.rb,
lib/ml_dsa/version.rb,
lib/ml_dsa/internal.rb,
lib/ml_dsa/key_pair.rb,
lib/ml_dsa/requests.rb,
lib/ml_dsa/public_key.rb,
lib/ml_dsa/secret_key.rb,
lib/ml_dsa/batch_builder.rb,
lib/ml_dsa/parameter_set.rb,
ext/ml_dsa/ml_dsa_ext.c
Defined Under Namespace
Classes: BatchBuilder, Config, Error, KeyPair, ParameterSet, PublicKey, Result, SecretKey, SignRequest, VerifyRequest
Constant Summary collapse
- SEED_BYTES =
Seed size (bytes) for deterministic key generation.
32- VERSION =
"0.1.0"- ML_DSA_OIDS =
OIDs assigned by NIST for ML-DSA parameter sets (FIPS 204).
{ 44 => "2.16.840.1.101.3.4.3.17", 65 => "2.16.840.1.101.3.4.3.18", 87 => "2.16.840.1.101.3.4.3.19" }.freeze
- ML_DSA_OID_TO_CODE =
ML_DSA_OIDS.invert.freeze
Class Method Summary collapse
-
._keygen(rb_ps) ⇒ Object
---- Keygen + batch ops — module singleton methods ----.
-
._keygen_seed(rb_ps, rb_seed) ⇒ Object
------------------------------------------------------------------.
-
._param_data ⇒ Object
------------------------------------------------------------------.
- ._sign_many(rb_ops) ⇒ Object
- ._verify_many(rb_ops) ⇒ Object
-
.batch(config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) {|BatchBuilder| ... } ⇒ Array<String>
Unified batch builder — collects sign/verify ops and executes them in a single GVL drop.
-
.config ⇒ Config
The default global Config instance.
-
.keygen(param_set, seed: nil, config: nil) ⇒ KeyPair
Generate a key pair for the given parameter set.
-
.random_source ⇒ Proc?
The current random source on the default config.
-
.random_source=(source) ⇒ Object
Set the random source on the default config.
-
.sign_many(operations, config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) ⇒ Array<String>
Sign multiple messages in a single GVL drop.
-
.subscribe(&block) ⇒ Object
Subscribe to instrumentation events on the default config.
-
.unsubscribe(subscriber) ⇒ Object
Remove a subscriber from the default config.
-
.verify_many(operations, config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) ⇒ Array<Result>
Verify multiple signatures in a single GVL drop.
Class Method Details
._keygen(rb_ps) ⇒ Object
---- Keygen + batch ops — module singleton methods ----
825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 |
# File 'ext/ml_dsa/ml_dsa_ext.c', line 825
static VALUE rb_ml_dsa_keygen(VALUE self, VALUE rb_ps)
{
(void)self;
int ps = NUM2INT(rb_ps);
const ml_dsa_impl_t *impl = find_impl(ps);
struct keygen_state s;
s.impl = impl;
s.ps = ps;
s.has_seed = 0;
s.pk_buf = NULL;
s.sk_buf = NULL;
/* Generate seed from OS CSPRNG before GVL drop */
randombytes(s.seed, ML_DSA_SEED_BYTES);
return ML_DSA_ENSURE(keygen_body, keygen_ensure, &s);
}
|
._keygen_seed(rb_ps, rb_seed) ⇒ Object
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 |
# File 'ext/ml_dsa/ml_dsa_ext.c', line 848
static VALUE rb_ml_dsa_keygen_seed(VALUE self, VALUE rb_ps, VALUE rb_seed)
{
(void)self;
Check_Type(rb_seed, T_STRING);
if (RSTRING_LEN(rb_seed) != ML_DSA_SEED_BYTES)
rb_raise(rb_eArgError, "seed must be exactly %d bytes, got %ld",
ML_DSA_SEED_BYTES, RSTRING_LEN(rb_seed));
int ps = NUM2INT(rb_ps);
const ml_dsa_impl_t *impl = find_impl(ps);
struct keygen_state s;
s.impl = impl;
s.ps = ps;
s.has_seed = 1;
s.pk_buf = NULL;
s.sk_buf = NULL;
memcpy(s.seed, RSTRING_PTR(rb_seed), ML_DSA_SEED_BYTES);
return ML_DSA_ENSURE(keygen_body, keygen_ensure, &s);
}
|
._param_data ⇒ Object
1228 1229 1230 1231 1232 |
# File 'ext/ml_dsa/ml_dsa_ext.c', line 1228
static VALUE rb_ml_dsa_param_data(VALUE self)
{
(void)self;
return ml_dsa_param_data_cache;
}
|
._sign_many(rb_ops) ⇒ Object
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 |
# File 'ext/ml_dsa/ml_dsa_ext.c', line 1037
static VALUE rb_ml_dsa_sign_many(VALUE self, VALUE rb_ops)
{
(void)self;
Check_Type(rb_ops, T_ARRAY);
long count = RARRAY_LEN(rb_ops);
if (count == 0) {
VALUE empty = rb_ary_new();
OBJ_FREEZE(empty);
return empty;
}
struct sign_batch_state s;
s.rb_ops = rb_ops;
s.count = (size_t)count;
s.items_initialized = 0;
s.items = NULL;
return ML_DSA_ENSURE(sign_many_body, sign_many_ensure, &s);
}
|
._verify_many(rb_ops) ⇒ Object
1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 |
# File 'ext/ml_dsa/ml_dsa_ext.c', line 1204
static VALUE rb_ml_dsa_verify_many(VALUE self, VALUE rb_ops)
{
(void)self;
Check_Type(rb_ops, T_ARRAY);
long count = RARRAY_LEN(rb_ops);
if (count == 0) {
VALUE empty = rb_ary_new();
OBJ_FREEZE(empty);
return empty;
}
struct verify_batch_state s;
s.rb_ops = rb_ops;
s.count = (size_t)count;
s.items_initialized = 0;
s.items = NULL;
return ML_DSA_ENSURE(verify_many_body, verify_many_ensure, &s);
}
|
.batch(config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) {|BatchBuilder| ... } ⇒ Array<String>
Unified batch builder — collects sign/verify ops and executes them in a single GVL drop.
179 180 181 182 183 184 |
# File 'lib/ml_dsa.rb', line 179 def batch(config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) cfg = config || @config builder = BatchBuilder.new yield builder builder.execute(config: cfg, yield_every: yield_every) end |
.config ⇒ Config
The default global Config instance.
22 23 24 |
# File 'lib/ml_dsa.rb', line 22 def self.config @config end |
.keygen(param_set, seed: nil, config: nil) ⇒ KeyPair
Generate a key pair for the given parameter set.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/ml_dsa.rb', line 68 def keygen(param_set, seed: nil, config: nil) cfg = config || @config ps = Internal.resolve_ps(param_set) t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) if seed raise TypeError, "seed must be a String" unless seed.is_a?(String) seed_bin = seed.b raise ArgumentError, "seed must be exactly 32 bytes, got #{seed_bin.bytesize}" unless seed_bin.bytesize == 32 pk, sk = _keygen_seed(ps.code, seed_bin) elsif cfg.random_source # Pluggable RNG: generate a seed and use deterministic keygen rng_seed = cfg.random_source.call(SEED_BYTES) unless rng_seed.is_a?(String) && rng_seed.bytesize == SEED_BYTES raise ArgumentError, "random_source must return #{SEED_BYTES} bytes, " \ "got #{rng_seed.is_a?(String) ? rng_seed.bytesize : rng_seed.class}" end pk, sk = _keygen_seed(ps.code, rng_seed.b) else pk, sk = _keygen(ps.code) end now = Time.now.freeze pk.instance_variable_set(:@created_at, now) sk.instance_variable_set(:@created_at, now) duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - t0 cfg.notify(:keygen, ps, 1, duration) KeyPair.new(pk, sk) end |
.random_source ⇒ Proc?
Returns the current random source on the default config.
45 46 47 |
# File 'lib/ml_dsa.rb', line 45 def self.random_source @config.random_source end |
.random_source=(source) ⇒ Object
Set the random source on the default config.
51 52 53 |
# File 'lib/ml_dsa.rb', line 51 def self.random_source=(source) @config.random_source = source end |
.sign_many(operations, config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) ⇒ Array<String>
Sign multiple messages in a single GVL drop.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/ml_dsa.rb', line 104 def sign_many(operations, config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) cfg = config || @config unless operations.is_a?(Array) raise TypeError, "operations must be an Array, got #{operations.class}" end return [].freeze if operations.empty? ops = operations.each_with_index.map do |op, i| Internal.maybe_yield(i, yield_every) normalize_sign_op(op, i, cfg) end t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) result = _sign_many(ops) duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - t0 ps = operations.first&.sk&.param_set cfg.notify(:sign, ps, operations.size, duration) result end |
.subscribe(&block) ⇒ Object
Subscribe to instrumentation events on the default config.
34 35 36 |
# File 'lib/ml_dsa.rb', line 34 def self.subscribe(&block) @config.subscribe(&block) end |
.unsubscribe(subscriber) ⇒ Object
Remove a subscriber from the default config.
40 41 42 |
# File 'lib/ml_dsa.rb', line 40 def self.unsubscribe(subscriber) @config.unsubscribe(subscriber) end |
.verify_many(operations, config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) ⇒ Array<Result>
Verify multiple signatures in a single GVL drop.
Returns Result objects with per-item details: .ok? indicates
success, .reason distinguishes wrong-size signatures from
cryptographic verification failures.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/ml_dsa.rb', line 133 def verify_many(operations, config: nil, yield_every: Internal::DEFAULT_YIELD_EVERY) cfg = config || @config unless operations.is_a?(Array) raise TypeError, "operations must be an Array, got #{operations.class}" end return [].freeze if operations.empty? # Pre-check signature sizes to distinguish size errors from crypto failures size_ok = operations.map do |op| Internal.resolve_ps(op.pk.param_set) op.signature.bytesize == op.pk.param_set.signature_bytes end ops = operations.each_with_index.map do |op, i| Internal.maybe_yield(i, yield_every) normalize_verify_op(op, i) end t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) bools = _verify_many(ops) duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - t0 ps = operations.first&.pk&.param_set cfg.notify(:verify, ps, operations.size, duration) results = operations.each_with_index.map do |op, i| if bools[i] Result.new(value: true, ok: true, reason: nil) elsif !size_ok[i] expected = op.pk.param_set.signature_bytes Result.new(value: false, ok: false, reason: "wrong_signature_size: expected #{expected}, got #{op.signature.bytesize}") else Result.new(value: false, ok: false, reason: "verification_failed") end end results.freeze end |