quantumos-host-tools 0.4.0__py3-none-any.whl

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.
qbraid_boot.py ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ QuantumOS qBraid boot harness.
4
+
5
+ Draws quantum-random bytes, hands them to the QuantumOS kernel as a
6
+ `qseed=<hex>` boot command line, launches the kernel under QEMU, and
7
+ streams the serial console. On a qBraid lab machine the entropy comes
8
+ from a real QPU (or the free qBraid simulator); off-lab it falls back
9
+ to the OS CSPRNG so the harness is testable anywhere.
10
+
11
+ The kernel mixes the seed into its quantum resource manager's PRNG at
12
+ boot and echoes it back on serial, so a round-trip can be verified:
13
+
14
+ [BOOT] Boot entropy accepted from cmdline (qseed=)
15
+ [BOOT] qseed value:
16
+ <the same 64-bit value the harness sent>
17
+
18
+ Usage:
19
+ python3 scripts/qbraid_boot.py # local entropy
20
+ python3 scripts/qbraid_boot.py --backend qbraid-sim # qBraid simulator
21
+ python3 scripts/qbraid_boot.py --backend <device> # real QPU on qBraid
22
+ python3 scripts/qbraid_boot.py --exit-on-ready # quit at "QuantumOS ready"
23
+
24
+ SPDX-License-Identifier: GPL-2.0-only
25
+ """
26
+
27
+ import argparse
28
+ import os
29
+ import subprocess
30
+ import sys
31
+
32
+ SEED_BYTES = 8 # 64-bit qseed
33
+
34
+
35
+ def draw_entropy_local(nbytes):
36
+ """OS CSPRNG fallback (not quantum, but unblocks off-lab testing)."""
37
+ return os.urandom(nbytes), "local-csprng"
38
+
39
+
40
+ def draw_entropy_quantum(nbytes, backend):
41
+ """Draw `nbytes` of quantum-random bytes on qBraid.
42
+
43
+ Runs a one-qubit Hadamard-and-measure circuit repeatedly; each shot
44
+ yields one unbiased bit. Requires the qBraid runtime (available in a
45
+ qBraid lab) and, for a real device, that `backend` names an
46
+ accessible QPU. Raises on any failure so the caller can fall back.
47
+ """
48
+ from qbraid.runtime import QbraidProvider # qBraid lab environment
49
+
50
+ # A qBraid device id, or the free simulator by default
51
+ device_id = "qbraid_qir_simulator" if backend == "qbraid-sim" else backend
52
+
53
+ # One-qubit H + measure, drawn as an OpenQASM 2 program so we do not
54
+ # hard-depend on any single circuit SDK being importable.
55
+ qasm = (
56
+ "OPENQASM 2.0;\n"
57
+ 'include "qelib1.inc";\n'
58
+ "qreg q[1];\ncreg c[1];\n"
59
+ "h q[0];\nmeasure q[0] -> c[0];\n"
60
+ )
61
+
62
+ provider = QbraidProvider()
63
+ device = provider.get_device(device_id)
64
+
65
+ bits = []
66
+ need = nbytes * 8
67
+ # Batch the shots; von Neumann debiasing is unnecessary for H on an
68
+ # ideal simulator, and real-device bias is mixed out by the kernel's
69
+ # SplitMix avalanche, but we still collect raw measured bits.
70
+ while len(bits) < need:
71
+ shots = min(1024, need - len(bits))
72
+ job = device.run(qasm, shots=shots)
73
+ result = job.result()
74
+ counts = result.data.get_counts() if hasattr(result, "data") else result.measurement_counts()
75
+ # Expand counts back into a bit stream
76
+ for outcome, n in counts.items():
77
+ b = int(str(outcome)[-1])
78
+ bits.extend([b] * n)
79
+ bits = bits[:need]
80
+
81
+ out = bytearray(nbytes)
82
+ for i, b in enumerate(bits):
83
+ out[i // 8] |= (b & 1) << (i % 8)
84
+ return bytes(out), f"qbraid:{device_id}"
85
+
86
+
87
+ def draw_entropy(nbytes, backend):
88
+ if backend == "local":
89
+ return draw_entropy_local(nbytes)
90
+ try:
91
+ return draw_entropy_quantum(nbytes, backend)
92
+ except Exception as exc: # noqa: BLE001 — any failure => fall back
93
+ print(f"[harness] quantum entropy unavailable ({exc!r}); "
94
+ f"falling back to local CSPRNG", file=sys.stderr)
95
+ return draw_entropy_local(nbytes)
96
+
97
+
98
+ def main():
99
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
100
+ default_kernel = os.path.join(repo_root, "build", "x86_64", "kernel.elf32")
101
+
102
+ ap = argparse.ArgumentParser(description="QuantumOS qBraid boot harness")
103
+ ap.add_argument("--backend", default="local",
104
+ help="entropy source: 'local', 'qbraid-sim', or a QPU device id")
105
+ ap.add_argument("--kernel", default=default_kernel,
106
+ help="path to the ELF32 kernel image")
107
+ ap.add_argument("--qemu", default="qemu-system-x86_64")
108
+ ap.add_argument("--memory", default="128M")
109
+ ap.add_argument("--timeout", type=int, default=0,
110
+ help="seconds before killing QEMU (0 = run until it exits)")
111
+ ap.add_argument("--exit-on-ready", action="store_true",
112
+ help="stop once the kernel prints 'QuantumOS ready'")
113
+ args = ap.parse_args()
114
+
115
+ if not os.path.exists(args.kernel):
116
+ sys.exit(f"kernel image not found: {args.kernel}\n"
117
+ f"build it first: make")
118
+
119
+ seed_bytes, source = draw_entropy(SEED_BYTES, args.backend)
120
+ seed_hex = seed_bytes.hex()
121
+ # Kernel reads qseed as a big-endian hex u64 (high nibble first)
122
+ seed_be = int.from_bytes(seed_bytes, "big")
123
+
124
+ print(f"[harness] entropy source : {source}")
125
+ print(f"[harness] qseed (64-bit) : 0x{seed_be:016x}")
126
+ print(f"[harness] booting {os.path.basename(args.kernel)} under QEMU\n")
127
+
128
+ cmd = [
129
+ args.qemu,
130
+ "-kernel", args.kernel,
131
+ "-append", f"qseed={seed_hex}",
132
+ "-m", args.memory,
133
+ "-serial", "stdio",
134
+ "-display", "none",
135
+ "-no-reboot",
136
+ ]
137
+
138
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
139
+ stderr=subprocess.STDOUT, text=True,
140
+ bufsize=1)
141
+ saw_seed = False
142
+ try:
143
+ for line in proc.stdout:
144
+ sys.stdout.write(line)
145
+ sys.stdout.flush()
146
+ if "qseed value" in line:
147
+ saw_seed = True
148
+ if args.exit_on_ready and "QuantumOS ready" in line:
149
+ proc.terminate()
150
+ break
151
+ except KeyboardInterrupt:
152
+ proc.terminate()
153
+ finally:
154
+ try:
155
+ proc.wait(timeout=args.timeout or None)
156
+ except subprocess.TimeoutExpired:
157
+ proc.terminate()
158
+
159
+ print(f"\n[harness] entropy handoff {'CONFIRMED' if saw_seed else 'not observed'} "
160
+ f"(sent 0x{seed_be:016x})")
161
+
162
+
163
+ if __name__ == "__main__":
164
+ main()