@mindpeeker/entropy 0.1.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.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +496 -0
  3. package/dist/errors.d.ts +14 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/errors.js +13 -0
  6. package/dist/errors.js.map +1 -0
  7. package/dist/index.d.ts +13 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +10 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/internal/bits.d.ts +9 -0
  12. package/dist/internal/bits.d.ts.map +1 -0
  13. package/dist/internal/bits.js +29 -0
  14. package/dist/internal/bits.js.map +1 -0
  15. package/dist/internal/byte-source.d.ts +16 -0
  16. package/dist/internal/byte-source.d.ts.map +1 -0
  17. package/dist/internal/byte-source.js +96 -0
  18. package/dist/internal/byte-source.js.map +1 -0
  19. package/dist/internal/bytes.d.ts +5 -0
  20. package/dist/internal/bytes.d.ts.map +1 -0
  21. package/dist/internal/bytes.js +56 -0
  22. package/dist/internal/bytes.js.map +1 -0
  23. package/dist/internal/composite.d.ts +15 -0
  24. package/dist/internal/composite.d.ts.map +1 -0
  25. package/dist/internal/composite.js +36 -0
  26. package/dist/internal/composite.js.map +1 -0
  27. package/dist/internal/condition.d.ts +38 -0
  28. package/dist/internal/condition.d.ts.map +1 -0
  29. package/dist/internal/condition.js +49 -0
  30. package/dist/internal/condition.js.map +1 -0
  31. package/dist/internal/encoding.d.ts +14 -0
  32. package/dist/internal/encoding.d.ts.map +1 -0
  33. package/dist/internal/encoding.js +91 -0
  34. package/dist/internal/encoding.js.map +1 -0
  35. package/dist/internal/health.d.ts +25 -0
  36. package/dist/internal/health.d.ts.map +1 -0
  37. package/dist/internal/health.js +84 -0
  38. package/dist/internal/health.js.map +1 -0
  39. package/dist/internal/http.d.ts +25 -0
  40. package/dist/internal/http.d.ts.map +1 -0
  41. package/dist/internal/http.js +76 -0
  42. package/dist/internal/http.js.map +1 -0
  43. package/dist/internal/nist-pulse.d.ts +31 -0
  44. package/dist/internal/nist-pulse.d.ts.map +1 -0
  45. package/dist/internal/nist-pulse.js +62 -0
  46. package/dist/internal/nist-pulse.js.map +1 -0
  47. package/dist/internal/provider.d.ts +19 -0
  48. package/dist/internal/provider.d.ts.map +1 -0
  49. package/dist/internal/provider.js +62 -0
  50. package/dist/internal/provider.js.map +1 -0
  51. package/dist/internal/rate-limit.d.ts +15 -0
  52. package/dist/internal/rate-limit.d.ts.map +1 -0
  53. package/dist/internal/rate-limit.js +47 -0
  54. package/dist/internal/rate-limit.js.map +1 -0
  55. package/dist/internal/sampled.d.ts +25 -0
  56. package/dist/internal/sampled.d.ts.map +1 -0
  57. package/dist/internal/sampled.js +49 -0
  58. package/dist/internal/sampled.js.map +1 -0
  59. package/dist/internal/stream.d.ts +29 -0
  60. package/dist/internal/stream.d.ts.map +1 -0
  61. package/dist/internal/stream.js +78 -0
  62. package/dist/internal/stream.js.map +1 -0
  63. package/dist/internal/validate.d.ts +5 -0
  64. package/dist/internal/validate.d.ts.map +1 -0
  65. package/dist/internal/validate.js +39 -0
  66. package/dist/internal/validate.js.map +1 -0
  67. package/dist/node/ffmpeg-frames.d.ts +27 -0
  68. package/dist/node/ffmpeg-frames.d.ts.map +1 -0
  69. package/dist/node/ffmpeg-frames.js +102 -0
  70. package/dist/node/ffmpeg-frames.js.map +1 -0
  71. package/dist/node/ffmpeg-samples.d.ts +21 -0
  72. package/dist/node/ffmpeg-samples.d.ts.map +1 -0
  73. package/dist/node/ffmpeg-samples.js +81 -0
  74. package/dist/node/ffmpeg-samples.js.map +1 -0
  75. package/dist/node/hwrng.d.ts +12 -0
  76. package/dist/node/hwrng.d.ts.map +1 -0
  77. package/dist/node/hwrng.js +43 -0
  78. package/dist/node/hwrng.js.map +1 -0
  79. package/dist/node/index.d.ts +9 -0
  80. package/dist/node/index.d.ts.map +1 -0
  81. package/dist/node/index.js +7 -0
  82. package/dist/node/index.js.map +1 -0
  83. package/dist/node/rtl-sdr.d.ts +31 -0
  84. package/dist/node/rtl-sdr.d.ts.map +1 -0
  85. package/dist/node/rtl-sdr.js +63 -0
  86. package/dist/node/rtl-sdr.js.map +1 -0
  87. package/dist/node/serial-source.d.ts +22 -0
  88. package/dist/node/serial-source.d.ts.map +1 -0
  89. package/dist/node/serial-source.js +63 -0
  90. package/dist/node/serial-source.js.map +1 -0
  91. package/dist/providers/anu-legacy.d.ts +17 -0
  92. package/dist/providers/anu-legacy.d.ts.map +1 -0
  93. package/dist/providers/anu-legacy.js +56 -0
  94. package/dist/providers/anu-legacy.js.map +1 -0
  95. package/dist/providers/anu.d.ts +14 -0
  96. package/dist/providers/anu.d.ts.map +1 -0
  97. package/dist/providers/anu.js +42 -0
  98. package/dist/providers/anu.js.map +1 -0
  99. package/dist/providers/bitcoin.d.ts +14 -0
  100. package/dist/providers/bitcoin.d.ts.map +1 -0
  101. package/dist/providers/bitcoin.js +95 -0
  102. package/dist/providers/bitcoin.js.map +1 -0
  103. package/dist/providers/camera.d.ts +49 -0
  104. package/dist/providers/camera.d.ts.map +1 -0
  105. package/dist/providers/camera.js +123 -0
  106. package/dist/providers/camera.js.map +1 -0
  107. package/dist/providers/crypto.d.ts +8 -0
  108. package/dist/providers/crypto.d.ts.map +1 -0
  109. package/dist/providers/crypto.js +26 -0
  110. package/dist/providers/crypto.js.map +1 -0
  111. package/dist/providers/curby.d.ts +17 -0
  112. package/dist/providers/curby.d.ts.map +1 -0
  113. package/dist/providers/curby.js +113 -0
  114. package/dist/providers/curby.js.map +1 -0
  115. package/dist/providers/drand.d.ts +19 -0
  116. package/dist/providers/drand.d.ts.map +1 -0
  117. package/dist/providers/drand.js +87 -0
  118. package/dist/providers/drand.js.map +1 -0
  119. package/dist/providers/flow.d.ts +15 -0
  120. package/dist/providers/flow.d.ts.map +1 -0
  121. package/dist/providers/flow.js +82 -0
  122. package/dist/providers/flow.js.map +1 -0
  123. package/dist/providers/index.d.ts +56 -0
  124. package/dist/providers/index.d.ts.map +1 -0
  125. package/dist/providers/index.js +32 -0
  126. package/dist/providers/index.js.map +1 -0
  127. package/dist/providers/inmetro.d.ts +17 -0
  128. package/dist/providers/inmetro.d.ts.map +1 -0
  129. package/dist/providers/inmetro.js +25 -0
  130. package/dist/providers/inmetro.js.map +1 -0
  131. package/dist/providers/jitter.d.ts +33 -0
  132. package/dist/providers/jitter.d.ts.map +1 -0
  133. package/dist/providers/jitter.js +81 -0
  134. package/dist/providers/jitter.js.map +1 -0
  135. package/dist/providers/lfdr.d.ts +11 -0
  136. package/dist/providers/lfdr.d.ts.map +1 -0
  137. package/dist/providers/lfdr.js +34 -0
  138. package/dist/providers/lfdr.js.map +1 -0
  139. package/dist/providers/microphone.d.ts +26 -0
  140. package/dist/providers/microphone.d.ts.map +1 -0
  141. package/dist/providers/microphone.js +120 -0
  142. package/dist/providers/microphone.js.map +1 -0
  143. package/dist/providers/nist-beacon.d.ts +11 -0
  144. package/dist/providers/nist-beacon.d.ts.map +1 -0
  145. package/dist/providers/nist-beacon.js +20 -0
  146. package/dist/providers/nist-beacon.js.map +1 -0
  147. package/dist/providers/nqsn.d.ts +11 -0
  148. package/dist/providers/nqsn.d.ts.map +1 -0
  149. package/dist/providers/nqsn.js +16 -0
  150. package/dist/providers/nqsn.js.map +1 -0
  151. package/dist/providers/outshift.d.ts +14 -0
  152. package/dist/providers/outshift.d.ts.map +1 -0
  153. package/dist/providers/outshift.js +52 -0
  154. package/dist/providers/outshift.js.map +1 -0
  155. package/dist/providers/padova.d.ts +12 -0
  156. package/dist/providers/padova.d.ts.map +1 -0
  157. package/dist/providers/padova.js +56 -0
  158. package/dist/providers/padova.js.map +1 -0
  159. package/dist/providers/qbck.d.ts +13 -0
  160. package/dist/providers/qbck.d.ts.map +1 -0
  161. package/dist/providers/qbck.js +62 -0
  162. package/dist/providers/qbck.js.map +1 -0
  163. package/dist/providers/qci.d.ts +14 -0
  164. package/dist/providers/qci.d.ts.map +1 -0
  165. package/dist/providers/qci.js +91 -0
  166. package/dist/providers/qci.js.map +1 -0
  167. package/dist/providers/qrandom.d.ts +12 -0
  168. package/dist/providers/qrandom.d.ts.map +1 -0
  169. package/dist/providers/qrandom.js +39 -0
  170. package/dist/providers/qrandom.js.map +1 -0
  171. package/dist/providers/randao.d.ts +14 -0
  172. package/dist/providers/randao.d.ts.map +1 -0
  173. package/dist/providers/randao.js +81 -0
  174. package/dist/providers/randao.js.map +1 -0
  175. package/dist/providers/random-org.d.ts +13 -0
  176. package/dist/providers/random-org.d.ts.map +1 -0
  177. package/dist/providers/random-org.js +89 -0
  178. package/dist/providers/random-org.js.map +1 -0
  179. package/dist/providers/sdr.d.ts +21 -0
  180. package/dist/providers/sdr.d.ts.map +1 -0
  181. package/dist/providers/sdr.js +63 -0
  182. package/dist/providers/sdr.js.map +1 -0
  183. package/dist/providers/sensor.d.ts +24 -0
  184. package/dist/providers/sensor.d.ts.map +1 -0
  185. package/dist/providers/sensor.js +119 -0
  186. package/dist/providers/sensor.js.map +1 -0
  187. package/dist/providers/serial.d.ts +35 -0
  188. package/dist/providers/serial.d.ts.map +1 -0
  189. package/dist/providers/serial.js +78 -0
  190. package/dist/providers/serial.js.map +1 -0
  191. package/dist/providers/solana.d.ts +13 -0
  192. package/dist/providers/solana.d.ts.map +1 -0
  193. package/dist/providers/solana.js +83 -0
  194. package/dist/providers/solana.js.map +1 -0
  195. package/dist/providers/superrand.d.ts +35 -0
  196. package/dist/providers/superrand.d.ts.map +1 -0
  197. package/dist/providers/superrand.js +215 -0
  198. package/dist/providers/superrand.js.map +1 -0
  199. package/dist/providers/tezos.d.ts +13 -0
  200. package/dist/providers/tezos.d.ts.map +1 -0
  201. package/dist/providers/tezos.js +92 -0
  202. package/dist/providers/tezos.js.map +1 -0
  203. package/dist/providers/uchile.d.ts +11 -0
  204. package/dist/providers/uchile.d.ts.map +1 -0
  205. package/dist/providers/uchile.js +22 -0
  206. package/dist/providers/uchile.js.map +1 -0
  207. package/dist/strategies/fallback.d.ts +12 -0
  208. package/dist/strategies/fallback.d.ts.map +1 -0
  209. package/dist/strategies/fallback.js +45 -0
  210. package/dist/strategies/fallback.js.map +1 -0
  211. package/dist/strategies/race.d.ts +7 -0
  212. package/dist/strategies/race.d.ts.map +1 -0
  213. package/dist/strategies/race.js +41 -0
  214. package/dist/strategies/race.js.map +1 -0
  215. package/dist/strategies/xor.d.ts +9 -0
  216. package/dist/strategies/xor.d.ts.map +1 -0
  217. package/dist/strategies/xor.js +56 -0
  218. package/dist/strategies/xor.js.map +1 -0
  219. package/dist/types.d.ts +36 -0
  220. package/dist/types.d.ts.map +1 -0
  221. package/dist/types.js +2 -0
  222. package/dist/types.js.map +1 -0
  223. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mindpeeker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,496 @@
