From 61926a2dac4fa0eb83d73edec80ef6ab77878cde Mon Sep 17 00:00:00 2001 From: davorradic Date: Wed, 18 Mar 2026 10:44:25 +0100 Subject: [PATCH 1/3] Add TRNG source indicator to GUI status panel Show a status LED in the bottom-left panel that displays the current TRNG source: "INFINITE NOISE TRNG" (green) when hardware device is connected, "CACHED HOTBITS" or "PSEUDO RANDOM" (gray) as fallback. Also fix startGui.bat to run JAR from target/ folder and fix PJOGL icon crash when running from JAR. Co-Authored-By: Claude Opus 4.6 (1M context) --- pom.xml | 2 +- .../processing2/AetherOneConstants.java | 1 + .../polos/AetherOnePi/processing2/AetherOneUI.java | 2 +- .../processing2/elements/GuiElements.java | 8 ++++++++ .../processing2/hotbits/HotbitsHandler.java | 14 ++++++++++++++ startGui.bat | 5 ++++- 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ac93340..afe1c10 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 2.2.0 - 24 + 23 2.7 1.2 5.1.3.201810200350-r diff --git a/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneConstants.java b/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneConstants.java index b268a4c..57b6772 100644 --- a/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneConstants.java +++ b/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneConstants.java @@ -51,6 +51,7 @@ public class AetherOneConstants { public static final String BROADCASTING = "BROADCASTING"; public static final String CLEARING = "CLEARING"; public static final String COPYING = "COPYING"; + public static final String TRNG = "TRNG"; public static final String HOTBITS = "HOTBITS"; public static final String PACKAGES = "PACKAGES"; public static final String CACHE = "CACHE"; diff --git a/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneUI.java b/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneUI.java index 3fd30b9..a1eeb69 100644 --- a/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneUI.java +++ b/src/main/java/de/isuret/polos/AetherOnePi/processing2/AetherOneUI.java @@ -93,7 +93,6 @@ public static void main(String[] args) { public void settings() { p = this; - PJOGL.setIcon(getClass().getClassLoader().getResource("icons/aetherOnePi.png").getPath()); try { titleAffix = " " + new File(FilenameUtils.getFullPathNoEndSeparator(new File(".").getAbsolutePath())).getName(); @@ -309,6 +308,7 @@ public void run() { .addStatusLED(AetherOneConstants.CLEARING) .addStatusLED(AetherOneConstants.GROUNDING) .addStatusLED(AetherOneConstants.COPYING) + .addStatusLED(AetherOneConstants.TRNG) .addSlider(AetherOneConstants.PACKAGES, 100, 10, 100) .addSlider(AetherOneConstants.CACHE, 100, 10, 20000) .addSlider(AetherOneConstants.PROGRESS, 100, 10, 100) diff --git a/src/main/java/de/isuret/polos/AetherOnePi/processing2/elements/GuiElements.java b/src/main/java/de/isuret/polos/AetherOnePi/processing2/elements/GuiElements.java index 04d6fc0..eed9d20 100644 --- a/src/main/java/de/isuret/polos/AetherOnePi/processing2/elements/GuiElements.java +++ b/src/main/java/de/isuret/polos/AetherOnePi/processing2/elements/GuiElements.java @@ -294,6 +294,14 @@ public void draw() { drawableElementList.add(broadcastElement); } + // Update TRNG status LED and display source name + boolean trngConnected = p.getHotbitsHandler().isTrngConnected(); + StatusLED trngLed = statusLEDMap.get(AetherOneConstants.TRNG); + if (trngLed != null) { + trngLed.setOn(trngConnected); + trngLed.setText(p.getHotbitsHandler().getTrngSourceName()); + } + if (p.getSettings().getBoolean(SettingsScreen.DYNAMIC_ADJUSTMENTS, false)) { p.fill(0,255,0); p.text("DYNAMIC ADJUSTMENTS", 22,700); diff --git a/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java b/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java index 0edad32..d7bbeca 100644 --- a/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java +++ b/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java @@ -181,6 +181,20 @@ public int getInteger(int bound) { return getInteger(0, bound); } + public boolean isTrngConnected() { + return infiniteNoiseGenerator != null && infiniteNoiseGenerator.isRunning(); + } + + public String getTrngSourceName() { + if (infiniteNoiseGenerator != null && infiniteNoiseGenerator.isRunning()) { + return "INFINITE NOISE TRNG"; + } else if (hotbits.size() > 0) { + return "CACHED HOTBITS"; + } else { + return "PSEUDO RANDOM"; + } + } + @Override public int getInteger(Integer min, Integer max) { if (hotbits != null && hotbits.size() > 0) { diff --git a/startGui.bat b/startGui.bat index 15e0ec5..8e29632 100644 --- a/startGui.bat +++ b/startGui.bat @@ -1 +1,4 @@ -call java -jar AetherOnePi-2.2.0.jar \ No newline at end of file +@echo off +cd /d "%~dp0" +java -jar target/AetherOnePi-2.2.0.jar +pause \ No newline at end of file From 5c6e0f2a3b4cb364ae601cbd7d9c77f882c8c0c3 Mon Sep 17 00:00:00 2001 From: davorradic Date: Sat, 21 Mar 2026 00:48:34 +0100 Subject: [PATCH 2/3] Add TrueRNG Pro v2.0 (ubld.it) as hotbits source Integrate the TrueRNG Pro v2.0 USB hardware TRNG as an additional hotbits source alongside the Infinite Noise TRNG. The device is a USB CDC serial device that streams whitened random bytes when DTR is asserted. Auto-detects by USB VID:PID (04D8:EBB5) on startup and falls back gracefully when not connected. GUI status indicator shows "TRUERNG PRO" when device is active. Co-Authored-By: Claude Opus 4.6 (1M context) --- pom.xml | 7 + .../hotbits/hrng/TrueRngProGenerator.java | 273 ++++++++++++++++++ .../processing2/hotbits/HotbitsHandler.java | 33 ++- 3 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/isuret/polos/AetherOnePi/hotbits/hrng/TrueRngProGenerator.java diff --git a/pom.xml b/pom.xml index afe1c10..1e5a792 100644 --- a/pom.xml +++ b/pom.xml @@ -192,6 +192,13 @@ 5.14.0 + + + com.fazecast + jSerialComm + 2.10.4 + + org.junit.jupiter junit-jupiter-engine diff --git a/src/main/java/de/isuret/polos/AetherOnePi/hotbits/hrng/TrueRngProGenerator.java b/src/main/java/de/isuret/polos/AetherOnePi/hotbits/hrng/TrueRngProGenerator.java new file mode 100644 index 0000000..36cdd04 --- /dev/null +++ b/src/main/java/de/isuret/polos/AetherOnePi/hotbits/hrng/TrueRngProGenerator.java @@ -0,0 +1,273 @@ +package de.isuret.polos.AetherOnePi.hotbits.hrng; + +import com.fazecast.jSerialComm.SerialPort; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.isuret.polos.AetherOnePi.hotbits.HotBitIntegers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +/** + * TrueRNG Pro v2.0 (ubld.it) hardware TRNG integration. + * + * The TrueRNG Pro is a USB CDC serial device that streams whitened random bytes + * when the port is opened and DTR is asserted. No special protocol is needed - + * just read bytes from the serial port. + * + * USB VID:PID = 04D8:EBB5 (Microchip Technology / TrueRNGpro V2) + * + * See: https://ubld.it/products/truerngprov2 + */ +public class TrueRngProGenerator { + + private static final Logger logger = LoggerFactory.getLogger(TrueRngProGenerator.class); + + // USB identification for TrueRNG Pro V2 + private static final int VENDOR_ID = 0x04D8; + private static final int PRODUCT_ID = 0xEBB5; + + // Also match TrueRNG V1/V2/V3 (VID:04D8 PID:F5FE) + private static final int TRUERNG_PRODUCT_ID = 0xF5FE; + + // Read buffer size + private static final int READ_BUFFER_SIZE = 4096; + + private SerialPort serialPort; + private volatile boolean running = false; + + /** + * Check if a TrueRNG Pro device is connected by scanning serial ports. + */ + public static boolean isDevicePresent() { + return findDevice() != null; + } + + /** + * Find the TrueRNG Pro serial port by scanning all ports for matching VID/PID + * or description containing "TrueRNG". + */ + private static SerialPort findDevice() { + try { + for (SerialPort port : SerialPort.getCommPorts()) { + int vid = port.getVendorID(); + int pid = port.getProductID(); + + // Match by VID/PID + if (vid == VENDOR_ID && (pid == PRODUCT_ID || pid == TRUERNG_PRODUCT_ID)) { + return port; + } + + // Fallback: match by description + String desc = port.getDescriptivePortName().toLowerCase(); + if (desc.contains("truerng")) { + return port; + } + } + } catch (Exception e) { + logger.debug("Error scanning for TrueRNG Pro: {}", e.getMessage()); + } + return null; + } + + /** + * Initialize the TrueRNG Pro device. + */ + public boolean initialize() { + serialPort = findDevice(); + if (serialPort == null) { + logger.info("No TrueRNG Pro device found"); + return false; + } + + logger.info("TrueRNG Pro found on port: {} ({})", + serialPort.getSystemPortName(), serialPort.getDescriptivePortName()); + + // Configure serial port + serialPort.setBaudRate(300); + serialPort.setNumDataBits(8); + serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT); + serialPort.setParity(SerialPort.NO_PARITY); + serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED); + serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 5000, 0); + + if (!serialPort.openPort()) { + logger.error("Failed to open TrueRNG Pro serial port: {}", serialPort.getSystemPortName()); + serialPort = null; + return false; + } + + // Assert DTR to start random data streaming + serialPort.setDTR(); + + // Flush any stale data + try { + Thread.sleep(100); + } catch (InterruptedException ignored) {} + byte[] flush = new byte[READ_BUFFER_SIZE]; + serialPort.readBytes(flush, flush.length); + + // Verify we can read data + byte[] test = new byte[64]; + int bytesRead = serialPort.readBytes(test, test.length); + if (bytesRead <= 0) { + logger.error("TrueRNG Pro: no data received from device"); + close(); + return false; + } + + logger.info("TrueRNG Pro v2.0 initialized successfully - {} bytes test read OK", bytesRead); + return true; + } + + /** + * Read random bytes directly from the TrueRNG Pro. + */ + public byte[] generateRandomBytes(int numBytes) { + if (serialPort == null || !serialPort.isOpen()) { + return null; + } + + byte[] result = new byte[numBytes]; + int offset = 0; + + while (offset < numBytes) { + int toRead = Math.min(READ_BUFFER_SIZE, numBytes - offset); + byte[] buf = new byte[toRead]; + int bytesRead = serialPort.readBytes(buf, toRead); + + if (bytesRead <= 0) { + logger.error("TrueRNG Pro: USB read failed"); + return null; + } + + System.arraycopy(buf, 0, result, offset, bytesRead); + offset += bytesRead; + } + + return result; + } + + /** + * Generate random integers and save them as a hotbits JSON file. + */ + public File generateHotbitsFile(String targetFolder, int integersPerPackage) { + List integerList = new ArrayList<>(); + + // Read all bytes at once for efficiency (4 bytes per integer) + byte[] randomBytes = generateRandomBytes(integersPerPackage * 4); + if (randomBytes == null) { + logger.error("Failed to generate random bytes for hotbits file"); + return null; + } + + for (int i = 0; i < integersPerPackage; i++) { + int offset = i * 4; + int value = ((randomBytes[offset] & 0xFF) << 24) | + ((randomBytes[offset + 1] & 0xFF) << 16) | + ((randomBytes[offset + 2] & 0xFF) << 8) | + (randomBytes[offset + 3] & 0xFF); + integerList.add(value); + } + + HotBitIntegers hotBits = new HotBitIntegers(); + hotBits.setIntegerList(integerList); + + File folder = new File(targetFolder); + if (!folder.exists()) { + folder.mkdirs(); + } + + File file = new File(targetFolder + "/hotbits_" + Calendar.getInstance().getTimeInMillis() + ".json"); + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(file, hotBits); + return file; + } catch (Exception e) { + logger.error("Failed to write hotbits file: {}", e.getMessage()); + return null; + } + } + + /** + * Start background thread that continuously generates hotbits files. + */ + public void startContinuousGeneration(String targetFolder, int maxFiles, int integersPerPackage) { + if (running) { + logger.warn("TrueRNG Pro continuous generation is already running"); + return; + } + + running = true; + + Thread generatorThread = new Thread(() -> { + logger.info("TrueRNG Pro: starting continuous hotbits generation into '{}'", targetFolder); + + while (running) { + File folder = new File(targetFolder); + if (!folder.exists()) { + folder.mkdirs(); + } + + int currentFiles = 0; + File[] files = folder.listFiles(); + if (files != null) { + currentFiles = files.length; + } + + if (currentFiles < maxFiles) { + File generated = generateHotbitsFile(targetFolder, integersPerPackage); + if (generated != null) { + logger.debug("Generated hotbits file: {}", generated.getName()); + } else { + logger.warn("Failed to generate hotbits file, pausing..."); + sleep(5000); + } + } else { + sleep(2000); + } + } + + logger.info("TrueRNG Pro: continuous generation stopped"); + }, "TrueRngPro-Generator"); + + generatorThread.setDaemon(true); + generatorThread.start(); + } + + public void stop() { + running = false; + } + + public void close() { + running = false; + if (serialPort != null && serialPort.isOpen()) { + try { + serialPort.clearDTR(); + serialPort.closePort(); + } catch (Exception e) { + logger.error("Error closing TrueRNG Pro: {}", e.getMessage()); + } + serialPort = null; + } + } + + public boolean isRunning() { + return running; + } + + public boolean isDeviceOpen() { + return serialPort != null && serialPort.isOpen(); + } + + private void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java b/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java index d7bbeca..9998abf 100644 --- a/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java +++ b/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java @@ -4,6 +4,7 @@ import de.isuret.polos.AetherOnePi.hotbits.HotBitIntegers; import de.isuret.polos.AetherOnePi.hotbits.IHotbitsClient; import de.isuret.polos.AetherOnePi.hotbits.hrng.InfiniteNoiseGenerator; +import de.isuret.polos.AetherOnePi.hotbits.hrng.TrueRngProGenerator; import de.isuret.polos.AetherOnePi.processing2.AetherOneUI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +30,7 @@ public class HotbitsHandler implements IHotbitsClient { private boolean simulation = false; private File hotbitsFolder = new File("hotbits"); private InfiniteNoiseGenerator infiniteNoiseGenerator; + private TrueRngProGenerator trueRngProGenerator; public HotbitsHandler(AetherOneUI p) { this.p = p; @@ -38,8 +40,11 @@ public HotbitsHandler(AetherOneUI p) { hotbitsFolder.mkdir(); } - // Try to initialize Infinite Noise TRNG if connected + // Try to initialize hardware TRNGs (first one found wins) initInfiniteNoiseTrng(); + if (infiniteNoiseGenerator == null) { + initTrueRngPro(); + } } private void initInfiniteNoiseTrng() { @@ -63,6 +68,27 @@ private void initInfiniteNoiseTrng() { } } + private void initTrueRngPro() { + try { + if (TrueRngProGenerator.isDevicePresent()) { + logger.info("TrueRNG Pro detected! Initializing..."); + trueRngProGenerator = new TrueRngProGenerator(); + if (trueRngProGenerator.initialize()) { + logger.info("TrueRNG Pro initialized successfully - using hardware TRNG for hotbits"); + trueRngProGenerator.startContinuousGeneration("hotbits", 2000, 10000); + } else { + logger.warn("TrueRNG Pro detected but initialization failed - falling back to default hotbits source"); + trueRngProGenerator = null; + } + } else { + logger.info("No TrueRNG Pro detected - using default hotbits source"); + } + } catch (Exception e) { + logger.info("TrueRNG Pro not available: {} - using default hotbits source", e.getMessage()); + trueRngProGenerator = null; + } + } + public synchronized void loadHotbits() { (new Thread() { @@ -182,12 +208,15 @@ public int getInteger(int bound) { } public boolean isTrngConnected() { - return infiniteNoiseGenerator != null && infiniteNoiseGenerator.isRunning(); + return (infiniteNoiseGenerator != null && infiniteNoiseGenerator.isRunning()) + || (trueRngProGenerator != null && trueRngProGenerator.isRunning()); } public String getTrngSourceName() { if (infiniteNoiseGenerator != null && infiniteNoiseGenerator.isRunning()) { return "INFINITE NOISE TRNG"; + } else if (trueRngProGenerator != null && trueRngProGenerator.isRunning()) { + return "TRUERNG PRO"; } else if (hotbits.size() > 0) { return "CACHED HOTBITS"; } else { From 4abc19b55ab34fd701a3528a6a261e8d171ac1f8 Mon Sep 17 00:00:00 2001 From: davorradic Date: Sat, 21 Mar 2026 00:52:35 +0100 Subject: [PATCH 3/3] Update TRNG display name to "TrueRNGPro v2.0" in GUI status Co-Authored-By: Claude Opus 4.6 (1M context) --- .../polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java b/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java index 9998abf..611e851 100644 --- a/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java +++ b/src/main/java/de/isuret/polos/AetherOnePi/processing2/hotbits/HotbitsHandler.java @@ -216,7 +216,7 @@ public String getTrngSourceName() { if (infiniteNoiseGenerator != null && infiniteNoiseGenerator.isRunning()) { return "INFINITE NOISE TRNG"; } else if (trueRngProGenerator != null && trueRngProGenerator.isRunning()) { - return "TRUERNG PRO"; + return "TrueRNGPro v2.0"; } else if (hotbits.size() > 0) { return "CACHED HOTBITS"; } else {