-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquantum-signature.ts
More file actions
334 lines (294 loc) · 11.4 KB
/
Copy pathquantum-signature.ts
File metadata and controls
334 lines (294 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
import crypto from "crypto";
import {
BraketClient,
CreateQuantumTaskCommand,
GetQuantumTaskCommand,
} from "@aws-sdk/client-braket";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const region = process.env.AWS_REGION_NAME || process.env.AWS_REGION || "us-west-2";
const braket = new BraketClient({ region });
const s3 = new S3Client({ region });
// Two independent simulator sources for the two-source QRNG (see
// docs/paper/quantum-rng-implementation.md). SV1 is the ideal state-vector
// simulator; DM1 is the density-matrix simulator carrying injected noise, used
// as the "weak" second source the Toeplitz extractor is designed to condense.
const SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1";
const DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1";
const OUTPUT_BUCKET = process.env.BRAKET_BUCKET || "";
const OUTPUT_PREFIX = "braket-results";
// Two-source extractor parameters (notebook: Randomness_Generation.ipynb).
const EPS = 1e-8; // security parameter
const K_RATE = 0.72; // conservative per-source min-entropy rate
const OUTPUT_BYTES = 36; // 4 bytes -> quantumNumber, 32 bytes -> nonce r
const OUTPUT_BITS = OUTPUT_BYTES * 8; // m = 288
export interface QuantumSignature {
quantumNumber: number;
publicKeyHash: string;
signature: string;
nonce: string; // hex of the 32 fresh random bytes r
bellState: [number, number, number, number];
algorithm: string;
visualColor: string;
device: string;
}
// ---------------------------------------------------------------------------
// Circuits
// ---------------------------------------------------------------------------
// Single-qubit Hadamard source: one quantum-random bit per shot (notebook cell-7).
function buildHadamardCircuit(): string {
return [
"OPENQASM 3.0;",
"qubit[1] q;",
"bit[1] c;",
"h q[0];",
"c[0] = measure q[0];",
].join("\n");
}
// Same Hadamard source, but on DM1 with injected noise so it acts as a genuine
// *weak* randomness source (depolarizing channel + amplitude damping toward
// |0>, modelling readout-ground bias). The extractor is built to tolerate this.
function buildNoisyHadamardCircuit(): string {
return [
"OPENQASM 3.0;",
"qubit[1] q;",
"bit[1] c;",
"h q[0];",
"#pragma braket noise depolarizing(0.02) q[0]",
"#pragma braket noise amplitude_damping(0.04) q[0]",
"c[0] = measure q[0];",
].join("\n");
}
// 2-qubit Bell state |Φ+> = (|00> + |11>)/sqrt(2) — structural witness, unchanged.
function buildBellCircuit(): string {
return [
"OPENQASM 3.0;",
"qubit[2] q;",
"bit[2] c;",
"h q[0];",
"cnot q[0], q[1];",
"c[0] = measure q[0];",
"c[1] = measure q[1];",
].join("\n");
}
// ---------------------------------------------------------------------------
// Braket execution
// ---------------------------------------------------------------------------
// Submit an OpenQASM circuit to a device, wait for completion, and return the
// parsed results JSON from S3.
async function submitAndFetch(
deviceArn: string,
openQasm: string,
shots: number
): Promise<any> {
const action = JSON.stringify({
braketSchemaHeader: { name: "braket.ir.openqasm.program", version: "1" },
source: openQasm,
});
const taskRes = await braket.send(
new CreateQuantumTaskCommand({
deviceArn,
action,
shots,
outputS3Bucket: OUTPUT_BUCKET,
outputS3KeyPrefix: OUTPUT_PREFIX,
})
);
const taskArn = taskRes.quantumTaskArn!;
console.log(`[braket] Task created on ${deviceArn.split("/").pop()}: ${taskArn}`);
let status = "";
let outputDir = "";
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 1000));
const check = await braket.send(
new GetQuantumTaskCommand({ quantumTaskArn: taskArn })
);
status = check.status || "";
if (status === "COMPLETED") {
outputDir = check.outputS3Directory || "";
break;
}
if (status === "FAILED" || status === "CANCELLED") {
throw new Error(`Braket task ${status}: ${check.failureReason || "unknown"}`);
}
}
if (status !== "COMPLETED") {
throw new Error(`Braket task timed out after 30s, status: ${status}`);
}
const check = await braket.send(
new GetQuantumTaskCommand({ quantumTaskArn: taskArn })
);
const bucket = check.outputS3Bucket || OUTPUT_BUCKET;
const key = `${outputDir}/results.json`;
const obj = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const body = await obj.Body!.transformToString();
return JSON.parse(body);
}
// Run a single-qubit source circuit and return the raw per-shot bit stream.
async function runForBits(
deviceArn: string,
openQasm: string,
shots: number
): Promise<number[]> {
const results = await submitAndFetch(deviceArn, openQasm, shots);
// Raw per-shot measurements: array of per-shot bit arrays (one bit each here).
if (Array.isArray(results.measurements) && results.measurements.length > 0) {
return results.measurements.map((m: number[]) => Number(m[0]) & 1);
}
throw new Error("No raw per-shot measurements returned (cannot harvest quantum bits)");
}
// Run the Bell circuit and return measurement counts.
async function runForCounts(
deviceArn: string,
openQasm: string,
shots: number
): Promise<Record<string, number>> {
const results = await submitAndFetch(deviceArn, openQasm, shots);
const counts: Record<string, number> = {};
if (results.measurementProbabilities) {
for (const [state, prob] of Object.entries(results.measurementProbabilities)) {
counts[state] = Math.round((prob as number) * shots);
}
} else if (Array.isArray(results.measurements)) {
for (const m of results.measurements) {
const k = m.join("");
counts[k] = (counts[k] || 0) + 1;
}
}
return counts;
}
// ---------------------------------------------------------------------------
// Toeplitz two-source extractor (notebook cell-11), direct O(m·(n-m)) mod-2.
// Ext(x, y) = x · (T(y) | I_m)^T (mod 2), with x ∈ {0,1}^n, y ∈ {0,1}^{n-1}.
// ---------------------------------------------------------------------------
// Required raw input length per source for `m` output bits at security `eps`.
export function requiredInputLength(m: number, eps: number, k: number): number {
return Math.floor((m - 1 - 2 * Math.log2(eps)) / (k + k - 1));
}
export function toeplitzExtract(x: number[], y: number[], m: number): number[] {
const n = x.length;
if (y.length < n - 1) throw new Error(`y too short: need ${n - 1}, got ${y.length}`);
if (n < 2 * m) throw new Error(`n (${n}) must be >= 2m (${2 * m}) for this construction`);
// y is indexed d ∈ [-(m-1), n-m-1]; store as Y[k] with k = d + (m-1).
const out: number[] = new Array(m).fill(0);
const cols = n - m; // number of Toeplitz columns
for (let i = 0; i < m; i++) {
let acc = 0;
for (let j = 0; j < cols; j++) {
const k = j - i + (m - 1); // index into y
acc ^= (x[j] & y[k]);
}
// Identity block: i-th column selects x[(n-m)+i].
acc ^= x[cols + i];
out[i] = acc & 1;
}
return out;
}
// Pack a bit array (MSB-first) into a Buffer of ceil(bits/8) bytes.
function bitsToBuffer(bits: number[]): Buffer {
const buf = Buffer.alloc(Math.ceil(bits.length / 8));
for (let i = 0; i < bits.length; i++) {
if (bits[i] & 1) buf[i >> 3] |= 0x80 >> (i & 7);
}
return buf;
}
// ---------------------------------------------------------------------------
// ToyLWE signature (educational PQC stand-in) seeded by the quantum nonce r.
// ---------------------------------------------------------------------------
function shake256(data: Buffer, length: number): Buffer {
return crypto.createHash("shake256", { outputLength: length }).update(data).digest();
}
// 𝒮 = SHAKE-256(username ‖ quantumNumber ‖ r); pkHash = SHA-256(𝒮[0:32])[0:12];
// 𝒢 = base64(SHA-256(H_msg : H_ent : pkHash))[0:24].
function toyLweSign(
username: string,
message: string,
quantumNumber: number,
r: Buffer
): { publicKeyHash: string; signature: string } {
const sigMaterial = shake256(
Buffer.concat([
Buffer.from(username, "utf8"),
Buffer.from("|", "utf8"),
Buffer.from(String(quantumNumber), "utf8"),
Buffer.from("|", "utf8"),
r,
]),
64
);
const pkHash = crypto
.createHash("sha256")
.update(sigMaterial.subarray(0, 32))
.digest("hex")
.substring(0, 12)
.toUpperCase();
const msgHash = crypto.createHash("sha256").update(message).digest("hex");
const entropyHash = crypto.createHash("sha256").update(String(quantumNumber)).digest("hex");
const sigHash = crypto
.createHash("sha256")
.update(`${msgHash}:${entropyHash}:${pkHash}`)
.digest("hex");
const signature = Buffer.from(sigHash).toString("base64").substring(0, 24);
return { publicKeyHash: pkHash, signature };
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function generateQuantumSignature(
username: string,
messageText: string
): Promise<QuantumSignature> {
let quantumNumber: number;
let r: Buffer;
let bellState: [number, number, number, number];
let device = "SV1+DM1";
let algorithm = "ToyLWE-2Source-Toeplitz";
try {
// Two-source QRNG: harvest per-shot bit streams from two independent
// simulators, then condense with the Toeplitz two-source extractor.
const n = requiredInputLength(OUTPUT_BITS, EPS, K_RATE);
const [sourceA, sourceB, bellCounts] = await Promise.all([
runForBits(SV1_ARN, buildHadamardCircuit(), n), // ideal source x
runForBits(DM1_ARN, buildNoisyHadamardCircuit(), n), // weak/noisy source y
runForCounts(SV1_ARN, buildBellCircuit(), 200), // structural witness
]);
const outBits = toeplitzExtract(sourceA, sourceB, OUTPUT_BITS);
const Q = bitsToBuffer(outBits); // 36 bytes
quantumNumber = Q.readUInt32BE(0) % 1001;
r = Buffer.from(Q.subarray(4, 36)); // 32 fresh quantum-random bytes
const totalShots = Object.values(bellCounts).reduce((a, b) => a + b, 0) || 1;
bellState = [
(bellCounts["00"] || 0) / totalShots,
(bellCounts["01"] || 0) / totalShots,
(bellCounts["10"] || 0) / totalShots,
(bellCounts["11"] || 0) / totalShots,
];
console.log(
`[braket] Two-source QRNG: n=${n}/source, quantumNumber=${quantumNumber}, bell=${JSON.stringify(bellState)}`
);
} catch (err) {
console.error("[braket] QRNG failed, falling back to local CSPRNG:", err);
device = "local-fallback";
algorithm = "ToyLWE-local-fallback";
// Honest fallback: fresh OS-random entropy (NOT quantum, and NOT derived
// from message content). Tagged so the admin dashboard can distinguish it.
quantumNumber = crypto.randomBytes(4).readUInt32BE(0) % 1001;
r = crypto.randomBytes(32);
bellState = [0.5, 0.0, 0.0, 0.5];
}
const { publicKeyHash, signature } = toyLweSign(username, messageText, quantumNumber, r);
// Visual color from quantum number + Bell-state probabilities.
const hue = (quantumNumber * 137.5) % 360;
const sat = 70 + bellState[0] * 30;
const light = 45 + bellState[3] * 20;
const visualColor = `hsl(${hue.toFixed(0)}, ${sat.toFixed(0)}%, ${light.toFixed(0)}%)`;
return {
quantumNumber,
publicKeyHash,
signature,
nonce: r.toString("hex"),
bellState,
algorithm,
visualColor,
device,
};
}