Skip to main content

Python SDK for the QCicada QRNG (Crypta Labs) — macOS-first, works on Linux too

Project description

qcicada

Rust and Python SDK for the QCicada quantum random number generator by Crypta Labs.

macOS-first — fixes FTDI serial driver issues that break the official SDK. Works on Linux too.

Install

Python

pip install qcicada

Rust

[dependencies]
qcicada = "0.1"

Quick Start

PythonRust
from qcicada import QCicada

with QCicada() as qrng:
    print(qrng.random(32).hex())
use qcicada::QCicada;

let mut qrng = QCicada::open(None, None)?;
let bytes = qrng.random(32)?;
println!("{:02x?}", bytes);

The device is auto-detected. If you have multiple USB-serial devices, pass the port explicitly:

QCicada(port="/dev/cu.usbserial-DK0HFP4T")      # Python
QCicada::open(Some("/dev/cu.usbserial-DK0HFP4T"), None)?;  // Rust

Device Discovery

PythonRust
from qcicada import (
    find_devices,
    discover_devices,
    open_by_serial,
)

# Fast port scan (no device I/O)
find_devices()
# ['/dev/cu.usbserial-DK0HFP4T']

# Probe and verify each device
for dev in discover_devices():
    print(dev.port, dev.info.serial)

# Open by serial number
qrng = open_by_serial("QC0000000217")
use qcicada::{
    find_devices,
    discover_devices,
    open_by_serial,
};

// Fast port scan (no device I/O)
let ports = find_devices();

// Probe and verify each device
for dev in discover_devices() {
    println!("{} {}", dev.port, dev.info.serial);
}

// Open by serial number
let mut qrng = open_by_serial("QC0000000217")?;

Entropy Modes

The QCicada supports three post-processing modes:

Mode What you get
SHA256 (default) NIST SP 800-90B conditioned output — use for cryptography
Raw Noise After health-test conditioning — use for entropy research
Raw Samples Unprocessed samples from the quantum optical module
PythonRust
from qcicada import QCicada, PostProcess

with QCicada() as qrng:
    qrng.random(32)  # SHA256 (default)

    qrng.set_postprocess(PostProcess.RAW_NOISE)
    qrng.random(32)

    qrng.set_postprocess(PostProcess.RAW_SAMPLES)
    qrng.random(32)
use qcicada::{QCicada, PostProcess};

let mut qrng = QCicada::open(None, None)?;
qrng.random(32)?;  // SHA256 (default)

qrng.set_postprocess(PostProcess::RawNoise)?;
qrng.random(32)?;

qrng.set_postprocess(PostProcess::RawSamples)?;
qrng.random(32)?;

Signed Reads

Random bytes with a 64-byte cryptographic signature from the device's internal key. Requires firmware 5.13+.

PythonRust
result = qrng.signed_read(32)
result.data       # 32 random bytes
result.signature  # 64-byte signature
let result = qrng.signed_read(32)?;
result.data       // 32 random bytes
result.signature  // 64-byte signature

Certificate Verification

Verify the device's identity using its ECDSA P-256 certificate chain. The device holds an internal keypair; a Certificate Authority (CA) signs the device's public key along with its hardware version and serial number.

PythonRust
# CA public key (64 bytes, from Crypta Labs)
ca_pub_key = bytes.fromhex("...")

# Verify device and get its public key
dev_pub = qrng.get_verified_pub_key(ca_pub_key)

# Signed read with signature verification
result = qrng.signed_read_verified(32, dev_pub)
result.data       # 32 verified random bytes
result.signature  # 64-byte signature
// CA public key (64 bytes, from Crypta Labs)
let ca_pub_key = hex::decode("...").unwrap();

// Verify device and get its public key
let dev_pub = qrng.get_verified_pub_key(&ca_pub_key)?;

// Signed read with signature verification
let result = qrng.signed_read_verified(32, &dev_pub)?;
result.data       // 32 verified random bytes
result.signature  // 64-byte signature

You can also access the raw primitives directly:

PythonRust
pub_key = qrng.get_dev_pub_key()      # 64 bytes
cert = qrng.get_dev_certificate()     # 64 bytes

from qcicada import verify_certificate
valid = verify_certificate(
    ca_pub_key, pub_key, cert,
    hw_major=1, hw_minor=1, serial_int=217,
)
let pub_key = qrng.get_dev_pub_key()?;   // 64 bytes
let cert = qrng.get_dev_certificate()?;  // 64 bytes

use qcicada::crypto::verify_certificate;
let valid = verify_certificate(
    &ca_pub_key, &pub_key, &cert, 1, 1, 217,
)?;

Continuous Mode

High-throughput streaming with no per-request overhead:

PythonRust
qrng.start_continuous()
for _ in range(100):
    chunk = qrng.read_continuous(1024)
qrng.stop()
qrng.start_continuous()?;
for _ in 0..100 {
    let chunk = qrng.read_continuous(1024)?;
}
qrng.stop()?;

If you care about the first continuous read being as fresh as possible after entering continuous mode, use the fresh-start helper. It starts continuous mode and drains any already-buffered input bytes once:

PythonRust
drained = qrng.start_continuous_fresh()
chunk = qrng.read_continuous(1024)
qrng.stop()
let drained = qrng.start_continuous_fresh()?;
let chunk = qrng.read_continuous(1024)?;
qrng.stop()?;

