@kirkelliott/kdfts 1.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.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/bench/index.js +232 -0
- package/dist/core/complex.js +37 -0
- package/dist/core/gates.js +148 -0
- package/dist/core/state.js +74 -0
- package/dist/crypto/kdf.js +113 -0
- package/dist/index.js +9 -0
- package/dist/kdf.js +99 -0
- package/dist/lang/circuit.js +330 -0
- package/dist/lang/encoder.js +146 -0
- package/dist/lang/ops.js +55 -0
- package/dist/qrng/anu.js +125 -0
- package/dist/qrng.js +72 -0
- package/package.json +31 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.quantumScrypt = quantumScrypt;
|
|
4
|
+
exports.verifyKey = verifyKey;
|
|
5
|
+
exports.formatCertificate = formatCertificate;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const anu_1 = require("../qrng/anu");
|
|
8
|
+
const scryptAsync = (password, salt, keyLen, opts) => new Promise((resolve, reject) => (0, crypto_1.scrypt)(password, salt, keyLen, opts, (err, key) => err ? reject(err) : resolve(key)));
|
|
9
|
+
const DEFAULT_PARAMS = { N: 16384, r: 8, p: 1, keyLen: 32 };
|
|
10
|
+
/**
|
|
11
|
+
* Derive a cryptographic key from a password using scrypt,
|
|
12
|
+
* with a salt sourced from ANU quantum vacuum fluctuations.
|
|
13
|
+
*
|
|
14
|
+
* The security claim:
|
|
15
|
+
* Classical KDFs use pseudo-random salts (computationally indistinguishable
|
|
16
|
+
* from random). This uses physically random salt — unpredictable by any
|
|
17
|
+
* computation because the randomness comes from quantum vacuum fluctuations,
|
|
18
|
+
* not an algorithm.
|
|
19
|
+
*
|
|
20
|
+
* Even if an attacker breaks the PRNG used in every other system, these
|
|
21
|
+
* salts were never generated by one.
|
|
22
|
+
*/
|
|
23
|
+
async function quantumScrypt(password, options = {}, qrng) {
|
|
24
|
+
const params = { ...DEFAULT_PARAMS, ...options.scrypt };
|
|
25
|
+
const saltLen = options.saltBytes ?? 32;
|
|
26
|
+
const context = options.context ?? null;
|
|
27
|
+
// Resolve QRNG
|
|
28
|
+
let source;
|
|
29
|
+
let resolvedQrng;
|
|
30
|
+
if (qrng) {
|
|
31
|
+
resolvedQrng = qrng;
|
|
32
|
+
source = qrng instanceof anu_1.ANUQRNG ? 'anu-vacuum-fluctuations' : 'crypto-fallback';
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
try {
|
|
36
|
+
resolvedQrng = new anu_1.ANUQRNG();
|
|
37
|
+
await resolvedQrng.getBytes(1);
|
|
38
|
+
source = 'anu-vacuum-fluctuations';
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
resolvedQrng = new anu_1.CryptoQRNG();
|
|
42
|
+
source = 'crypto-fallback';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Fetch quantum-random salt
|
|
46
|
+
const saltBytes = await resolvedQrng.getBytes(saltLen);
|
|
47
|
+
const salt = Buffer.from(saltBytes);
|
|
48
|
+
// Mix in context if provided
|
|
49
|
+
const ikm = context
|
|
50
|
+
? Buffer.concat([Buffer.from(password), Buffer.from('\x00'), Buffer.from(context)])
|
|
51
|
+
: Buffer.from(password);
|
|
52
|
+
// Derive key via scrypt
|
|
53
|
+
const key = await scryptAsync(ikm, salt, params.keyLen, {
|
|
54
|
+
N: params.N,
|
|
55
|
+
r: params.r,
|
|
56
|
+
p: params.p,
|
|
57
|
+
maxmem: 128 * params.N * params.r * 2
|
|
58
|
+
});
|
|
59
|
+
const certificate = {
|
|
60
|
+
source,
|
|
61
|
+
fetchedAt: new Date().toISOString(),
|
|
62
|
+
entropyBytes: saltLen,
|
|
63
|
+
saltHash: sha256(salt),
|
|
64
|
+
keyHash: sha256(key),
|
|
65
|
+
scrypt: params,
|
|
66
|
+
context
|
|
67
|
+
};
|
|
68
|
+
return { key, salt, certificate };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verify that a password + salt re-derives to the expected key.
|
|
72
|
+
*/
|
|
73
|
+
async function verifyKey(password, salt, expectedKey, params = {}, context) {
|
|
74
|
+
const p = { ...DEFAULT_PARAMS, ...params };
|
|
75
|
+
const ikm = context
|
|
76
|
+
? Buffer.concat([Buffer.from(password), Buffer.from('\x00'), Buffer.from(context)])
|
|
77
|
+
: Buffer.from(password);
|
|
78
|
+
const derived = await scryptAsync(ikm, salt, p.keyLen, {
|
|
79
|
+
N: p.N, r: p.r, p: p.p,
|
|
80
|
+
maxmem: 128 * p.N * p.r * 2
|
|
81
|
+
});
|
|
82
|
+
// Constant-time comparison
|
|
83
|
+
return derived.length === expectedKey.length &&
|
|
84
|
+
derived.equals(expectedKey);
|
|
85
|
+
}
|
|
86
|
+
function sha256(buf) {
|
|
87
|
+
return (0, crypto_1.createHash)('sha256').update(buf).digest('hex');
|
|
88
|
+
}
|
|
89
|
+
/** Format a certificate as a human-readable string */
|
|
90
|
+
function formatCertificate(cert) {
|
|
91
|
+
const w = 52;
|
|
92
|
+
const line = '═'.repeat(w);
|
|
93
|
+
const row = (label, value) => ` ${label.padEnd(14)} ${value}`;
|
|
94
|
+
const sourceLabel = cert.source === 'anu-vacuum-fluctuations'
|
|
95
|
+
? 'ANU Quantum Vacuum (photon shot noise)'
|
|
96
|
+
: 'crypto.getRandomValues() [classical]';
|
|
97
|
+
return [
|
|
98
|
+
line,
|
|
99
|
+
' kwantumscrypt Key Certificate',
|
|
100
|
+
line,
|
|
101
|
+
row('Source:', sourceLabel),
|
|
102
|
+
row('Fetched:', cert.fetchedAt),
|
|
103
|
+
row('Entropy:', `${cert.entropyBytes} bytes (${cert.entropyBytes * 8} bits)`),
|
|
104
|
+
row('KDF:', 'scrypt'),
|
|
105
|
+
row('N / r / p:', `${cert.scrypt.N} / ${cert.scrypt.r} / ${cert.scrypt.p}`),
|
|
106
|
+
row('Key length:', `${cert.scrypt.keyLen} bytes (${cert.scrypt.keyLen * 8} bits)`),
|
|
107
|
+
cert.context ? row('Context:', cert.context) : null,
|
|
108
|
+
'',
|
|
109
|
+
row('Salt hash:', cert.saltHash.slice(0, 32) + '...'),
|
|
110
|
+
row('Key hash:', cert.keyHash.slice(0, 32) + '...'),
|
|
111
|
+
line,
|
|
112
|
+
].filter(l => l !== null).join('\n');
|
|
113
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuantumSource = exports.verify = exports.formatCert = exports.derive = void 0;
|
|
4
|
+
var kdf_1 = require("./kdf");
|
|
5
|
+
Object.defineProperty(exports, "derive", { enumerable: true, get: function () { return kdf_1.derive; } });
|
|
6
|
+
Object.defineProperty(exports, "formatCert", { enumerable: true, get: function () { return kdf_1.formatCert; } });
|
|
7
|
+
Object.defineProperty(exports, "verify", { enumerable: true, get: function () { return kdf_1.verify; } });
|
|
8
|
+
var qrng_1 = require("./qrng");
|
|
9
|
+
Object.defineProperty(exports, "QuantumSource", { enumerable: true, get: function () { return qrng_1.QuantumSource; } });
|
package/dist/kdf.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.derive = derive;
|
|
4
|
+
exports.verify = verify;
|
|
5
|
+
exports.formatCert = formatCert;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const qrng_1 = require("./qrng");
|
|
8
|
+
const scryptAsync = (password, salt, keyLen, opts) => new Promise((resolve, reject) => (0, node_crypto_1.scrypt)(password, salt, keyLen, opts, (err, key) => err ? reject(err) : resolve(key)));
|
|
9
|
+
/**
|
|
10
|
+
* Derive a cryptographic key from a password using scrypt with a
|
|
11
|
+
* quantum-random salt sourced from ANU vacuum fluctuations.
|
|
12
|
+
*/
|
|
13
|
+
async function derive(password, options = {}) {
|
|
14
|
+
const saltBytes = options.saltBytes ?? 32;
|
|
15
|
+
const keyLength = options.keyLength ?? 32;
|
|
16
|
+
const context = options.context ?? null;
|
|
17
|
+
const N = options.cost?.N ?? 16384;
|
|
18
|
+
const r = options.cost?.r ?? 8;
|
|
19
|
+
const p = options.cost?.p ?? 1;
|
|
20
|
+
const qs = options.source ?? (await qrng_1.QuantumSource.create());
|
|
21
|
+
const salt = await qs.bytes(saltBytes);
|
|
22
|
+
const ikm = context
|
|
23
|
+
? Buffer.concat([
|
|
24
|
+
Buffer.from(password),
|
|
25
|
+
Buffer.from('\x00'),
|
|
26
|
+
Buffer.from(context),
|
|
27
|
+
])
|
|
28
|
+
: Buffer.from(password);
|
|
29
|
+
const key = await scryptAsync(ikm, salt, keyLength, {
|
|
30
|
+
N,
|
|
31
|
+
r,
|
|
32
|
+
p,
|
|
33
|
+
maxmem: 128 * N * r * 2,
|
|
34
|
+
});
|
|
35
|
+
const certificate = {
|
|
36
|
+
source: qs.source,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
saltHash: sha256(salt),
|
|
39
|
+
keyHash: sha256(key),
|
|
40
|
+
params: { N, r, p, keyLength, saltBytes },
|
|
41
|
+
context,
|
|
42
|
+
};
|
|
43
|
+
return { key, salt, certificate };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Re-derive from password + salt and check against the expected key.
|
|
47
|
+
* Uses constant-time comparison.
|
|
48
|
+
*/
|
|
49
|
+
async function verify(password, salt, key, options = {}) {
|
|
50
|
+
const keyLength = options.keyLength ?? key.length;
|
|
51
|
+
const N = options.cost?.N ?? 16384;
|
|
52
|
+
const r = options.cost?.r ?? 8;
|
|
53
|
+
const p = options.cost?.p ?? 1;
|
|
54
|
+
const context = options.context ?? null;
|
|
55
|
+
const ikm = context
|
|
56
|
+
? Buffer.concat([
|
|
57
|
+
Buffer.from(password),
|
|
58
|
+
Buffer.from('\x00'),
|
|
59
|
+
Buffer.from(context),
|
|
60
|
+
])
|
|
61
|
+
: Buffer.from(password);
|
|
62
|
+
const derived = await scryptAsync(ikm, salt, keyLength, {
|
|
63
|
+
N,
|
|
64
|
+
r,
|
|
65
|
+
p,
|
|
66
|
+
maxmem: 128 * N * r * 2,
|
|
67
|
+
});
|
|
68
|
+
return derived.length === key.length && (0, node_crypto_1.timingSafeEqual)(derived, key);
|
|
69
|
+
}
|
|
70
|
+
/** Format a certificate as a human-readable block. */
|
|
71
|
+
function formatCert(cert) {
|
|
72
|
+
const w = 52;
|
|
73
|
+
const line = '═'.repeat(w);
|
|
74
|
+
const row = (label, value) => ` ${label.padEnd(14)} ${value}`;
|
|
75
|
+
const sourceLabel = cert.source === 'anu'
|
|
76
|
+
? 'ANU Quantum Vacuum (photon shot noise)'
|
|
77
|
+
: 'crypto.getRandomValues() [classical]';
|
|
78
|
+
return [
|
|
79
|
+
line,
|
|
80
|
+
' kdfts Key Certificate',
|
|
81
|
+
line,
|
|
82
|
+
row('Source:', sourceLabel),
|
|
83
|
+
row('Timestamp:', cert.timestamp),
|
|
84
|
+
row('KDF:', 'scrypt'),
|
|
85
|
+
row('N / r / p:', `${cert.params.N} / ${cert.params.r} / ${cert.params.p}`),
|
|
86
|
+
row('Key length:', `${cert.params.keyLength} bytes (${cert.params.keyLength * 8} bits)`),
|
|
87
|
+
row('Salt bytes:', `${cert.params.saltBytes} bytes (${cert.params.saltBytes * 8} bits)`),
|
|
88
|
+
cert.context ? row('Context:', cert.context) : null,
|
|
89
|
+
'',
|
|
90
|
+
row('Salt hash:', `${cert.saltHash.slice(0, 32)}...`),
|
|
91
|
+
row('Key hash:', `${cert.keyHash.slice(0, 32)}...`),
|
|
92
|
+
line,
|
|
93
|
+
]
|
|
94
|
+
.filter((l) => l !== null)
|
|
95
|
+
.join('\n');
|
|
96
|
+
}
|
|
97
|
+
function sha256(buf) {
|
|
98
|
+
return (0, node_crypto_1.createHash)('sha256').update(buf).digest('hex');
|
|
99
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KwantumScrypt = void 0;
|
|
4
|
+
const state_1 = require("../core/state");
|
|
5
|
+
const gates_1 = require("../core/gates");
|
|
6
|
+
const anu_1 = require("../qrng/anu");
|
|
7
|
+
const encoder_1 = require("./encoder");
|
|
8
|
+
/**
|
|
9
|
+
* KwantumScrypt — a meta language that IS a quantum computer.
|
|
10
|
+
*
|
|
11
|
+
* State vector simulation + true quantum randomness (ANU QRNG) for measurement.
|
|
12
|
+
* The randomness in every measurement comes from quantum vacuum fluctuations.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const ks = await KwantumScrypt.create(3) // 3-qubit register
|
|
16
|
+
* ks.h(0).cx(0,1).cx(0,2) // GHZ state
|
|
17
|
+
* const result = await ks.run() // measure with real quantum noise
|
|
18
|
+
*
|
|
19
|
+
* Bitstring notation:
|
|
20
|
+
* ks.bs('H|0⟩').bs('CX|0→1⟩').bs('M|*⟩')
|
|
21
|
+
*
|
|
22
|
+
* Binary KSIF encoding:
|
|
23
|
+
* const bits = ks.toKSIF() // encode as bitstring
|
|
24
|
+
* const ks2 = KwantumScrypt.fromKSIF(bits, 3) // decode
|
|
25
|
+
*/
|
|
26
|
+
class KwantumScrypt {
|
|
27
|
+
numQubits;
|
|
28
|
+
ops = [];
|
|
29
|
+
qrng;
|
|
30
|
+
qrngSource;
|
|
31
|
+
constructor(numQubits, qrng, source) {
|
|
32
|
+
if (numQubits < 1 || numQubits > 28) {
|
|
33
|
+
throw new RangeError(`numQubits must be 1–28 (got ${numQubits}). ` +
|
|
34
|
+
`At 28 qubits, state vector requires ~4 GB RAM.`);
|
|
35
|
+
}
|
|
36
|
+
this.numQubits = numQubits;
|
|
37
|
+
this.qrng = qrng;
|
|
38
|
+
this.qrngSource = source;
|
|
39
|
+
}
|
|
40
|
+
/** Create a KwantumScrypt instance, probing ANU QRNG first.
|
|
41
|
+
* @param apiKey ANU API key. Falls back to ANU_API_KEY env var, then crypto.
|
|
42
|
+
*/
|
|
43
|
+
static async create(numQubits, apiKey) {
|
|
44
|
+
try {
|
|
45
|
+
const qrng = new anu_1.ANUQRNG(apiKey);
|
|
46
|
+
await qrng.getBytes(1); // probe
|
|
47
|
+
console.log('[kwantumscrypt] Quantum source: ANU vacuum fluctuations ✓');
|
|
48
|
+
return new KwantumScrypt(numQubits, qrng, 'anu');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
console.warn('[kwantumscrypt] ANU QRNG unreachable — using crypto.getRandomValues()');
|
|
52
|
+
return new KwantumScrypt(numQubits, new anu_1.CryptoQRNG(), 'crypto');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Create with explicit QRNG (for testing) */
|
|
56
|
+
static withQRNG(numQubits, qrng, source) {
|
|
57
|
+
return new KwantumScrypt(numQubits, qrng, source);
|
|
58
|
+
}
|
|
59
|
+
// ── Fluent gate API ──────────────────────────────────────────────────────
|
|
60
|
+
h(target) { return this.push({ kind: 'H', target }); }
|
|
61
|
+
x(target) { return this.push({ kind: 'X', target }); }
|
|
62
|
+
y(target) { return this.push({ kind: 'Y', target }); }
|
|
63
|
+
z(target) { return this.push({ kind: 'Z', target }); }
|
|
64
|
+
s(target) { return this.push({ kind: 'S', target }); }
|
|
65
|
+
si(target) { return this.push({ kind: 'SI', target }); }
|
|
66
|
+
t(target) { return this.push({ kind: 'T', target }); }
|
|
67
|
+
ti(target) { return this.push({ kind: 'TI', target }); }
|
|
68
|
+
rx(angle, target) { return this.push({ kind: 'RX', target, angle }); }
|
|
69
|
+
ry(angle, target) { return this.push({ kind: 'RY', target, angle }); }
|
|
70
|
+
rz(angle, target) { return this.push({ kind: 'RZ', target, angle }); }
|
|
71
|
+
cx(control, target) { return this.push({ kind: 'CX', control, target }); }
|
|
72
|
+
swap(a, b) { return this.push({ kind: 'SWAP', a, b }); }
|
|
73
|
+
xx(angle, a, b) { return this.push({ kind: 'XX', a, b, angle }); }
|
|
74
|
+
gpi(phase, target) { return this.push({ kind: 'GPi', target, phase }); }
|
|
75
|
+
gpi2(phase, target) { return this.push({ kind: 'GPi2', target, phase }); }
|
|
76
|
+
/** Measure specific qubits. No args = measure all */
|
|
77
|
+
measure(...targets) {
|
|
78
|
+
if (targets.length === 0) {
|
|
79
|
+
for (let i = 0; i < this.numQubits; i++)
|
|
80
|
+
this.push({ kind: 'MEASURE', target: i });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
targets.forEach(t => this.push({ kind: 'MEASURE', target: t }));
|
|
84
|
+
}
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
// ── Quantum notation parser ───────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Parse quantum notation strings into circuit ops.
|
|
90
|
+
*
|
|
91
|
+
* Examples:
|
|
92
|
+
* ks.bs('H|0⟩') → H on qubit 0
|
|
93
|
+
* ks.bs('X|3⟩') → X on qubit 3
|
|
94
|
+
* ks.bs('CX|0→1⟩') → CNOT control=0 target=1
|
|
95
|
+
* ks.bs('SWAP|1↔2⟩') → SWAP qubits 1 and 2
|
|
96
|
+
* ks.bs('RX(π/2)|0⟩') → RX(π/2) on qubit 0
|
|
97
|
+
* ks.bs('M|*⟩') → measure all
|
|
98
|
+
* ks.bs('M|0,1,2⟩') → measure qubits 0,1,2
|
|
99
|
+
*/
|
|
100
|
+
bs(notation) {
|
|
101
|
+
const parsed = parseNotation(notation, this.numQubits);
|
|
102
|
+
parsed.forEach(op => this.push(op));
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
// ── Binary KSIF encoding ──────────────────────────────────────────────────
|
|
106
|
+
/** Encode the current circuit as a binary bitstring */
|
|
107
|
+
toKSIF() {
|
|
108
|
+
return (0, encoder_1.encode)(this.ops);
|
|
109
|
+
}
|
|
110
|
+
/** Load a circuit from a KSIF binary bitstring */
|
|
111
|
+
static fromKSIF(ksif, numQubits, qrng, source) {
|
|
112
|
+
const ks = new KwantumScrypt(numQubits, qrng, source);
|
|
113
|
+
ks.ops = (0, encoder_1.decode)(ksif);
|
|
114
|
+
return ks;
|
|
115
|
+
}
|
|
116
|
+
// ── Execution ─────────────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* Execute the circuit once.
|
|
119
|
+
* Returns measured bitstring + full superposition snapshot before measurement.
|
|
120
|
+
*/
|
|
121
|
+
async run(shots = 1) {
|
|
122
|
+
if (shots < 1)
|
|
123
|
+
throw new RangeError('shots must be >= 1');
|
|
124
|
+
// Build the state vector
|
|
125
|
+
const state = this.buildState();
|
|
126
|
+
const superposition = state.toBitstrings();
|
|
127
|
+
// Sample `shots` times
|
|
128
|
+
const counts = {};
|
|
129
|
+
let lastBits = '0'.repeat(this.numQubits);
|
|
130
|
+
for (let s = 0; s < shots; s++) {
|
|
131
|
+
const rand = await this.qrng.getFloat();
|
|
132
|
+
lastBits = sampleBorn(superposition, rand);
|
|
133
|
+
counts[lastBits] = (counts[lastBits] ?? 0) + 1;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
bitstring: lastBits,
|
|
137
|
+
superposition,
|
|
138
|
+
counts,
|
|
139
|
+
qrngSource: this.qrngSource
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Inspect the state vector without measuring */
|
|
143
|
+
inspect() {
|
|
144
|
+
return this.buildState();
|
|
145
|
+
}
|
|
146
|
+
/** Reset all gates */
|
|
147
|
+
reset() {
|
|
148
|
+
this.ops = [];
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
// ── Internals ─────────────────────────────────────────────────────────────
|
|
152
|
+
push(op) {
|
|
153
|
+
this.validate(op);
|
|
154
|
+
this.ops.push(op);
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
validate(op) {
|
|
158
|
+
const q = (n) => {
|
|
159
|
+
if (n < 0 || n >= this.numQubits)
|
|
160
|
+
throw new RangeError(`Qubit ${n} out of range (circuit has ${this.numQubits} qubits)`);
|
|
161
|
+
};
|
|
162
|
+
switch (op.kind) {
|
|
163
|
+
case 'H':
|
|
164
|
+
case 'X':
|
|
165
|
+
case 'Y':
|
|
166
|
+
case 'Z':
|
|
167
|
+
case 'S':
|
|
168
|
+
case 'SI':
|
|
169
|
+
case 'T':
|
|
170
|
+
case 'TI':
|
|
171
|
+
case 'RX':
|
|
172
|
+
case 'RY':
|
|
173
|
+
case 'RZ':
|
|
174
|
+
case 'GPi':
|
|
175
|
+
case 'GPi2':
|
|
176
|
+
case 'MEASURE':
|
|
177
|
+
q(op.target);
|
|
178
|
+
break;
|
|
179
|
+
case 'CX':
|
|
180
|
+
q(op.control);
|
|
181
|
+
q(op.target);
|
|
182
|
+
break;
|
|
183
|
+
case 'SWAP':
|
|
184
|
+
q(op.a);
|
|
185
|
+
q(op.b);
|
|
186
|
+
break;
|
|
187
|
+
case 'XX':
|
|
188
|
+
q(op.a);
|
|
189
|
+
q(op.b);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
buildState() {
|
|
194
|
+
const state = new state_1.StateVector(this.numQubits);
|
|
195
|
+
for (const op of this.ops) {
|
|
196
|
+
if (op.kind === 'MEASURE')
|
|
197
|
+
continue; // measure after all unitary ops
|
|
198
|
+
applyOp(state, op);
|
|
199
|
+
}
|
|
200
|
+
return state;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.KwantumScrypt = KwantumScrypt;
|
|
204
|
+
// ── Apply a single op to a state vector ──────────────────────────────────────
|
|
205
|
+
function applyOp(state, op) {
|
|
206
|
+
switch (op.kind) {
|
|
207
|
+
case 'H':
|
|
208
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.H);
|
|
209
|
+
break;
|
|
210
|
+
case 'X':
|
|
211
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.X);
|
|
212
|
+
break;
|
|
213
|
+
case 'Y':
|
|
214
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.Y);
|
|
215
|
+
break;
|
|
216
|
+
case 'Z':
|
|
217
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.Z);
|
|
218
|
+
break;
|
|
219
|
+
case 'S':
|
|
220
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.S);
|
|
221
|
+
break;
|
|
222
|
+
case 'SI':
|
|
223
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.SI);
|
|
224
|
+
break;
|
|
225
|
+
case 'T':
|
|
226
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.T);
|
|
227
|
+
break;
|
|
228
|
+
case 'TI':
|
|
229
|
+
(0, gates_1.applySingleQubit)(state, op.target, gates_1.GATE_MATRICES.TI);
|
|
230
|
+
break;
|
|
231
|
+
case 'RX':
|
|
232
|
+
(0, gates_1.applySingleQubit)(state, op.target, (0, gates_1.rx)(op.angle));
|
|
233
|
+
break;
|
|
234
|
+
case 'RY':
|
|
235
|
+
(0, gates_1.applySingleQubit)(state, op.target, (0, gates_1.ry)(op.angle));
|
|
236
|
+
break;
|
|
237
|
+
case 'RZ':
|
|
238
|
+
(0, gates_1.applySingleQubit)(state, op.target, (0, gates_1.rz)(op.angle));
|
|
239
|
+
break;
|
|
240
|
+
case 'CX':
|
|
241
|
+
(0, gates_1.applyCX)(state, op.control, op.target);
|
|
242
|
+
break;
|
|
243
|
+
case 'SWAP':
|
|
244
|
+
(0, gates_1.applySWAP)(state, op.a, op.b);
|
|
245
|
+
break;
|
|
246
|
+
case 'XX':
|
|
247
|
+
(0, gates_1.applyXX)(state, op.a, op.b, op.angle);
|
|
248
|
+
break;
|
|
249
|
+
case 'GPi':
|
|
250
|
+
(0, gates_1.applyGPi)(state, op.target, op.phase);
|
|
251
|
+
break;
|
|
252
|
+
case 'GPi2':
|
|
253
|
+
(0, gates_1.applyGPi2)(state, op.target, op.phase);
|
|
254
|
+
break;
|
|
255
|
+
case 'MEASURE': break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// ── Born-rule sampler ─────────────────────────────────────────────────────────
|
|
259
|
+
function sampleBorn(probs, rand) {
|
|
260
|
+
let cumulative = 0;
|
|
261
|
+
for (const [bits, p] of Object.entries(probs)) {
|
|
262
|
+
cumulative += p;
|
|
263
|
+
if (rand < cumulative)
|
|
264
|
+
return bits;
|
|
265
|
+
}
|
|
266
|
+
// Floating-point edge case: return last entry
|
|
267
|
+
return Object.keys(probs).at(-1);
|
|
268
|
+
}
|
|
269
|
+
// ── Quantum notation parser ───────────────────────────────────────────────────
|
|
270
|
+
const PI = Math.PI;
|
|
271
|
+
function parseNotation(notation, numQubits) {
|
|
272
|
+
const s = notation.trim();
|
|
273
|
+
// M|*⟩ or MEASURE|*⟩
|
|
274
|
+
if (/^M(EASURE)?\|\*[⟩>]?$/i.test(s)) {
|
|
275
|
+
return Array.from({ length: numQubits }, (_, i) => ({ kind: 'MEASURE', target: i }));
|
|
276
|
+
}
|
|
277
|
+
// M|0,1,2⟩
|
|
278
|
+
const measureMatch = s.match(/^M(EASURE)?\|([0-9,\s]+)[⟩>]?$/i);
|
|
279
|
+
if (measureMatch) {
|
|
280
|
+
return measureMatch[2].split(',').map(n => ({ kind: 'MEASURE', target: parseInt(n.trim()) }));
|
|
281
|
+
}
|
|
282
|
+
// CX|ctrl→tgt⟩
|
|
283
|
+
const cxMatch = s.match(/^CX\|(\d+)[→>](\d+)[⟩>]?$/i);
|
|
284
|
+
if (cxMatch) {
|
|
285
|
+
return [{ kind: 'CX', control: parseInt(cxMatch[1]), target: parseInt(cxMatch[2]) }];
|
|
286
|
+
}
|
|
287
|
+
// SWAP|a↔b⟩
|
|
288
|
+
const swapMatch = s.match(/^SWAP\|(\d+)[↔<>](\d+)[⟩>]?$/i);
|
|
289
|
+
if (swapMatch) {
|
|
290
|
+
return [{ kind: 'SWAP', a: parseInt(swapMatch[1]), b: parseInt(swapMatch[2]) }];
|
|
291
|
+
}
|
|
292
|
+
// XX(θ)|a,b⟩
|
|
293
|
+
const xxMatch = s.match(/^XX\(([^)]+)\)\|(\d+),(\d+)[⟩>]?$/i);
|
|
294
|
+
if (xxMatch) {
|
|
295
|
+
return [{ kind: 'XX', a: parseInt(xxMatch[2]), b: parseInt(xxMatch[3]), angle: parseAngle(xxMatch[1]) }];
|
|
296
|
+
}
|
|
297
|
+
// RX(θ)|q⟩ RY(θ)|q⟩ RZ(θ)|q⟩
|
|
298
|
+
const rotMatch = s.match(/^(RX|RY|RZ)\(([^)]+)\)\|(\d+)[⟩>]?$/i);
|
|
299
|
+
if (rotMatch) {
|
|
300
|
+
const kind = rotMatch[1].toUpperCase();
|
|
301
|
+
return [{ kind, target: parseInt(rotMatch[3]), angle: parseAngle(rotMatch[2]) }];
|
|
302
|
+
}
|
|
303
|
+
// GPi(φ)|q⟩ GPi2(φ)|q⟩
|
|
304
|
+
const gpiMatch = s.match(/^(GPi2?)\(([^)]+)\)\|(\d+)[⟩>]?$/i);
|
|
305
|
+
if (gpiMatch) {
|
|
306
|
+
const kind = gpiMatch[1].toLowerCase() === 'gpi2' ? 'GPi2' : 'GPi';
|
|
307
|
+
return [{ kind, target: parseInt(gpiMatch[3]), phase: parseAngle(gpiMatch[2]) }];
|
|
308
|
+
}
|
|
309
|
+
// H|q⟩ X|q⟩ Y|q⟩ Z|q⟩ S|q⟩ T|q⟩
|
|
310
|
+
const singleMatch = s.match(/^(H|X|Y|Z|S|SI|T|TI)\|(\d+)[⟩>]?$/i);
|
|
311
|
+
if (singleMatch) {
|
|
312
|
+
const kind = singleMatch[1].toUpperCase();
|
|
313
|
+
return [{ kind, target: parseInt(singleMatch[2]) }];
|
|
314
|
+
}
|
|
315
|
+
throw new SyntaxError(`Cannot parse quantum notation: "${notation}"`);
|
|
316
|
+
}
|
|
317
|
+
function parseAngle(expr) {
|
|
318
|
+
const s = expr.trim().replace(/\s/g, '');
|
|
319
|
+
if (s === 'π' || s === 'pi')
|
|
320
|
+
return PI;
|
|
321
|
+
if (s === 'π/2' || s === 'pi/2')
|
|
322
|
+
return PI / 2;
|
|
323
|
+
if (s === 'π/4' || s === 'pi/4')
|
|
324
|
+
return PI / 4;
|
|
325
|
+
if (s === '2π' || s === '2pi')
|
|
326
|
+
return 2 * PI;
|
|
327
|
+
if (s === '3π/2' || s === '3pi/2')
|
|
328
|
+
return 3 * PI / 2;
|
|
329
|
+
return parseFloat(s.replace('π', String(PI)).replace('pi', String(PI)));
|
|
330
|
+
}
|