java.lang.Object
java.lang.Thread
net.i2p.util.I2PThread
net.i2p.router.crypto.pqc.MLKEMKeyFactory
All Implemented Interfaces:
Runnable, KeyFactory

public class MLKEMKeyFactoryextends I2PThread implements KeyFactory
Try to keep key pairs at the ready. It's important to do this in a separate thread, because if we run out, the pairs are generated other threads, and it can fall behind.

Started by ECIESAEADEngine. One type per-thread. Only used for 768, for now.

  • Field Details

    • lastInstance

      private static volatile MLKEMKeyFactory lastInstance
      The factory created last, for reuse.
    • _context

      private final I2PAppContext _context
      The app context.
    • _log

      private final Log _log
      Logger for the key precalculation.
    • _minSize

      private volatile int _minSize
      Keys kept in the queue at least.
    • _maxSize

      private volatile int _maxSize
      Keys kept in the queue at most.
    • _calcDelay

      private volatile int _calcDelay
      Delay between precalculation batches.
    • _keys

      private final LinkedBlockingQueue<KeyPair> _keys
      Queue of the precalculated key pairs.
    • _type

      private final EncType _type
      The ML-KEM parameter set generated.
    • _isRunning

      private volatile boolean _isRunning
      True while the factory thread runs.
    • _checkDelay

      private long _checkDelay
      Delay before checking the queue again.
    • _emptyCount

      private final AtomicInteger _emptyCount
      Times the queue was found empty.
    • _usedCount

      private final AtomicInteger _usedCount
      Keys taken from the queue.
    • PROP_MLKEM_PRECALC_MIN

      private static final String PROP_MLKEM_PRECALC_MIN
      Config key for the minimum queue size.
      See Also:
    • PROP_MLKEM_PRECALC_MAX

      private static final String PROP_MLKEM_PRECALC_MAX
      Config key for the maximum queue size.
      See Also:
    • PROP_MLKEM_PRECALC_DELAY

      private static final String PROP_MLKEM_PRECALC_DELAY
      Config key for the precalculation delay.
      See Also:
    • BYTES_PER_PAIR

      static final int BYTES_PER_PAIR
      Approximate heap size of one pooled ML-KEM-768 keypair, in bytes. The pool scales with cores and memory, so the scaled defaults are capped by the byte budget below - an uncapped count scale can reach 100+ MB on high-core boxes.
      See Also:
    • MAX_POOL_BYTES

      static final long MAX_POOL_BYTES
      Ceiling on the whole pool, in bytes.
      See Also:
    • DEFAULT_MLKEM_PRECALC_MIN

      private static final int DEFAULT_MLKEM_PRECALC_MIN
      Default minimum queue size.
      See Also:
    • DEFAULT_MLKEM_PRECALC_MAX

      private static final int DEFAULT_MLKEM_PRECALC_MAX
      Default maximum queue size.
      See Also:
    • DEFAULT_MLKEM_PRECALC_DELAY

      private static final int DEFAULT_MLKEM_PRECALC_DELAY
      Default precalculation delay, in ms.
      See Also:
    • HARD_MAX

      private static final int HARD_MAX
      Largest queue size allowed.
      See Also:
    • BYTE_CAP

      static final int BYTE_CAP
      The byte budget expressed in keypairs. This, not HARD_MAX, is what bounds the pool - HARD_MAX alone would allow 32768 pairs, about 128 MB, which is what the demand-driven resize used to be able to reach.
  • Constructor Details

    • MLKEMKeyFactory

      public MLKEMKeyFactory(I2PAppContext ctx, EncType type)
      Alice side only.
      Parameters:
      ctx - the application context whose log, stat and system info are used
      type - must be one of the internal types MLKEM*_INT
  • Method Details

    • getByteCap

      public static int getByteCap()
      The pool ceiling in keypairs that setMinSize(int) will accept.

      Public because the auto-tuner declares a range for this pool and must not propose a floor the setter silently rewrites; a target above this cap lands back on the current value and reads as a param that will not tune.

      Returns:
      the byte-budget cap expressed in keypairs
    • getInstance

      public static MLKEMKeyFactory getInstance()
      The most recently constructed factory.
      Returns:
      the most recently constructed factory, or null if none has been created yet
    • getMinSize

      public int getMinSize()
      Lower bound on the precalculated key queue size.
      Returns:
      the min size
    • setMinSize

      public void setMinSize(int min)
      Minimum precalc queue size.
      Parameters:
      min - the floor on the queue, clamped into [1, BYTE_CAP] so a Tuner value cannot lift the floor past the byte budget
    • getMaxSize

      public int getMaxSize()
      Upper bound on the precalculated key queue size.
      Returns:
      the max size
    • setMaxSize

      public void setMaxSize(int max)
      Maximum precalc queue size.
      Parameters:
      max - the ceiling on the queue, raised to the minimum if it is lower
    • getSize

      public int getSize()
      Returns the current number of precalc keys queued.
      Returns:
      the number of prebuilt keypairs currently waiting in the pool
    • boundedPoolSize

      static int boundedPoolSize(int minSize, int demandMax, int cap)
      Bound a pool size: at least four above the floor, never above the cap.

      The floor is capped too, so a configured minimum larger than the cap cannot smuggle the pool past the byte budget by way of the floor.

      Pure and static so the byte budget's reach can be pinned without running the precalc thread.

      Parameters:
      minSize - the pool floor the pool must stay above
      demandMax - the size the caller wants, from demand or configuration
      cap - the ceiling, in keypairs
      Returns:
      the size to use, always ≤ cap
    • poolFloor

      static int poolFloor(int minSize, int cap)
      The smallest size the pool may be driven to: its floor, never above the cap. A floor set at the cap pins the pool there instead of letting every "at least min + 4" margin push it past the byte budget.
      Parameters:
      minSize - the pool floor
      cap - the ceiling, in keypairs
      Returns:
      the effective floor, always ≤ cap
    • refreshPoolSize

      public void refreshPoolSize()
      Called by the Tuner every 15 seconds. Adjusts max pool size based on demand.

      Demand is bounded by the byte budget, not by HARD_MAX: the earlier bound let demand drive the pool to 32768 pairs, roughly 128 MB, and the memory-pressure halving below only engages above 70% pressure, so a router with a large heap could sit there indefinitely.

    • pressureAdjustedMax

      static int pressureAdjustedMax(int maxSize, int minSize, int cap, double memPressure)
      Shrink the pool ceiling under memory pressure, without letting the shrink raise it past the byte budget on its way back down.

      The floor of each shrink goes through poolFloor(int, int), so a floor set at the budget cannot add its four-pair margin on top of the cap.

      Pure and static so the pressure branches can be pinned without driving the JVM's free-memory ratio.

      Parameters:
      maxSize - the ceiling demand produced
      minSize - the pool floor
      cap - the ceiling, in keypairs
      memPressure - the fraction of the heap in use, 0 to 1
      Returns:
      the ceiling to use, never above cap
    • shutdown

      public void shutdown()
      Note that this stops the singleton precalc thread. You don't want to do this if there are multiple routers in the JVM. Fix this if you care. See Router.shutdown(int).
    • run

      public void run()
      Description copied from class: I2PThread
      Run the thread, catching and reporting OOM and other failures.
      Specified by:
      run in interface Runnable
      Overrides:
      run in class I2PThread
    • run2

      private void run2() throws GeneralSecurityException
      Runs the precalculation loop.
      Throws:
      GeneralSecurityException - if a key pair cannot be generated
    • getKeys

      public KeyPair getKeys()
      Pulls a prebuilt keypair from the queue, or if not available, construct a new one.
      Specified by:
      getKeys in interface KeyFactory
      Returns:
      an ML-KEM keypair, taken from the prebuilt pool when one was available and generated on demand otherwise, never null
    • precalc

      private KeyPair precalc() throws GeneralSecurityException
      Generates one key pair.
      Returns:
      the new key pair
      Throws:
      GeneralSecurityException - if the generation fails
    • returnUnused

      public void returnUnused(KeyPair kp)
      Return an unused key pair to be put back onto the queue for reuse.
      Parameters:
      kp - the pair that was taken from the queue but never used
    • addKeys

      private final boolean addKeys(KeyPair kp)
      Adds a precomputed key pair to the pool.
      Parameters:
      kp - the key pair to add
      Returns:
      true if successful, false if full