Device Info & Status

PythonRust
info = qrng.get_info()
# serial, fw_version, core_version, hw_info

status = qrng.get_status()
# initialized, ready_bytes, health flags...

stats = qrng.get_statistics()
# generated_bytes, speed, failure counts...

config = qrng.get_config()
# postprocess, block_size, auto_calibration...
let info = qrng.get_info()?;
// serial, fw_version, core_version, hw_info

let status = qrng.get_status()?;
// initialized, ready_bytes, health flags...

let stats = qrng.get_statistics()?;
// generated_bytes, speed, failure counts...

let config = qrng.get_config()?;
// postprocess, block_size, auto_calibration...

Configuration

Every device setting is readable and writable:

PythonRust
from dataclasses import replace

config = qrng.get_config()
config = replace(config,
    block_size=256,
    auto_calibration=False,
)
qrng.set_config(config)
let mut config = qrng.get_config()?;
config.block_size = 256;
config.auto_calibration = false;
qrng.set_config(&config)?;
Field Type Description
postprocess PostProcess SHA256, RawNoise, or RawSamples
initial_level f32 LED initial level
startup_test bool Run health test on startup
auto_calibration bool Auto-calibrate light source
repetition_count bool NIST SP 800-90B repetition count test
adaptive_proportion bool NIST SP 800-90B adaptive proportion test
bit_count bool Crypta Labs bit balance test
generate_on_error bool Keep generating if a health test fails
n_lsbits u8 Number of LSBs to extract per sample
hash_input_size u8 Bytes fed into SHA256 per output block
block_size u16 Output block size in bytes
autocalibration_target u16 Target value for auto-calibration

API Reference

Method Description
random(n) Get n random bytes (1–65535, one-shot)
signed_read(n) Get n random bytes + 64-byte signature (FW 5.13+)
signed_read_verified(n, pub_key) Signed read + ECDSA signature verification
start_continuous() Start continuous streaming mode
start_continuous_fresh() Start continuous mode and discard buffered input
drain_input() Discard queued input bytes and return the number drained
read_continuous(n) Read n bytes from continuous stream
fill_bytes(buf) Fill a buffer of any size (auto-chunks)
get_info() Serial number, firmware version, hardware
get_status() Health flags, ready byte count
get_config() Full device configuration
set_config(config) Write device configuration
set_postprocess(mode) Shortcut to change entropy mode
get_statistics() Bytes generated, speed, failure counts
get_dev_pub_key() Device's ECDSA P-256 public key (64 bytes)
get_dev_certificate() CA-signed device certificate (64 bytes)
get_verified_pub_key(ca_key) Verify certificate chain, return device public key
reboot() Reboot the device (reconnect required)
reset() Restart generation and clear statistics
stop() Halt any active generation
close() Close serial port

Rust also implements std::io::Read, so QCicada works anywhere a reader is expected.

Project Structure

qcicada/
├── src/              # Rust crate
│   ├── lib.rs
│   ├── device.rs     # QCicada high-level API
│   ├── protocol.rs   # Wire protocol (pure, no I/O)
│   ├── crypto.rs     # ECDSA P-256 certificate verification
│   ├── serial.rs     # Serial transport + macOS fixes
│   ├── discovery.rs  # Device discovery
│   └── types.rs      # Shared data types
├── tests/            # Rust integration tests (device required)
├── examples/         # Rust examples
├── python/
│   ├── src/qcicada/  # Python package (mirrors Rust API)
│   ├── tests/        # Python unit + integration tests
│   └── examples/     # Python examples
├── Cargo.toml
└── python/pyproject.toml

Both SDKs implement the same wire protocol and share the same test vectors. Changes to one should be reflected in the other.

Why Not pyqcc?

The official Crypta Labs SDK (pyqcc) has macOS issues:

  • Uses /dev/tty.* ports — macOS needs /dev/cu.*
  • Sets inter_byte_timeout — causes FTDI read failures on macOS
  • Timeouts too short — macOS FTDI driver needs at least 500ms
  • No flush delay — FTDI driver drops bytes without a post-write pause
  • Device may be left in continuous mode — no drain on connect

This SDK fixes all of these. It also works fine on Linux.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qcicada-0.2.2.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qcicada-0.2.2-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file qcicada-0.2.2.tar.gz.

File metadata

  • Download URL: qcicada-0.2.2.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for qcicada-0.2.2.tar.gz
Algorithm Hash digest
SHA256 eff2858f46c9c8ca84df3dadaec23154896096fea350a059fcc2345e390a8f01
MD5 3ba4c4c62e65c33d0112222cb134c627
BLAKE2b-256 b54b8ce26a6bb2b70c885bde1653df4999bebc920b64f2a251c887019bee6eb0

See more details on using hashes here.

File details

Details for the file qcicada-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: qcicada-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 16.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for qcicada-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ed052fd19e5c36b3d05d977d5172e090e7ec364b154c181d26d77f7471272a69
MD5 ca50320ab5577c7101c349414583548d
BLAKE2b-256 4d363ffccd54229254aff4892e4f9b9a8ec7bb813a52c54d0d9c4db3528ddbeb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page