@q-ching/core 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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/dist/casting.d.ts +45 -0
  4. package/dist/casting.js +110 -0
  5. package/dist/casting.js.map +1 -0
  6. package/dist/engine.test.d.ts +1 -0
  7. package/dist/engine.test.js +74 -0
  8. package/dist/engine.test.js.map +1 -0
  9. package/dist/entropy/gesture.d.ts +26 -0
  10. package/dist/entropy/gesture.js +52 -0
  11. package/dist/entropy/gesture.js.map +1 -0
  12. package/dist/entropy/pool.d.ts +29 -0
  13. package/dist/entropy/pool.js +70 -0
  14. package/dist/entropy/pool.js.map +1 -0
  15. package/dist/entropy/qrng.d.ts +47 -0
  16. package/dist/entropy/qrng.js +130 -0
  17. package/dist/entropy/qrng.js.map +1 -0
  18. package/dist/hexagram-data.d.ts +2 -0
  19. package/dist/hexagram-data.js +1859 -0
  20. package/dist/hexagram-data.js.map +1 -0
  21. package/dist/hexagrams.d.ts +19 -0
  22. package/dist/hexagrams.js +90 -0
  23. package/dist/hexagrams.js.map +1 -0
  24. package/dist/index.d.ts +14 -0
  25. package/dist/index.js +15 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/trigrams.d.ts +10 -0
  28. package/dist/trigrams.js +60 -0
  29. package/dist/trigrams.js.map +1 -0
  30. package/dist/types.d.ts +82 -0
  31. package/dist/types.js +10 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/util.d.ts +17 -0
  34. package/dist/util.js +79 -0
  35. package/dist/util.js.map +1 -0
  36. package/package.json +48 -0
  37. package/src/casting.ts +131 -0
  38. package/src/engine.test.ts +77 -0
  39. package/src/entropy/gesture.ts +57 -0
  40. package/src/entropy/pool.ts +74 -0
  41. package/src/entropy/qrng.ts +170 -0
  42. package/src/hexagram-data.ts +1863 -0
  43. package/src/hexagrams.ts +97 -0
  44. package/src/index.ts +33 -0
  45. package/src/trigrams.ts +66 -0
  46. package/src/types.ts +98 -0
  47. package/src/util.ts +83 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jean Llorca
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,33 @@
1
+ # @q-ching/core
2
+
3
+ > Platform-agnostic I-Ching engine: entropy pool, QRNG clients, casting math, and the 64 hexagrams.
4
+
5
+ The dependency-free engine behind [q-ching](https://github.com/Hylaean/q-ching#readme). It relies only on Web Crypto (`crypto.subtle`) and global `fetch`, so the *same* engine runs in the browser, in Node, and in the terminal. It powers the [`@q-ching/tui`](https://www.npmjs.com/package/@q-ching/tui) terminal app and the [`@q-ching/mcp`](https://www.npmjs.com/package/@q-ching/mcp) server.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @q-ching/core
11
+ ```
12
+
13
+ ## Cast a reading
14
+
15
+ ```js
16
+ import { cast } from "@q-ching/core";
17
+
18
+ const reading = await cast({ question: "What should I attend to?" });
19
+ console.log(reading.primary.number, reading.primary.name.english);
20
+ console.log(reading.changingPositions); // which lines (1..6) are changing
21
+ console.log(reading.transformed?.number); // the hexagram it changes into, or null
22
+ console.log(reading.seed); // reproducible: cast({ seed }) reproduces it exactly
23
+ ```
24
+
25
+ ## What it does
26
+
27
+ A reading is cast by absorbing the querent's gesture entropy, concurrently-fetched QRNG draws (NIST beacon, ANU, RANDOM.ORG), a fresh local CSPRNG draw, and a timestamp salt into an HKDF-style `EntropyPool`, then squeezing a whitened bit stream and drawing six lines. The pool's `fingerprint()` is the shareable, auditable seed; the local CSPRNG always carries the cast, so a blocked remote source can never block or bias it.
28
+
29
+ Requires Node ≥ 20 (or any modern browser).
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1,45 @@
1
+ import type { CastMethod, LineValue, Reading } from './types.js';
2
+ import { BitReader } from './util.js';
3
+ import { type QrngConfig, type QrngResult } from './entropy/qrng.js';
4
+ /**
5
+ * Coin method (three coins). Each coin: heads = 3, tails = 2. Three fair bits
6
+ * decide the line. With h = number of heads, value = 6 + h, giving
7
+ * 6 (old yin) 1/8
8
+ * 7 (young yang) 3/8
9
+ * 8 (young yin) 3/8
10
+ * 9 (old yang) 1/8
11
+ */
12
+ declare function coinLine(reader: BitReader): LineValue;
13
+ /**
14
+ * Yarrow-stalk method. The traditional stalk ritual yields an asymmetric
15
+ * distribution that makes changing lines rarer:
16
+ * 6 (old yin) 1/16
17
+ * 7 (young yang) 5/16
18
+ * 8 (young yin) 7/16
19
+ * 9 (old yang) 3/16
20
+ * Drawn from four uniform bits (0..15), since 16 is a power of two.
21
+ */
22
+ declare function yarrowLine(reader: BitReader): LineValue;
23
+ export interface CastInput {
24
+ /** 'coin' (default) or 'yarrow'. */
25
+ method?: CastMethod;
26
+ /** Bytes captured from a human gesture (mouse, touch, motion, keystrokes). */
27
+ userEntropy?: Uint8Array | number[];
28
+ /**
29
+ * Quantum entropy: pass `true` to auto-gather, a QrngConfig to configure the
30
+ * gather, or a pre-fetched QrngResult[] (e.g. fetched server-side).
31
+ */
32
+ qrng?: boolean | QrngConfig | QrngResult[];
33
+ /** Reproduce a prior cast from its seed (hex fingerprint). */
34
+ seed?: string;
35
+ /** Override the clock (mainly for tests/determinism). */
36
+ now?: () => number;
37
+ }
38
+ /**
39
+ * Cast a reading. Builds an entropy pool from the querent's gesture, optional
40
+ * quantum sources, and the local CSPRNG; squeezes a whitened stream; and draws
41
+ * six lines bottom -> top, deriving the primary hexagram, the changing lines,
42
+ * and the transformed hexagram.
43
+ */
44
+ export declare function cast(input?: CastInput): Promise<Reading>;
45
+ export { coinLine, yarrowLine };
@@ -0,0 +1,110 @@
1
+ import { BitReader } from './util.js';
2
+ import { EntropyPool } from './entropy/pool.js';
3
+ import { gatherEntropy, localCsprng } from './entropy/qrng.js';
4
+ import { hexagramByBits } from './hexagrams.js';
5
+ /**
6
+ * Coin method (three coins). Each coin: heads = 3, tails = 2. Three fair bits
7
+ * decide the line. With h = number of heads, value = 6 + h, giving
8
+ * 6 (old yin) 1/8
9
+ * 7 (young yang) 3/8
10
+ * 8 (young yin) 3/8
11
+ * 9 (old yang) 1/8
12
+ */
13
+ function coinLine(reader) {
14
+ const h = reader.readBit() + reader.readBit() + reader.readBit();
15
+ return (6 + h);
16
+ }
17
+ /**
18
+ * Yarrow-stalk method. The traditional stalk ritual yields an asymmetric
19
+ * distribution that makes changing lines rarer:
20
+ * 6 (old yin) 1/16
21
+ * 7 (young yang) 5/16
22
+ * 8 (young yin) 7/16
23
+ * 9 (old yang) 3/16
24
+ * Drawn from four uniform bits (0..15), since 16 is a power of two.
25
+ */
26
+ function yarrowLine(reader) {
27
+ const v = reader.readBits(4); // 0..15, uniform
28
+ if (v === 0)
29
+ return 6; // 1/16
30
+ if (v <= 3)
31
+ return 9; // 3/16 (1,2,3)
32
+ if (v <= 8)
33
+ return 7; // 5/16 (4,5,6,7,8)
34
+ return 8; // 7/16 (9..15)
35
+ }
36
+ const BYTES_TO_GATHER = 48;
37
+ const STREAM_BYTES = 64; // 6 lines need at most 24 bits; 64 bytes is generous headroom
38
+ /**
39
+ * Cast a reading. Builds an entropy pool from the querent's gesture, optional
40
+ * quantum sources, and the local CSPRNG; squeezes a whitened stream; and draws
41
+ * six lines bottom -> top, deriving the primary hexagram, the changing lines,
42
+ * and the transformed hexagram.
43
+ */
44
+ export async function cast(input = {}) {
45
+ const method = input.method ?? 'coin';
46
+ const now = input.now ?? (() => Date.now());
47
+ let pool;
48
+ if (input.seed) {
49
+ pool = EntropyPool.fromSeed(input.seed);
50
+ }
51
+ else {
52
+ pool = new EntropyPool();
53
+ if (input.userEntropy && input.userEntropy.length) {
54
+ pool.absorb('gesture', input.userEntropy);
55
+ }
56
+ if (input.qrng) {
57
+ let results;
58
+ if (Array.isArray(input.qrng)) {
59
+ results = input.qrng;
60
+ }
61
+ else {
62
+ const config = input.qrng === true ? {} : input.qrng;
63
+ results = await gatherEntropy(BYTES_TO_GATHER, config);
64
+ }
65
+ for (const r of results) {
66
+ if (r.ok && r.bytes && r.bytes.length)
67
+ pool.absorb(`qrng:${r.source}`, r.bytes);
68
+ }
69
+ }
70
+ // Always fold in a fresh local CSPRNG draw and a timestamp salt.
71
+ pool.absorb('csprng', localCsprng(32));
72
+ pool.absorb('time', String(now()));
73
+ }
74
+ const stream = await pool.squeeze(STREAM_BYTES);
75
+ const reader = new BitReader(stream);
76
+ const lines = [];
77
+ for (let i = 0; i < 6; i++) {
78
+ const value = method === 'coin' ? coinLine(reader) : yarrowLine(reader);
79
+ lines.push({
80
+ position: i + 1,
81
+ value,
82
+ yang: value === 7 || value === 9,
83
+ changing: value === 6 || value === 9,
84
+ });
85
+ }
86
+ const primaryBits = lines.map((l) => (l.yang ? 1 : 0));
87
+ const primary = hexagramByBits(primaryBits);
88
+ const changingPositions = lines.filter((l) => l.changing).map((l) => l.position);
89
+ let transformed = null;
90
+ if (changingPositions.length > 0) {
91
+ const tBits = lines.map((l) => {
92
+ if (l.changing)
93
+ return (l.yang ? 0 : 1); // old yang -> yin, old yin -> yang
94
+ return (l.yang ? 1 : 0);
95
+ });
96
+ transformed = hexagramByBits(tBits);
97
+ }
98
+ return {
99
+ method,
100
+ lines,
101
+ primary,
102
+ transformed,
103
+ changingPositions,
104
+ seed: await pool.fingerprint(),
105
+ sources: pool.transcript,
106
+ createdAt: new Date(now()).toISOString(),
107
+ };
108
+ }
109
+ export { coinLine, yarrowLine };
110
+ //# sourceMappingURL=casting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"casting.js","sourceRoot":"","sources":["../src/casting.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { cast } from './casting.js';
4
+ import { EntropyPool } from './entropy/pool.js';
5
+ import { HEXAGRAMS, hexagramByBits, hexagramByNumber, validateHexagrams } from './hexagrams.js';
6
+ import { BitReader } from './util.js';
7
+ import { coinLine, yarrowLine } from './casting.js';
8
+ test('dataset passes deterministic structural validation', () => {
9
+ const result = validateHexagrams();
10
+ assert.equal(result.ok, true, result.errors.join('\n'));
11
+ assert.equal(HEXAGRAMS.length, 64);
12
+ });
13
+ test('every hexagram has complete prose', () => {
14
+ for (const h of HEXAGRAMS) {
15
+ assert.ok(h.judgment.length > 10, `#${h.number} judgment`);
16
+ assert.ok(h.image.length > 10, `#${h.number} image`);
17
+ assert.ok(h.gloss.length > 2, `#${h.number} gloss`);
18
+ assert.equal(h.lineTexts.length, 6, `#${h.number} lineTexts`);
19
+ for (const lt of h.lineTexts)
20
+ assert.ok(lt.length > 3, `#${h.number} line text`);
21
+ }
22
+ });
23
+ test('hexagram 1 is Qian (all yang), 2 is Kun (all yin)', () => {
24
+ assert.equal(hexagramByNumber(1).bits.join(''), '111111');
25
+ assert.equal(hexagramByNumber(2).bits.join(''), '000000');
26
+ assert.equal(hexagramByBits([1, 1, 1, 1, 1, 1]).number, 1);
27
+ assert.equal(hexagramByBits([0, 0, 0, 0, 0, 0]).number, 2);
28
+ });
29
+ test('a seed reproduces an identical reading', async () => {
30
+ const a = await cast({ userEntropy: [1, 2, 3, 4, 5], now: () => 1000 });
31
+ const b = await cast({ seed: a.seed, now: () => 1000 });
32
+ assert.equal(a.primary.number, b.primary.number);
33
+ assert.equal(a.transformed?.number ?? null, b.transformed?.number ?? null);
34
+ assert.deepEqual(a.lines.map((l) => l.value), b.lines.map((l) => l.value));
35
+ });
36
+ test('coin distribution ~ 1/8, 3/8, 3/8, 1/8', async () => {
37
+ const counts = { 6: 0, 7: 0, 8: 0, 9: 0 };
38
+ const N = 200_000;
39
+ // deterministic uniform bytes from the pool, expanded large
40
+ const stream = await EntropyPool.fromSeed('00'.repeat(32)).squeeze(Math.ceil((N * 3) / 8) + 64);
41
+ const reader = new BitReader(stream);
42
+ for (let i = 0; i < N; i++)
43
+ counts[coinLine(reader)]++;
44
+ assert.ok(Math.abs(counts[6] / N - 1 / 8) < 0.01, `6: ${counts[6] / N}`);
45
+ assert.ok(Math.abs(counts[7] / N - 3 / 8) < 0.01, `7: ${counts[7] / N}`);
46
+ assert.ok(Math.abs(counts[8] / N - 3 / 8) < 0.01, `8: ${counts[8] / N}`);
47
+ assert.ok(Math.abs(counts[9] / N - 1 / 8) < 0.01, `9: ${counts[9] / N}`);
48
+ });
49
+ test('yarrow distribution ~ 1/16, 5/16, 7/16, 3/16', async () => {
50
+ const counts = { 6: 0, 7: 0, 8: 0, 9: 0 };
51
+ const N = 200_000;
52
+ const stream = await EntropyPool.fromSeed('a5'.repeat(32)).squeeze(Math.ceil((N * 4) / 8) + 64);
53
+ const reader = new BitReader(stream);
54
+ for (let i = 0; i < N; i++)
55
+ counts[yarrowLine(reader)]++;
56
+ assert.ok(Math.abs(counts[6] / N - 1 / 16) < 0.01, `6: ${counts[6] / N}`);
57
+ assert.ok(Math.abs(counts[7] / N - 5 / 16) < 0.01, `7: ${counts[7] / N}`);
58
+ assert.ok(Math.abs(counts[8] / N - 7 / 16) < 0.01, `8: ${counts[8] / N}`);
59
+ assert.ok(Math.abs(counts[9] / N - 3 / 16) < 0.01, `9: ${counts[9] / N}`);
60
+ });
61
+ test('changing lines produce a distinct transformed hexagram', async () => {
62
+ // search seeds for a cast with changing lines
63
+ let found = false;
64
+ for (let i = 0; i < 50 && !found; i++) {
65
+ const r = await cast({ userEntropy: [i], now: () => i });
66
+ if (r.changingPositions.length > 0) {
67
+ assert.ok(r.transformed, 'should have transformed hexagram');
68
+ assert.notEqual(r.transformed.number, r.primary.number);
69
+ found = true;
70
+ }
71
+ }
72
+ assert.ok(found, 'expected at least one changing-line cast in 50 tries');
73
+ });
74
+ //# sourceMappingURL=engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.test.js","sourceRoot":"","sources":["../src/engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A platform-agnostic accumulator for human "gesture" entropy: pointer moves,
3
+ * touch paths, device-motion samples, keystroke timings — anything the
4
+ * querent's body does while they hold their question in mind.
5
+ *
6
+ * It does not try to estimate entropy precisely; it simply captures the raw
7
+ * float bytes of each sample. The EntropyPool hashes everything afterward, so
8
+ * over-capture is harmless and a few genuinely unpredictable bits (the timing
9
+ * jitter of a human hand) are what matter.
10
+ */
11
+ export declare class GestureEntropy {
12
+ private buf;
13
+ private _count;
14
+ /** Number of samples captured. */
15
+ get count(): number;
16
+ private pushFloat;
17
+ /** Record a pointer/touch sample at (x, y) with a high-resolution timestamp. */
18
+ push(x: number, y: number, t: number): this;
19
+ /** Record a single scalar (e.g. an accelerometer axis or a key interval). */
20
+ pushScalar(v: number): this;
21
+ /** The captured bytes. */
22
+ get bytes(): Uint8Array;
23
+ /** A coarse 0..1 sense of "how much" the querent has stirred — for UI meters. */
24
+ get fill(): number;
25
+ reset(): void;
26
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A platform-agnostic accumulator for human "gesture" entropy: pointer moves,
3
+ * touch paths, device-motion samples, keystroke timings — anything the
4
+ * querent's body does while they hold their question in mind.
5
+ *
6
+ * It does not try to estimate entropy precisely; it simply captures the raw
7
+ * float bytes of each sample. The EntropyPool hashes everything afterward, so
8
+ * over-capture is harmless and a few genuinely unpredictable bits (the timing
9
+ * jitter of a human hand) are what matter.
10
+ */
11
+ export class GestureEntropy {
12
+ buf = [];
13
+ _count = 0;
14
+ /** Number of samples captured. */
15
+ get count() {
16
+ return this._count;
17
+ }
18
+ pushFloat(v) {
19
+ if (!Number.isFinite(v))
20
+ v = 0;
21
+ const dv = new DataView(new ArrayBuffer(4));
22
+ dv.setFloat32(0, v, true);
23
+ this.buf.push(dv.getUint8(0), dv.getUint8(1), dv.getUint8(2), dv.getUint8(3));
24
+ }
25
+ /** Record a pointer/touch sample at (x, y) with a high-resolution timestamp. */
26
+ push(x, y, t) {
27
+ this.pushFloat(x);
28
+ this.pushFloat(y);
29
+ this.pushFloat(t);
30
+ this._count += 1;
31
+ return this;
32
+ }
33
+ /** Record a single scalar (e.g. an accelerometer axis or a key interval). */
34
+ pushScalar(v) {
35
+ this.pushFloat(v);
36
+ this._count += 1;
37
+ return this;
38
+ }
39
+ /** The captured bytes. */
40
+ get bytes() {
41
+ return Uint8Array.from(this.buf);
42
+ }
43
+ /** A coarse 0..1 sense of "how much" the querent has stirred — for UI meters. */
44
+ get fill() {
45
+ return Math.min(1, this._count / 96);
46
+ }
47
+ reset() {
48
+ this.buf = [];
49
+ this._count = 0;
50
+ }
51
+ }
52
+ //# sourceMappingURL=gesture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gesture.js","sourceRoot":"","sources":["../../src/entropy/gesture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,cAAc;IACjB,GAAG,GAAa,EAAE,CAAC;IACnB,MAAM,GAAG,CAAC,CAAC;IAEnB,kCAAkC;IAClC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAEO,SAAS,CAAC,CAAS;QACzB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,gFAAgF;IAChF,IAAI,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;QAClC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,UAAU,CAAC,CAAS;QAClB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0BAA0B;IAC1B,IAAI,KAAK;QACP,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,iFAAiF;IACjF,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import type { EntropySourceRecord } from '../types.js';
2
+ /**
3
+ * An entropy pool that absorbs bytes from many labelled sources (a human
4
+ * gesture, one or more quantum RNGs, a local CSPRNG) and squeezes a
5
+ * whitened, uniform byte stream out of them.
6
+ *
7
+ * Design: extract-then-expand, HKDF-style.
8
+ * PRK = SHA-256( source_1 || source_2 || ... ) (the "seed")
9
+ * output_i = SHA-256( PRK || counter_i ) (the expansion)
10
+ *
11
+ * No single source can bias the result, a dead QRNG can't block a cast, and
12
+ * the PRK is exposed as a hex `fingerprint()` so a reading is fully
13
+ * reproducible from its seed — the basis for shareable, auditable casts.
14
+ */
15
+ export declare class EntropyPool {
16
+ private chunks;
17
+ private _prk;
18
+ /** Absorb bytes from a source. Chainable. */
19
+ absorb(label: string, data: Uint8Array | number[] | string): this;
20
+ /** A transcript of what contributed entropy and how much. */
21
+ get transcript(): EntropySourceRecord[];
22
+ private prk;
23
+ /** The reproducible seed: hex of the extracted pseudo-random key. */
24
+ fingerprint(): Promise<string>;
25
+ /** Produce `numBytes` of uniform output, deterministic for a given PRK. */
26
+ squeeze(numBytes: number): Promise<Uint8Array>;
27
+ /** Reconstruct a pool from a previously emitted seed (hex of the PRK). */
28
+ static fromSeed(seedHex: string): EntropyPool;
29
+ }
@@ -0,0 +1,70 @@
1
+ import { concatBytes, fromHex, sha256, toBytes, toHex } from '../util.js';
2
+ /**
3
+ * An entropy pool that absorbs bytes from many labelled sources (a human
4
+ * gesture, one or more quantum RNGs, a local CSPRNG) and squeezes a
5
+ * whitened, uniform byte stream out of them.
6
+ *
7
+ * Design: extract-then-expand, HKDF-style.
8
+ * PRK = SHA-256( source_1 || source_2 || ... ) (the "seed")
9
+ * output_i = SHA-256( PRK || counter_i ) (the expansion)
10
+ *
11
+ * No single source can bias the result, a dead QRNG can't block a cast, and
12
+ * the PRK is exposed as a hex `fingerprint()` so a reading is fully
13
+ * reproducible from its seed — the basis for shareable, auditable casts.
14
+ */
15
+ export class EntropyPool {
16
+ chunks = [];
17
+ _prk = null;
18
+ /** Absorb bytes from a source. Chainable. */
19
+ absorb(label, data) {
20
+ if (this._prk) {
21
+ throw new Error('Cannot absorb into a pool created from a fixed seed.');
22
+ }
23
+ const bytes = toBytes(data);
24
+ if (bytes.length > 0)
25
+ this.chunks.push({ label, bytes });
26
+ return this;
27
+ }
28
+ /** A transcript of what contributed entropy and how much. */
29
+ get transcript() {
30
+ if (this._prk)
31
+ return [{ label: 'seed', bytes: this._prk.length }];
32
+ return this.chunks.map((c) => ({ label: c.label, bytes: c.bytes.length }));
33
+ }
34
+ async prk() {
35
+ if (this._prk)
36
+ return this._prk;
37
+ const all = concatBytes(this.chunks.map((c) => c.bytes));
38
+ this._prk = await sha256(all);
39
+ return this._prk;
40
+ }
41
+ /** The reproducible seed: hex of the extracted pseudo-random key. */
42
+ async fingerprint() {
43
+ return toHex(await this.prk());
44
+ }
45
+ /** Produce `numBytes` of uniform output, deterministic for a given PRK. */
46
+ async squeeze(numBytes) {
47
+ const prk = await this.prk();
48
+ const out = new Uint8Array(numBytes);
49
+ let off = 0;
50
+ let counter = 0;
51
+ while (off < numBytes) {
52
+ const input = new Uint8Array(prk.length + 4);
53
+ input.set(prk, 0);
54
+ new DataView(input.buffer).setUint32(prk.length, counter, false);
55
+ counter += 1;
56
+ const block = await sha256(input);
57
+ const take = Math.min(block.length, numBytes - off);
58
+ out.set(block.subarray(0, take), off);
59
+ off += take;
60
+ }
61
+ return out;
62
+ }
63
+ /** Reconstruct a pool from a previously emitted seed (hex of the PRK). */
64
+ static fromSeed(seedHex) {
65
+ const pool = new EntropyPool();
66
+ pool._prk = fromHex(seedHex);
67
+ return pool;
68
+ }
69
+ }
70
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/entropy/pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Quantum / true random number sources.
3
+ *
4
+ * Honest note: a local CSPRNG is already statistically perfect for casting an
5
+ * oracle. The quantum sources are here for *meaning* — your hexagram drawn
6
+ * from vacuum fluctuations and an atmospheric hiss — and for transparency, not
7
+ * because they are "more random". Every gather always folds in the local
8
+ * CSPRNG so a cast can never be blocked or biased by a flaky remote API.
9
+ */
10
+ export type QrngSourceName = 'nist' | 'anu' | 'random.org' | 'csprng';
11
+ export interface QrngResult {
12
+ source: QrngSourceName;
13
+ ok: boolean;
14
+ bytes: Uint8Array | null;
15
+ detail?: string;
16
+ }
17
+ export interface QrngConfig {
18
+ /** Which sources to attempt. Default: ['nist', 'csprng']. csprng is always included. */
19
+ sources?: QrngSourceName[];
20
+ anuApiKey?: string;
21
+ randomOrgApiKey?: string;
22
+ /** Inject a fetch implementation (defaults to global fetch). */
23
+ fetchImpl?: typeof fetch;
24
+ /** Per-request timeout in ms (default 6000). */
25
+ timeoutMs?: number;
26
+ }
27
+ export declare function localCsprng(numBytes: number): Uint8Array;
28
+ /**
29
+ * NIST Randomness Beacon (v2). Public, keyless, quantum-seeded. Publishes a
30
+ * 512-bit value every 60s — the same for everyone in a given minute, which is
31
+ * its own kind of poetry: the cosmic pulse at the moment you asked.
32
+ */
33
+ export declare function fetchNistBeacon(numBytes: number, config?: QrngConfig): Promise<QrngResult>;
34
+ /**
35
+ * ANU Quantum Random Numbers — vacuum fluctuations. The classic public
36
+ * endpoint is now rate-limited and may require an API key; we pass one if
37
+ * provided and fail gracefully otherwise.
38
+ */
39
+ export declare function fetchAnu(numBytes: number, config?: QrngConfig): Promise<QrngResult>;
40
+ /** RANDOM.ORG atmospheric noise (JSON-RPC v4). Requires an API key. */
41
+ export declare function fetchRandomOrg(numBytes: number, config?: QrngConfig): Promise<QrngResult>;
42
+ /**
43
+ * Attempt every configured source concurrently and always include the local
44
+ * CSPRNG. Returns one result per attempted source (failures included, so the
45
+ * UI can show what answered the call).
46
+ */
47
+ export declare function gatherEntropy(numBytes: number, config?: QrngConfig): Promise<QrngResult[]>;