1
+ # @mindpeeker/entropy
2
+
3
+ Provider-pluggable **quantum (QRNG)** and **true (TRNG)** randomness for web and Node.js.
4
+
5
+ Eleven entropy backends behind one tiny interface, with composable combining strategies
6
+ (fallback chains, XOR mixing, racing), request/response *and* streaming access, and honest
7
+ source attribution — every result tells you where its bytes actually came from.
8
+
9
+ - **Runtime-agnostic**: standard `fetch`, `WebSocket`, `globalThis.crypto` only. Zero dependencies. ESM. Node ≥ 20.3 and evergreen browsers.
10
+ - **Exactly-n-or-throw**: `getBytes(n)` always resolves with exactly `n` bytes or throws a typed `EntropyError` — per-request caps and chunking are each provider's internal concern.
11
+ - **Honest metadata**: providers are classified by `kind` (`qrng` / `trng` / `beacon` / `csprng`) and `privacy` (`private` / `public`) so you can't accidentally seed a secret from a public beacon.
12
+
13
+ ```sh
14
+ npm install @mindpeeker/entropy # or bun add @mindpeeker/entropy
15
+ ```
16
+
17
+ Three entry points:
18
+
19
+ | Import | Contents |
20
+ |---|---|
21
+ | `@mindpeeker/entropy` | the core: types, `EntropyError`, strategies (`fallback`/`xorMix`/`race`), `defineProvider` |
22
+ | `@mindpeeker/entropy/providers` | **all providers** (cloud, beacons, local sensors/hardware) |
23
+ | `@mindpeeker/entropy/node` | Node-only adapters (`nodeSerialSource`, ffmpeg, `hwRng`, `rtlSdrSource`) — browser bundlers cannot resolve this on purpose |
24
+
25
+ ## Quick start
26
+
27
+ ```ts
28
+ import { fallback } from '@mindpeeker/entropy'
29
+ import { anuLegacy, cryptoProvider } from '@mindpeeker/entropy/providers'
30
+
31
+ // The classic: try quantum first, fall back to the local CSPRNG — and know which one served.
32
+ const entropy = fallback([anuLegacy(), cryptoProvider()])
33
+
34
+ const { bytes, sources } = await entropy.getBytes(32)
35
+ console.log(sources.map((s) => s.name)) // ['anu-legacy'] … or ['crypto'] if ANU was down
36
+ ```
37
+
38
+ ## Providers
39
+
40
+ | Factory | Source | Kind | Privacy | Auth | Notes |
41
+ |---|---|---|---|---|---|
42
+ | `cryptoProvider()` | runtime CSPRNG | csprng | private | — | always available; last link of every good fallback chain |
43
+ | `anu({ apiKey })` | ANU quantum vacuum | qrng | private | `x-api-key` (AWS Marketplace) | ≤1024 numbers/request (chunked automatically) |
44
+ | `anuLegacy()` | ANU quantum vacuum | qrng | private | none | **1 request/minute** (client-side gate built in); retirement announced |
45
+ | `qrandomIo()` | IDQ Quantis photonics | qrng | private | none | free; Falcon-512-signed responses (not verified in v1) |
46
+ | `lfdr()` | IDQ Quantis PCIe | qrng | private | none | hobby-grade lab service, no SLA |
47
+ | `outshift({ apiKey })` | Cisco photonic QRNG | qrng | private | `x-id-api-key` (free signup) | 100k bits/day free tier |
48
+ | `qci({ apiToken })` | QCi photonic uQRNG | qrng | private | OAuth2 token exchange | bearer token cached; auto re-auth on 401 |
49
+ | `randomOrg({ apiKey })` | atmospheric radio noise | trng | private | JSON-RPC apiKey | honors `advisoryDelay`; 250k bits/day free |
50
+ | `superRand({ apiKey })` | EM background noise | trng | private | key in query | **WebSocket streaming**; ≤256 values/request; wire format live-verified |
51
+ | `drand()` | League of Entropy (threshold BLS) | beacon | **public** | none | 3 s rounds, 32 B each; mirror failover built in |
52
+ | `nistBeacon()` | NIST full-entropy source | beacon | **public** | none | 512 bits/min; NIST: *never use as secret keys* |
53
+ | `nqsn()` | NQSN Singapore quantum beacon | beacon | **public** | none | NIST-2.0 format, 60 s pulses |
54
+ | `uchile()` | Random UChile hybrid beacon | beacon | **public** | none | quantum device + seismic/radio mixers, 60 s |
55
+ | `inmetro()` | Inmetro Brazil beacon | beacon | **public** | none | `variant: 'combination'` = 10-min multi-beacon VDF mix |
56
+ | `curby()` | CURBy (CU Boulder + NIST) | beacon | **public** | none | Twine chain, freshness guard built in |
57
+ | `padova()` | Univ. of Padova QRNG (VSIX) | qrng | private | none | keyless PRIVATE quantum draws — the simplest QRNG API alive |
58
+ | `qbck({apiKey})` | Quantum Blockchains aggregator (IDQ/qStream/SeQRNG/Tropos) | qrng | private | email-registration key | response parse marked VERIFY-WITH-KEY |
59
+ | `randao()` | Ethereum RANDAO | beacon | **public** | none | 12 s slots; proposer-biasable ~1 bit — see caveats |
60
+ | `bitcoinBeacon()` | Bitcoin block hashes | beacon | **public** | none | ~10 min cadence; miner-grindable at high cost |
61
+ | `solanaBeacon()` | Solana blockhashes | beacon | **public** | none | ~400 ms slots; leader-influenced |
62
+ | `tezosBeacon()` | Tezos block hashes | beacon | **public** | none | via the TzKT indexer (extra third-party trust) |
63
+ | `flowBeacon()` | Flow protocol randomness | beacon | **public** | none | 8 B per script execution |
64
+ | `cameraEntropy()` | camera sensor noise (shot/thermal) | trng | private | camera permission | frame-diff sign bits (AetherOnePi-style); browser or injected frames |
65
+ | `micEntropy()` | microphone ADC noise | trng | private | mic permission | sample LSBs; browser or injected PCM |
66
+ | `serialEntropy({...})` | ESP32 / TrueRNG / OneRNG over serial | trng | private | — | Web Serial `port` or any injected byte `source` |
67
+ | `sensorEntropy()` | phone/tablet motion sensors | trng | private | motion permission | quantized readings → tiny credit; breadth source |
68
+ | `sdrEntropy({source})` | RTL-SDR radio noise | trng | private | — | community-verified tier; `rtlSdrSource` from `/node` |
69
+ | `jitterEntropy()` | CPU timing jitter | trng | private | — | Node `hrtime` (credited 1/16 bit/delta); browser only via `allowCoarseClock` |
70
+
71
+ All network providers accept `fetch` (dependency injection / proxying) and a base-URL override.
72
+
73
+ ## Randomness classes — what you're actually getting
74
+
75
+ "Random" hides four very different claims. Every provider's class, physical process and the
76
+ actual basis of its unpredictability:
77
+
78
+ | Source | Class | Physical process | Why it's unpredictable |
79
+ |---|---|---|---|
80
+ | `cryptoProvider` | **CSPRNG** (deterministic) | none — an algorithm seeded from OS entropy | computational hardness only; predictable to anyone holding the state |
81
+ | `anu`, `anuLegacy` | **Quantum** (QRNG) | vacuum fluctuations of the electromagnetic field | quantum measurement — non-deterministic by the laws of physics |
82
+ | `qrandomIo`, `lfdr` | **Quantum** (QRNG) | photon detection (ID Quantique Quantis) | quantum shot noise |
83
+ | `outshift`, `qci`, `padova` | **Quantum** (QRNG) | photonic hardware (Cisco / QCi / Univ. Padova) | quantum optics |
84
+ | `qbck` | **Quantum** (QRNG) | aggregated IDQ/qStream/SeQRNG/Tropos hardware | quantum optics, multi-vendor |
85
+ | `cameraEntropy` | **Hybrid** quantum + classical | photon shot noise (lit scene — quantum) and sensor thermal noise (covered lens — classical) | photon-arrival statistics / Johnson noise |
86
+ | `randomOrg` | **Classical TRNG** | atmospheric radio noise | chaotic macroscopic physics — deterministic in principle, unmeasurable in practice |
87
+ | `superRand` | **Classical TRNG** | electromagnetic background noise | chaotic macroscopic physics |
88
+ | `serialEntropy` (ESP32) | **Classical TRNG** | SAR-ADC thermal/RF noise (`bootloader_random`) | thermal noise |
89
+ | `micEntropy` | **Classical TRNG** | microphone ADC / Johnson noise | thermal noise |
90
+ | `sensorEntropy` | **Classical TRNG** | accelerometer/gyroscope MEMS noise | thermal/mechanical noise (heavily quantized in browsers) |
91
+ | `sdrEntropy` | **Classical TRNG** | radio front-end thermal noise (RTL-SDR) | thermal noise; RF-injectable — mixing tier |
92
+ | `hwRng` | varies | whatever the kernel's `/dev/hwrng` driver trusts | device-dependent |
93
+ | `jitterEntropy` | **Software/physical hybrid** | CPU micro-architectural timing chaos | system state too complex to model — weakest physical claim |
94
+ | `drand` | **Cryptographic beacon** | none per round — threshold BLS signatures | cryptography + distributed trust; **PUBLIC** values |
95
+ | `nistBeacon`, `nqsn`, `uchile`, `inmetro`, `curby` | **TRNG/QRNG-backed beacons** | operator-run physical sources | operator trust; **PUBLIC** values |
96
+ | `randao`, `bitcoinBeacon`, `solanaBeacon`, `tezosBeacon`, `flowBeacon` | **Chain beacons** | none — consensus artifacts | economic cost of bias; **PUBLIC** and participant-influenceable |
97
+
98
+ Rules of thumb: only the quantum sources are non-deterministic *in principle*; classical TRNGs
99
+ are practically unpredictable but not fundamentally so; a CSPRNG is pure math; beacons are not
100
+ private entropy at all — they are shared, verifiable randomness.
101
+
102
+ ### Chain-beacon caveats (read before using `randao`/`bitcoinBeacon`/…)
103
+
104
+ Blockchain values are **public and participant-influenceable**: an Ethereum proposer can bias
105
+ RANDAO by roughly one bit per slot by withholding a block (and more with forking strategies —
106
+ eprint 2025/037); Bitcoin miners can discard unfavourable blocks, though at a cost estimated in
107
+ six figures per bit (Bonneau et al., eprint 2015/1015); Solana values pass through the slot
108
+ leader, and `tezosBeacon` additionally trusts the TzKT indexer. Use them for commitments,
109
+ audits and `xorMix` transparency inputs. For well-engineered public randomness with distributed
110
+ trust, prefer `drand`.
111
+
112
+ ## Local physical entropy
113
+
114
+ The four local providers share one pipeline: raw physical samples → **continuous NIST SP 800-90B
115
+ health tests** (Repetition Count + Adaptive Proportion, always on) → **SHA-256 extraction** with a
116
+ conservative per-source entropy credit — or, with `conditioning: 'raw'`, a health-tested
117
+ passthrough of the unwhitened physical bits (the provider then reports itself as `name(raw)` in
118
+ attribution, so results are always traceable). A failing source throws
119
+ `EntropyError('health_test')` — it never silently degrades to pseudo-randomness.
120
+
121
+ ```ts
122
+ import { xorMix } from '@mindpeeker/entropy'
123
+ import { cameraEntropy, cryptoProvider, serialEntropy } from '@mindpeeker/entropy/providers'
124
+
125
+ // Browser webcam, whitened:
126
+ const cam = cameraEntropy() // getUserMedia; cover the lens for pure thermal noise
127
+
128
+ // Raw hotbits for oracle/radionics workflows — unwhitened frame-diff sign bits:
129
+ const rawCam = cameraEntropy({ conditioning: 'raw' })
130
+
131
+ // Defense in depth stays available:
132
+ const belt = xorMix([cam, cryptoProvider()])
133
+ ```
134
+
135
+ ### Serial hardware in the BROWSER (Web Serial)
136
+
137
+ The `serialEntropy` provider works in Chromium (89+, Android 148) and Firefox (151+) today —
138
+ no adapter needed, just a user gesture on an HTTPS page:
139
+
140
+ ```ts
141
+ const port = await navigator.serial.requestPort() // user picks the ESP32/TrueRNG
142
+ const hw = serialEntropy({ port, name: 'esp32' })
143
+ ```
144
+
145
+ ### More boards: the firmware collection
146
+
147
+ [`firmware/`](firmware/) ships ready-to-flash sketches speaking the same raw-serial contract:
148
+ **Raspberry Pi Pico 2** (⭐ $5, real TRNG), **STM32F405/407** (⭐ best quality — but beware:
149
+ F401/F411 "Black Pill" and F446 have NO RNG peripheral), nRF52840, Arduino+avalanche-diode
150
+ (DIY tier, parts list included) and ESP8266 (compat-only). See `firmware/README.md` for the
151
+ full comparison table.
152
+
153
+ ### Motion sensors (phones/tablets)
154
+
155
+ ```ts
156
+ import { sensorEntropy } from '@mindpeeker/entropy/providers'
157
+ const motion = sensorEntropy() // Generic Sensor API, DeviceMotion fallback (iOS asks permission)
158
+ ```
159
+
160
+ Browsers quantize readings (0.1 m/s² / 0.1 °/s), so the credited entropy is deliberately tiny
161
+ (≤1 bit per reading; a 32-byte block takes several seconds even in motion). A frozen device
162
+ trips the health tests instead of producing fake entropy. Breadth source — mix it, don't rely
163
+ on it.
164
+
165
+ ### RTL-SDR radio noise (community-verified tier)
166
+
167
+ ```ts
168
+ import { sdrEntropy } from '@mindpeeker/entropy/providers'
169
+ import { rtlSdrSource } from '@mindpeeker/entropy/node'
170
+
171
+ const radio = sdrEntropy({ source: await rtlSdrSource() }) // 70 MHz, max manual gain
172
+ ```
173
+
174
+ Follows the rtl-entropy pipeline (6 LSBs per IQ sample, von Neumann, health tests on raw).
175
+ Needs the `rtl_sdr` CLI + a ~$35–45 dongle; **RTL-SDR Blog V4 requires the rtlsdrblog driver
176
+ fork** — stock drivers silently corrupt V4 output, which the raw health tests are there to
177
+ catch. RF injection attacks on TRNGs are demonstrated in the literature: treat radio noise as
178
+ a mixing source, never a sole root of trust. This provider is community-verified (built from
179
+ documented behavior, no hardware on the author's desk).
180
+
181
+ ### ESP32 (AetherOnePi firmware)
182
+
183
+ Flash the [AetherOnePi ESP32 sketch](https://github.com/isuretpolos/AetherOnePi) (streams raw
184
+ `esp_fill_random` bytes at 921 600 baud — keep `bootloader_random_enable()` on) and read it:
185
+
186
+ ```ts
187
+ // Browser (Chromium, Web Serial):
188
+ const port = await navigator.serial.requestPort()
189
+ const esp32 = serialEntropy({ port, name: 'esp32' })
190
+
191
+ // Node (macOS/Linux, zero deps — stty + fs):
192
+ import { nodeSerialSource } from '@mindpeeker/entropy/node'
193
+ const esp32 = serialEntropy({
194
+ source: await nodeSerialSource({ path: '/dev/cu.usbserial-110' }),
195
+ name: 'esp32',
196
+ })
197
+ ```
198
+
199
+ TrueRNG v3 works the same way (plain CDC read); OneRNG needs its init command via the
200
+ `init` option (check onerng.info for your firmware).
201
+
202
+ ### Node camera & microphone
203
+
204
+ Node has no `getUserMedia`; inject frames/PCM — the built-in adapters spawn `ffmpeg`
205
+ (must be installed) with zero npm dependencies:
206
+
207
+ ```ts
208
+ import { ffmpegFrameSource, ffmpegSampleSource, hwRng } from '@mindpeeker/entropy/node'
209
+
210
+ cameraEntropy({ source: ffmpegFrameSource({ device: '0' }) }) // avfoundation index / /dev/video0
211
+ micEntropy({ source: ffmpegSampleSource({ device: ':0' }) }) // ':0' avfoundation / 'default' alsa
212
+ hwRng() // /dev/hwrng (usually root-only)
213
+ ```
214
+
215
+ ## Measured performance
216
+
217
+ Real measurements from `bun scripts/bench.ts` (Apple Silicon macOS, Bun 1.3, residential
218
+ connection, 2026-07-07 — rerun it yourself; results vary with network, hardware and light):
219
+
220
+ | Rank | Provider | Kind | Transport | Request | Latency | Effective rate | Notes |
221
+ |---|---|---|---|---|---|---|---|
222
+ | 1 | `crypto` | csprng | in-process | 1 MiB | 1 ms | ~870 MiB/s | baseline, not physical entropy |
223
+ | 2 | `esp32` (raw) | trng | USB serial 921600 | 16 KiB | 233 ms | **~69 KiB/s** | AetherOnePi firmware, near wire speed |
224
+ | 3 | `esp32` (conditioned) | trng | USB serial 921600 | 2 KiB | 65 ms | ~31 KiB/s | SHA-256, 2× credit |
225
+ | 4 | `jitter` | trng | in-process | 1 KiB | 112 ms | ~9 KiB/s | hrtime deltas, conditioned |
226
+ | 5 | `camera` (raw) | trng | ffmpeg avfoundation | 1 KiB | 1.9 s | ~540 B/s | debiased sign bits |
227
+ | 6 | `lfdr.de` | qrng | https | 64 B | 139 ms | ~460 B/s | free, keyless |
228
+ | 7 | `random.org` | trng | https | 64 B | 196 ms | ~330 B/s | 250k bits/day |
229
+ | 8 | `qrandom.io` | qrng | https | 64 B | 232 ms | ~280 B/s | free, keyless |
230
+ | 9 | `outshift` | qrng | https | 64 B | 475 ms | ~135 B/s | 100k bits/day |
231
+ | 10 | `nist-beacon` | beacon | https | 64 B | 677 ms | ~95 B/s | PUBLIC bits |
232
+ | 11 | `drand` | beacon | https | 64 B | 890 ms | ~72 B/s | PUBLIC bits, 2 rounds |
233
+ | 12 | `anu` (keyed) | qrng | https | 64 B | 980 ms | ~65 B/s | quantum vacuum |
234
+ | 13 | `superrand` (REST) | trng | https | 64 B | 1.3 s | ~51 B/s | ≤256 values/request |
235
+ | 14 | `camera` (conditioned) | trng | ffmpeg | 64 B | 3.6 s | ~18 B/s | warmup + 8× credit dominate |
236
+ | 15 | `anu-legacy` | qrng | https | 16 B | 1.1 s | ~14 B/s | hard 1 req/min limit |
237
+
238
+ Reading the numbers:
239
+
240
+ - **Cloud rates are latency-bound**, not throughput limits — a 64-byte request costs one round
241
+ trip, so bigger requests amortize much better (e.g. `random.org` serves up to 128 KiB per call).
242
+ - **The ESP32 is by far the fastest physical source** — orders of magnitude ahead of any cloud
243
+ QRNG — which is exactly why local hardware is worth the USB cable.
244
+ - **Camera small reads pay fixed costs** (auto-exposure warmup + the deliberately paranoid 8×
245
+ extraction credit). Streaming or raw mode is where it shines; covered-lens thermal mode works too.
246
+ - For **bulk + strongest-source guarantees**, mix fast local with a cloud QRNG:
247
+ `xorMix([serialEntropy({...}), anu({...})])` costs one round trip regardless of size.
248
+
249
+ ## Entropy quality (measured)
250
+
251
+ `bun scripts/quality.ts` collects RAW output from each local source and runs Shannon entropy,
252
+ NIST SP 800-90B estimators (most-common-value, binary Markov), chi-square, serial correlation,
253
+ monobit/runs, Monte-Carlo π and gzip compressibility. **Statistical tests can only fail a
254
+ source, never certify one** — whitened output (all cloud providers, anything conditioned)
255
+ passes everything by construction, so the honest subjects are the raw local sources:
256
+
257
+ | Source | Sample | Shannon (b/B) | 90B MCV (b/B) | 90B Markov (b/bit) | χ² p | Serial corr | Runs z | gzip |
258
+ |---|---|---|---|---|---|---|---|---|
259
+ | `crypto` (baseline) | 1 MiB | 8.000 | 7.88 | 0.999 | 0.43 | 0.0013 | −1.6 | 1.000 |
260
+ | `microphone` raw | 32 KiB | 7.994 | 7.40 | 0.996 | 0.57 | 0.0110 | 1.3 | 1.001 |
261
+ | `esp32` raw | 1 MiB | 7.880 | 7.06 | 0.932 | 0.00 | −0.0805 | 143.7 | 0.992 |
262
+ | `camera` raw | 64 KiB | 7.969 | 7.02 | 0.896 | 0.00 | −0.0003 | 54.7 | 1.001 |
263
+ | `jitter` raw | 512 KiB | 2.211 | 1.29 | 0.432 | 0.00 | 0.2196 | 1024 | 0.100 |
264
+
265
+ What the numbers say (and why the credited H values hold up):
266
+
267
+ - **`esp32` raw measures 7.06 b/B against a credited 7 b/B** — almost exactly on target. Note
268
+ it is *not* perfectly white (visible serial correlation and run structure), which is precisely
269
+ why the library still conditions it by default instead of trusting `esp_fill_random` blindly.
270
+ - **`camera` raw (post-von-Neumann) measures 7.02 b/B against a credited 1 b/B** — a 7× safety
271
+ margin. The χ²/runs failures show real residual structure; the 8× extraction credit absorbs it.
272
+ - **`microphone` raw measures 7.40 b/B against a credited 2 b/B** — the MacBook mic's ADC noise
273
+ LSBs are surprisingly clean; the 4× credit leaves a comfortable margin for worse microphones.
274
+ - **`jitter` raw is heavily structured** (Shannon 2.2, gzip-compressible to 10%!) — validating
275
+ the ultra-conservative 1/16 b credit (measured MCV 1.29 → 20× margin).
276
+ - `crypto` aces everything, as any CSPRNG must — which is exactly why passing proves nothing.
277
+
278
+ ### Whitened sources (sanity check, not a ranking)
279
+
280
+ The same battery over the cloud providers, with quota-polite sample sizes. All of these are
281
+ whitened server-side, so differences here are **sample-size artifacts, not quality differences**
282
+ — the 90B MCV estimator subtracts a confidence penalty that shrinks with √N, which is why
283
+ smaller samples score lower. The value of this table is catching a *broken* source (none were):
284
+
285
+ | Source | Sample | Shannon (b/B) | 90B MCV (b/B) | χ² p | Serial corr | Quota spent |
286
+ |---|---|---|---|---|---|---|
287
+ | `qrandom.io` | 32 KiB | 7.994 | 7.44 | 0.40 | 0.0037 | fair-use |
288
+ | `lfdr.de` | 32 KiB | 7.994 | 7.44 | 0.26 | −0.0072 | fair-use |
289
+ | `random.org` | 16 KiB | 7.989 | 7.22 | 0.43 | 0.0025 | ~52% of daily bits |
290
+ | `superrand` | 8 KiB | 7.972 | 6.91 | 0.01 | −0.0142 | ~1.6% of free allowance |
291
+ | `drand` | 8 KiB | 7.980 | 6.91 | 0.89 | 0.0026 | free (256 rounds) |
292
+ | `anu` | 8 KiB | 7.976 | 6.88 | 0.18 | 0.0125 | 8 requests |
293
+ | `nist-beacon` | 4 KiB | 7.959 | 6.67 | 0.80 | 0.0006 | free (64 pulses) |
294
+ | `outshift` | 4 KiB | 7.954 | 6.54 | 0.46 | 0.0010 | ~33% of daily bits |
295
+
296
+ (`anu-legacy` is skipped by design: at 1 request/minute a meaningful sample would take hours,
297
+ and the keyed `anu` endpoint reads the same physical source.)
298
+
299
+ ### In plain English — what do these numbers mean?
300
+
301
+ Entropy quality = **how hard it is to guess the next byte**. A perfect source gives 8 bits of
302
+ surprise per byte; a broken source repeats itself. What each measure tells you:
303
+
304
+ | Measure | Good | Bad | In simple words |
305
+ |---|---|---|---|
306
+ | Shannon (b/B) | close to 8 | low | how much *surprise* each byte carries on average |
307
+ | 90B MCV (b/B) | close to 8 | low | how hard the *best possible guesser* finds the next byte — the strictest measure |
308
+ | 90B Markov (b/bit) | close to 1 | low | does the next bit depend on the previous one? low = it has "memory" |
309
+ | χ² p | 0.01 – 0.99 | ≈ 0 | are all 256 byte values used equally often? p ≈ 0 = suspiciously uneven |
310
+ | Serial corr | ≈ 0 | far from 0 | does one byte predict the next? |
311
+ | Runs z | between −3 and 3 | large | are there too many (or too few) streaks of same bits? |
312
+ | gzip | ≈ 1.0 | low | randomness cannot be compressed — if zip makes it smaller, it has patterns |
313
+
314
+ **Simple quality ranking of your real (raw) sources:**
315
+
316
+ | Rank | Source | Grade | In simple words |
317
+ |---|---|---|---|
318
+ | 🥇 | `esp32` raw | **excellent** | almost perfect randomness at high speed — the best real source you own. Tiny patterns exist, so we still clean it up by default |
319
+ | 🥇 | `camera` raw | **excellent** | real physical noise, ~7 of 8 bits are genuinely unguessable — and we only "count" 1 of them, a 7× safety margin |
320
+ | 🥈 | `microphone` raw | **excellent** | surprisingly clean noise from the mic; also runs with a big safety margin |
321
+ | 🥉 | `jitter` raw | **weak** | full of patterns (you can zip it to 10%!) — only safe because we squeeze 32 raw bytes into every 1 output byte |
322
+ | — | `crypto` | perfect but *fake* | flawless numbers, but from math, not physics — a very good magician, not nature |
323
+ | — | all cloud sources | pass | cleaned up (whitened) before they reach you, so tests cannot tell them apart — choose them by *trust and physics class*, not by these numbers |
324
+
325
+ Machine-readable version of all results: [`docs/quality.json`](docs/quality.json)
326
+ (regenerated on every `bun run quality`).
327
+
328
+ ### Noise bitmaps
329
+
330
+ Raw bytes rendered as 256×256 grayscale (`docs/noise/`) — human eyes are ruthless pattern
331
+ detectors. The ESP32 is clean white noise; jitter shows its timer-quantization banding:
332
+
333
+ | `esp32` raw | `camera` raw | `jitter` raw | `crypto` |
334
+ |---|---|---|---|
335
+ | ![esp32 raw noise](docs/noise/esp32-raw.png) | ![camera raw noise](docs/noise/camera-raw.png) | ![jitter raw noise](docs/noise/jitter-raw.png) | ![crypto noise](docs/noise/crypto.png) |
336
+
337
+ ### Sustained streaming (steady state)
338
+
339
+ `bun scripts/stream-bench.ts` measures `stream()` throughput after the first chunk — i.e.
340
+ without session setup, permissions or warmup:
341
+
342
+ | Source | Steady-state rate | Notes |
343
+ |---|---|---|
344
+ | `esp32` raw | 68.8 KiB/s | wire-speed passthrough |
345
+ | `esp32` conditioned | 29.7 KiB/s | SHA-256, 2× credit |
346
+ | `jitter` conditioned | 8.6 KiB/s | |
347
+ | `camera` conditioned | 0.28 KiB/s | 15× the single-call figure — warmup dominates small reads |
348
+ | `superrand` WebSocket | 0.09 KiB/s | round-trip bound (one 32 B request in flight) |
349
+ | `drand` beacon | 0.01 KiB/s | by design: one public 32 B round every 3 s |
350
+
351
+ ### Honest labels
352
+
353
+ - `jitterEntropy()` in the browser requires `allowCoarseClock: true` and is named
354
+ `jitter(coarse)`: real but **unquantified** entropy — only ever mix it via `xorMix`,
355
+ never use it alone. The Node variant (nanosecond `hrtime`) is credited very
356
+ conservatively at 1/16 bit per timing delta.
357
+ - Camera/mic quality varies wildly with device DSP (auto-exposure, noise suppression);
358
+ the health tests are the guard, and cheap sensors are often *better* entropy sources.
359
+
360
+ ## Combining strategies
361
+
362
+ Strategies implement the same `EntropyProvider` interface, so they nest arbitrarily.
363
+
364
+ ```ts
365
+ import { fallback, race, xorMix } from '@mindpeeker/entropy'
366
+ import { anu, cryptoProvider, drand, randomOrg } from '@mindpeeker/entropy/providers'
367
+
368
+ // Priority order, first success wins:
369
+ fallback([anu({ apiKey }), randomOrg({ apiKey }), cryptoProvider()])
370
+
371
+ // Defense in depth: XOR of independent sources is as strong as the STRONGEST one.
372
+ // Fails closed — if any member fails, the call fails (wrap in fallback to degrade).
373
+ xorMix([anu({ apiKey }), cryptoProvider()])
374
+
375
+ // Latency-critical: all providers start, fastest response wins, losers are aborted.
376
+ race([anu({ apiKey }), randomOrg({ apiKey })])
377
+
378
+ // Composition: auditable-but-private, and never fails:
379
+ fallback([xorMix([drand(), cryptoProvider()]), cryptoProvider()])
380
+ ```
381
+
382
+ **XOR privacy rule**: mixing a *public* beacon with at least one *independent private* source
383
+ yields a **private** result (`xorMix` reports `privacy: 'private'` if any member is private) —
384
+ the beacon adds public auditability without exposing the output. The independence of the
385
+ sources is your assumption to keep: don't feed the same upstream in twice.
386
+
387
+ `fallback` and `race` report the *pessimistic* privacy (public if any member is public),
388
+ because you can't know statically which member will serve.
389
+
390
+ ## Streaming
391
+
392
+ Every provider has a lazy, pull-based `stream()` (`AsyncIterable<Uint8Array>`):
393
+
394
+ ```ts
395
+ for await (const chunk of drand().stream()) {
396
+ // one 32-byte value per new drand round (every ~3 s), deduplicated
397
+ }
398
+
399
+ // SuperRand streams over its WebSocket API with tagged in-flight requests,
400
+ // transparent reconnects (3 attempts, exponential backoff) and clean teardown:
401
+ for await (const chunk of superRand({ apiKey }).stream({ chunkBytes: 64 })) { /* … */ }
402
+ ```
403
+
404
+ Default streams poll `getBytes` per pull, so provider rate limits are honored automatically
405
+ (`anuLegacy().stream()` naturally emits at most one chunk per minute). Stop a stream with
406
+ `break` / `return()` or an `AbortSignal`.
407
+
408
+ ## Errors
409
+
410
+ Everything throws `EntropyError` with a `code`:
411
+ `rate_limited` (with `retryAfterMs` when known) · `auth` · `network` · `bad_response` ·
412
+ `insufficient_entropy` · `timeout` · `aborted` · `invalid_request` · `health_test`
413
+ (a local source failed its continuous NIST health tests).
414
+
415
+ When a strategy exhausts all members, you get `insufficient_entropy` whose `cause` is an
416
+ `AggregateError` holding each member's error in attempt order.
417
+
418
+ ```ts
419
+ try {
420
+ await provider.getBytes(64, { timeoutMs: 5000, signal })
421
+ } catch (error) {
422
+ if (error instanceof EntropyError && error.code === 'rate_limited') {
423
+ console.log(`retry in ${error.retryAfterMs}ms`)
424
+ }
425
+ }
426
+ ```
427
+
428
+ ## Custom providers
429
+
430
+ ```ts
431
+ import { defineProvider } from '@mindpeeker/entropy'
432
+
433
+ const myQrng = defineProvider({
434
+ name: 'my-hardware',
435
+ kind: 'qrng',
436
+ privacy: 'private',
437
+ async getBytes(n, { signal } = {}) {
438
+ const bytes = await readFromMyDevice(n, signal)
439
+ return { bytes, sources: [{ name: 'my-hardware', kind: 'qrng', privacy: 'private' }] }
440
+ },
441
+ })
442
+ ```
443
+
444
+ `defineProvider` gives you length validation, abort handling, whole-call timeouts and a
445
+ default poll-based `stream()` for free.
446
+
447
+ ## Browser caveats
448
+
449
+ - **API keys in browser code are public.** For keyed providers, proxy server-side and point
450
+ the provider at your proxy via `baseUrl` — or use only keyless providers client-side.
451
+ - CORS support is unverified for several keyless services (lfdr, qrandom.io); if a provider
452
+ is blocked by CORS in the browser, route it through a small proxy.
453
+ - Node 20 has no global `WebSocket` (Node ≥ 21 does): pass `superRand({ WebSocketCtor })`,
454
+ e.g. from the `ws` package.
455
+
456
+ ## Sources we evaluated — and why they're NOT providers
457
+
458
+ Researched (mid-2026), rejected, and documented so nobody re-litigates them:
459
+
460
+ | Candidate | Why not |
461
+ |---|---|
462
+ | Network RTT jitter | attacker-observable and manipulable — RFC 4086 §3.5 says such input "must not be trusted as a source of entropy"; the Linux kernel removed exactly this source (`IRQF_SAMPLE_RANDOM`, gone since 3.6). Mix-don't-credit is the only defensible pattern, so we don't ship it |
463
+ | `/proc` interrupt/stat sampling | the kernel sees those events first and better; read-timing is our existing CPU-jitter source in disguise (no independent entropy) |
464
+ | Disk seek timing | the classic CRYPTO'94 air-turbulence source died with spinning platters; NVMe latency is engineered for *consistency* |
465
+ | macOS `powermetrics` / battery `ioreg` | needs root / high-inertia sensors with ≪1 bit per sample |
466
+ | Linux thermal zones | millidegree *units*, 0.5–1 °C actual quantization — near-zero entropy |
467
+ | RDRAND/RDSEED from JS | needs a native addon (zero-dep violation); the OS CSPRNG already mixes it |
468
+ | BLE heart-rate straps etc. | ~1 Hz and the literature is titled "Heartbeats Do Not Make Good PRNGs" |
469
+ | WebGPU races, DRAM (D-RaNGe), SGX | not reachable from JS/Node — speculative research |
470
+ | CURBy-Q quantum chain | stalled since 2025-08 (same round for months) — we use the fresh CURBy-RNG chain instead |
471
+ | USTC Jinan DIQRNG beacon | expired TLS + data stale since 2025-09 |
472
+ | LizaOnAir "QRNG" | an ANU-*seeded PRNG* (its own metadata admits hours-old seeds) |
473
+ | Tsotchke "Quantum RNG" | unverifiable marketing claims from a software vendor |
474
+ | csrng.net | a CSPRNG service — out of scope |
475
+ | beaconcha.in, Aptos/Sui/ICP randomness | now key-gated / not readable over plain HTTP |
476
+ | WebUSB RTL-SDR in the browser | possible in principle, stale pre-V4 libraries — documented as experimental only |
477
+
478
+ ## Verifying against live APIs
479
+
480
+ Mocked tests are authoritative for CI. To exercise real endpoints:
481
+
482
+ ```sh
483
+ LIVE=1 bun test live # keyless providers
484
+ LIVE=1 ANU_API_KEY=… RANDOM_ORG_API_KEY=… bun test live # + keyed ones
485
+ ```
486
+
487
+ Instead of exporting keys inline, copy the workspace's `.env.example` to `.env` (git-ignored)
488
+ and fill in what you have — Bun loads it automatically, and each keyed provider's live test
489
+ runs only when its variable is set.
490
+
491
+ The live suite loads the nearest `.env` itself (walking up from the test file), so it works
492
+ from the workspace root and from inside `packages/entropy` alike.
493
+
494
+ ## License
495
+
496
+ MIT
@@ -0,0 +1,14 @@
1
+ export type EntropyErrorCode = 'rate_limited' | 'auth' | 'network' | 'bad_response' | 'insufficient_entropy' | 'timeout' | 'aborted' | 'invalid_request' | 'health_test';
2
+ export interface EntropyErrorOptions {
3
+ provider?: string;
4
+ retryAfterMs?: number;
5
+ cause?: unknown;
6
+ }
7
+ export declare class EntropyError extends Error {
8
+ readonly code: EntropyErrorCode;
9
+ readonly provider?: string;
10
+ readonly retryAfterMs?: number;
11
+ readonly cause?: unknown;
12
+ constructor(code: EntropyErrorCode, message: string, opts?: EntropyErrorOptions);
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,MAAM,GACN,SAAS,GACT,cAAc,GACd,sBAAsB,GACtB,SAAS,GACT,SAAS,GACT,iBAAiB,GACjB,aAAa,CAAA;AAEjB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,SAAiB,KAAK,CAAC,EAAE,OAAO,CAAA;gBAEpB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB;CAOpF"}
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ export class EntropyError extends Error {
2
+ code;
3
+ provider;
4
+ retryAfterMs;
5
+ constructor(code, message, opts = {}) {
6
+ super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
7
+ this.name = 'EntropyError';
8
+ this.code = code;
9
+ this.provider = opts.provider;
10
+ this.retryAfterMs = opts.retryAfterMs;
11
+ }
12
+ }
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,IAAI,CAAkB;IACtB,QAAQ,CAAS;IACjB,YAAY,CAAS;IAG9B,YAAY,IAAsB,EAAE,OAAe,EAAE,OAA4B,EAAE;QACjF,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QAC5E,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAA;IACvC,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ export type { EntropyErrorCode, EntropyErrorOptions } from './errors.js';
2
+ export { EntropyError } from './errors.js';
3
+ export type { ByteSource } from './internal/byte-source.js';
4
+ export type { ConditioningMode, ConditioningOptions } from './internal/condition.js';
5
+ export type { NistFamilyOptions } from './internal/nist-pulse.js';
6
+ export type { ProviderSpec } from './internal/provider.js';
7
+ export { defineProvider } from './internal/provider.js';
8
+ export type { FallbackOptions } from './strategies/fallback.js';
9
+ export { fallback } from './strategies/fallback.js';
10
+ export { race } from './strategies/race.js';
11
+ export { xorMix } from './strategies/xor.js';
12
+ export type { EntropyKind, EntropyPrivacy, EntropyProvider, EntropyRequestOptions, EntropyResult, EntropySourceInfo, EntropyStreamOptions, } from './types.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAC3D,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AACjE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEvD,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,YAAY,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Core of @mindpeeker/entropy: types, errors, combining strategies and the
2
+ // custom-provider building blocks. The providers themselves live behind
3
+ // '@mindpeeker/entropy/providers'; Node-only adapters behind
4
+ // '@mindpeeker/entropy/node'.
5
+ export { EntropyError } from './errors.js';
6
+ export { defineProvider } from './internal/provider.js';
7
+ export { fallback } from './strategies/fallback.js';
8
+ export { race } from './strategies/race.js';
9
+ export { xorMix } from './strategies/xor.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,6DAA6D;AAC7D,8BAA8B;AAG9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM1C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,9 @@
1
+ /** Pack 0/1 samples into bytes, MSB first. Returns the packed bytes and any trailing bits (<8). */
2
+ export declare function packBits(bits: readonly number[]): [Uint8Array, number[]];
3
+ /**
4
+ * Von Neumann debiasing: consume bit pairs, 01→0, 10→1, 00/11 discarded.
5
+ * Removes bias exactly when bits are independent; yield is at most 25%.
6
+ * A trailing unpaired bit is dropped.
7
+ */
8
+ export declare function vonNeumann(bits: readonly number[]): number[];
9
+ //# sourceMappingURL=bits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bits.d.ts","sourceRoot":"","sources":["../../src/internal/bits.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAWxE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQ5D"}
@@ -0,0 +1,29 @@
1
+ /** Pack 0/1 samples into bytes, MSB first. Returns the packed bytes and any trailing bits (<8). */
2
+ export function packBits(bits) {
3
+ const byteCount = Math.floor(bits.length / 8);
4
+ const out = new Uint8Array(byteCount);
5
+ for (let i = 0; i < byteCount; i++) {
6
+ let value = 0;
7
+ for (let j = 0; j < 8; j++) {
8
+ value = (value << 1) | (bits[i * 8 + j] & 1);
9
+ }
10
+ out[i] = value;
11
+ }
12
+ return [out, bits.slice(byteCount * 8)];
13
+ }
14
+ /**
15
+ * Von Neumann debiasing: consume bit pairs, 01→0, 10→1, 00/11 discarded.
16
+ * Removes bias exactly when bits are independent; yield is at most 25%.
17
+ * A trailing unpaired bit is dropped.
18
+ */
19
+ export function vonNeumann(bits) {
20
+ const out = [];
21
+ for (let i = 0; i + 1 < bits.length; i += 2) {
22
+ const a = bits[i];
23
+ const b = bits[i + 1];
24
+ if (a !== b)
25
+ out.push(a === 1 ? 1 : 0);
26
+ }
27
+ return out;
28
+ }
29
+ //# sourceMappingURL=bits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bits.js","sourceRoot":"","sources":["../../src/internal/bits.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,MAAM,UAAU,QAAQ,CAAC,IAAuB;IAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC7C,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAA;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAE,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAY,GAAG,CAAC,CAAC,CAAA;QAC1D,CAAC;QACD,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;IAChB,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAa,CAAC,CAAA;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAuB;IAChD,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACrB,IAAI,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACxC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}