QuantaCore SDK Developer Guide
Advanced integration patterns, standard interfaces, asynchronous processing, performance optimization, and production best practices for the QuantaCore SDK v1.0.0.
SDK Architecture
The QuantaCore SDK is organized as a layered architecture providing multiple integration points for different application requirements.
| Layer | Component | Use Case |
|---|---|---|
| Application Layer | Language Bindings (Python, Rust, Go, Java, C#, Node.js) | High-level, idiomatic APIs for each language |
| Integration Layer | OpenSSL 3.x Provider, PKCS#11 v2.40 Module, Windows CNG Provider | Drop-in replacement for existing cryptographic stacks |
| Core API | libquac100 (C/C++) | Direct hardware access, maximum control and performance |
| Abstraction Layer | Device Manager, DMA Engine, Job Scheduler | Multi-device management, I/O optimization, async dispatch |
| Driver Layer | Linux DKMS Module, Windows KMDF Driver | OS-level hardware communication |
All layers ultimately communicate through the core C API (quac100.h), which manages device discovery, resource allocation, and DMA transfers to the QUAC 100 hardware. The integration layer provides standard cryptographic interfaces that existing applications can use without modification, while the language bindings provide idiomatic wrappers optimized for each programming ecosystem.
QUAC_SIMULATOR=1 or call quac_set_simulator_mode(true) before initialization.
Device Lifecycle
Proper device lifecycle management ensures correct resource allocation and cleanup. The SDK follows an initialize → open → operate → close → shutdown pattern.
#include <quac100.h>
int main(void) {
quac_result_t rc;
/* ── Step 1: Configure options (optional) ── */
quac_init_options_t opts;
QUAC_INIT_STRUCT(opts);
opts.flags = QUAC_INIT_SIMULATOR; /* Fall back to simulator */
opts.log_level = 3; /* info-level logging */
opts.async_thread_count = 4; /* async worker threads */
/* ── Step 2: Initialize SDK ── */
rc = quac_init(&opts);
if (QUAC_FAILED(rc)) {
fprintf(stderr, "Init failed: %s\n", quac_error_string(rc));
return 1;
}
/* ── Step 3: Enumerate & open device ── */
uint32_t count;
quac_device_count(&count);
printf("Found %u device(s)\n", count);
quac_device_t dev;
rc = quac_open(0, &dev);
if (QUAC_FAILED(rc)) {
quac_shutdown();
return 1;
}
/* ── Step 4: Query device info ── */
quac_device_info_t info;
QUAC_INIT_STRUCT(info);
quac_get_info(dev, &info);
printf("Device: %s FW: %u.%u.%u Temp: %d°C\n",
info.device_name,
info.firmware_major, info.firmware_minor, info.firmware_patch,
info.temperature_celsius);
/* ── Step 5: Perform operations ── */
/* ... cryptographic operations here ... */
/* ── Step 6: Cleanup ── */
quac_close(dev);
quac_shutdown();
return 0;
}
quac_init() and quac_shutdown() are not thread-safe. Call them from a single thread at application startup/shutdown. All other API functions are thread-safe when operating on different device handles.
Open by serial number — For multi-device deployments, use quac_open_by_serial() to target a specific card:
quac_device_t dev;
rc = quac_open_by_serial("QUAC-2025-000042", &dev);
OpenSSL 3.x Provider
The QUAC 100 OpenSSL provider enables transparent post-quantum acceleration for any application built against OpenSSL 3.0+. This is the recommended integration path for existing server applications (nginx, Apache, HAProxy) and libraries that already use OpenSSL.
Installation
The provider module installs to OpenSSL's module directory:
# Linux default path
/usr/local/lib/ossl-modules/quac100.so
# Verify installation
openssl list -providers -provider quac100
Configuration
There are three methods to load the provider. For production deployments, Method 2 (configuration file) is recommended:
Method 1: Environment Variable
export OPENSSL_MODULES=/usr/local/lib/ossl-modules
openssl list -providers -provider quac100
Method 2: openssl.cnf Configuration (Recommended)
# /etc/ssl/openssl-quac100.cnf
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
quac100 = quac100_sect
[default_sect]
activate = 1
[quac100_sect]
activate = 1
module = /usr/local/lib/ossl-modules/quac100.so
export OPENSSL_CONF=/etc/ssl/openssl-quac100.cnf
Method 3: Per-Command Loading
openssl -provider-path /usr/local/lib/ossl-modules \
-provider quac100 genpkey -algorithm ML-KEM-768 -out key.pem
Command-Line Operations
# Generate ML-KEM-768 keypair
openssl genpkey -provider quac100 -algorithm ML-KEM-768 -out mlkem768.pem
# Generate ML-DSA-65 keypair
openssl genpkey -provider quac100 -algorithm ML-DSA-65 -out mldsa65.pem
# Extract public key
openssl pkey -provider quac100 -in mldsa65.pem -pubout -out mldsa65_pub.pem
# Sign a file
openssl pkeyutl -provider quac100 -sign \
-inkey mldsa65.pem -in message.txt -out message.sig
# Verify signature
openssl pkeyutl -provider quac100 -verify \
-pubin -inkey mldsa65_pub.pem -in message.txt -sigfile message.sig
# KEM encapsulate
openssl pkeyutl -provider quac100 -encapsulate \
-pubin -inkey mlkem768_pub.pem -out ct.bin -secret ss.bin
# KEM decapsulate
openssl pkeyutl -provider quac100 -decapsulate \
-inkey mlkem768.pem -in ct.bin -secret recovered.bin
# Generate hardware random bytes
openssl rand -provider quac100 -hex 32
# Create PQC self-signed certificate
openssl req -provider quac100 -x509 -new -key mldsa65.pem \
-out cert.pem -days 365 -subj "/CN=example.com"
# Benchmark
openssl speed -provider quac100 -seconds 5 mlkem768 mldsa65
Programmatic Usage (C)
#include <openssl/evp.h>
#include <openssl/provider.h>
int main(void) {
/* Load provider */
OSSL_PROVIDER *quac = OSSL_PROVIDER_load(NULL, "quac100");
if (!quac) {
fprintf(stderr, "Failed to load quac100 provider\n");
return 1;
}
/* Generate ML-KEM-768 keypair */
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(
NULL, "ML-KEM-768", "provider=quac100");
EVP_PKEY_keygen_init(ctx);
EVP_PKEY *pkey = NULL;
EVP_PKEY_generate(ctx, &pkey);
/* ... encapsulate/decapsulate using pkey ... */
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
OSSL_PROVIDER_unload(quac);
return 0;
}
Environment Variables
| Variable | Description |
|---|---|
OPENSSL_MODULES | Directory containing provider modules |
OPENSSL_CONF | Path to OpenSSL configuration file |
QUAC_DEVICE | Device index (0–N) or "simulator" |
QUAC_LOG_LEVEL | 0=off, 1=error, 2=warn, 3=info, 4=debug |
QUAC_FIPS_MODE | Enable FIPS mode (1/0) |
PKCS#11 Module
The QUAC 100 PKCS#11 module implements the Cryptoki v2.40 standard, enabling integration with any application that supports PKCS#11 tokens — including Firefox/NSS, Java (SunPKCS11), and enterprise HSM management tools.
Module Location
# Linux
/usr/local/lib/libquac100_pkcs11.so
# Windows
C:\Program Files\QUAC100\bin\quac100_pkcs11.dll
# Verify with pkcs11-tool (OpenSC)
pkcs11-tool --module /usr/local/lib/libquac100_pkcs11.so --list-mechanisms
Supported Mechanisms
| Mechanism | Type Value | Description |
|---|---|---|
CKM_ML_KEM_512_KEY_PAIR_GEN | 0x80000401 | ML-KEM-512 key generation |
CKM_ML_KEM_768_KEY_PAIR_GEN | 0x80000402 | ML-KEM-768 key generation |
CKM_ML_KEM_1024_KEY_PAIR_GEN | 0x80000403 | ML-KEM-1024 key generation |
CKM_ML_KEM_768_ENCAPS | 0x80000412 | ML-KEM-768 encapsulation |
CKM_ML_KEM_768_DECAPS | 0x80000422 | ML-KEM-768 decapsulation |
CKM_ML_DSA_44_KEY_PAIR_GEN | 0x80000501 | ML-DSA-44 key generation |
CKM_ML_DSA_65_KEY_PAIR_GEN | 0x80000502 | ML-DSA-65 key generation |
CKM_ML_DSA_87_KEY_PAIR_GEN | 0x80000503 | ML-DSA-87 key generation |
CKM_ML_DSA_65 | 0x80000512 | ML-DSA-65 sign/verify |
CKM_QUAC_QRNG | 0x80000601 | QRNG random generation |
Session Workflow
#include <dlfcn.h>
#include "quac100_pkcs11.h"
int main(void) {
/* Load module dynamically */
void *handle = dlopen("libquac100_pkcs11.so", RTLD_NOW);
CK_C_GetFunctionList pGetFL =
(CK_C_GetFunctionList)dlsym(handle, "C_GetFunctionList");
CK_FUNCTION_LIST_PTR fn;
pGetFL(&fn);
fn->C_Initialize(NULL);
/* Open session on slot 0 */
CK_SESSION_HANDLE session;
fn->C_OpenSession(0, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL, NULL, &session);
/* Login (PIN defaults to "0000" for development) */
CK_UTF8CHAR pin[] = "0000";
fn->C_Login(session, CKU_USER, pin, sizeof(pin) - 1);
/* ── Generate ML-DSA-65 key pair ── */
CK_MECHANISM mech = { CKM_ML_DSA_65_KEY_PAIR_GEN, NULL, 0 };
CK_OBJECT_HANDLE hPub, hPriv;
CK_OBJECT_CLASS pubCls = CKO_PUBLIC_KEY;
CK_OBJECT_CLASS privCls = CKO_PRIVATE_KEY;
CK_KEY_TYPE kt = CKK_ML_DSA_65;
CK_BBOOL yes = CK_TRUE;
CK_ATTRIBUTE pubT[] = {
{CKA_CLASS, &pubCls, sizeof(pubCls)},
{CKA_KEY_TYPE,&kt, sizeof(kt)},
{CKA_VERIFY, &yes, sizeof(yes)}
};
CK_ATTRIBUTE privT[] = {
{CKA_CLASS, &privCls, sizeof(privCls)},
{CKA_KEY_TYPE,&kt, sizeof(kt)},
{CKA_SIGN, &yes, sizeof(yes)}
};
fn->C_GenerateKeyPair(session, &mech,
pubT, 3, privT, 3, &hPub, &hPriv);
/* ── Sign a message ── */
CK_MECHANISM signMech = { CKM_ML_DSA_65, NULL, 0 };
CK_BYTE msg[] = "Quantum-safe message";
CK_BYTE sig[3293];
CK_ULONG sigLen = sizeof(sig);
fn->C_SignInit(session, &signMech, hPriv);
fn->C_Sign(session, msg, sizeof(msg) - 1, sig, &sigLen);
/* ── Verify ── */
fn->C_VerifyInit(session, &signMech, hPub);
CK_RV rv = fn->C_Verify(session, msg, sizeof(msg) - 1, sig, sigLen);
printf("Signature %s\n", rv == CKR_OK ? "VALID" : "INVALID");
fn->C_Logout(session);
fn->C_CloseSession(session);
fn->C_Finalize(NULL);
dlclose(handle);
return 0;
}
Java Integration (SunPKCS11)
// pkcs11.cfg
// name = QUAC100
// library = /usr/local/lib/libquac100_pkcs11.so
import java.security.*;
Provider p = Security.getProvider("SunPKCS11");
p = p.configure("/path/to/pkcs11.cfg");
Security.addProvider(p);
KeyStore ks = KeyStore.getInstance("PKCS11", p);
ks.load(null, "0000".toCharArray());
Python Integration (PyKCS11)
import PyKCS11
lib = PyKCS11.PyKCS11Lib()
lib.load('/usr/local/lib/libquac100_pkcs11.so')
slots = lib.getSlotList(tokenPresent=True)
session = lib.openSession(slots[0], PyKCS11.CKF_SERIAL_SESSION)
# Generate hardware random bytes
random_data = session.generateRandom(32)
print(f"QRNG: {random_data.hex()}")
KEM Operations (ML-KEM)
ML-KEM (formerly Kyber) provides key encapsulation for establishing shared secrets. The SDK supports all three NIST-standardized parameter sets:
| Parameter Set | NIST Security | Public Key | Secret Key | Ciphertext | Shared Secret |
|---|---|---|---|---|---|
| ML-KEM-512 | Level 1 | 800 B | 1,632 B | 768 B | 32 B |
| ML-KEM-768 | Level 3 | 1,184 B | 2,400 B | 1,088 B | 32 B |
| ML-KEM-1024 | Level 5 | 1,568 B | 3,168 B | 1,568 B | 32 B |
Query Sizes at Runtime
size_t pk_sz, sk_sz, ct_sz, ss_sz;
quac_kem_sizes(QUAC_ALGORITHM_KYBER768, &pk_sz, &sk_sz, &ct_sz, &ss_sz);
printf("PK=%zu SK=%zu CT=%zu SS=%zu\n", pk_sz, sk_sz, ct_sz, ss_sz);
/* Output: PK=1184 SK=2400 CT=1088 SS=32 */
Full KEM Exchange (C)
/* ── Alice: generate key pair ── */
uint8_t pk[QUAC_KYBER768_PUBLIC_KEY_SIZE];
uint8_t sk[QUAC_KYBER768_SECRET_KEY_SIZE];
quac_kem_keygen(dev, QUAC_ALGORITHM_KYBER768, pk, sizeof(pk), sk, sizeof(sk));
/* ── Bob: encapsulate with Alice's public key ── */
uint8_t ct[QUAC_KYBER768_CIPHERTEXT_SIZE];
uint8_t ss_bob[QUAC_KYBER768_SHARED_SECRET_SIZE];
quac_kem_encaps(dev, QUAC_ALGORITHM_KYBER768,
pk, sizeof(pk), ct, sizeof(ct), ss_bob, sizeof(ss_bob));
/* ── Alice: decapsulate with her secret key ── */
uint8_t ss_alice[QUAC_KYBER768_SHARED_SECRET_SIZE];
quac_kem_decaps(dev, QUAC_ALGORITHM_KYBER768,
ct, sizeof(ct), sk, sizeof(sk), ss_alice, sizeof(ss_alice));
/* ss_alice == ss_bob → 32-byte shared secret established */
assert(memcmp(ss_alice, ss_bob, 32) == 0);
Python Example
from quantacore import QuantaCore
qc = QuantaCore(simulator=True)
# Alice generates keys
pk, sk = qc.kem_keygen("ML-KEM-768")
# Bob encapsulates
ct, ss_bob = qc.kem_encaps("ML-KEM-768", pk)
# Alice decapsulates
ss_alice = qc.kem_decaps("ML-KEM-768", ct, sk)
assert ss_alice == ss_bob # Shared secret established
Signature Operations (ML-DSA & SLH-DSA)
The SDK supports both lattice-based (ML-DSA) and hash-based (SLH-DSA) signature schemes:
| Algorithm | NIST Security | Public Key | Secret Key | Signature |
|---|---|---|---|---|
| ML-DSA-44 | Level 2 | 1,312 B | 2,528 B | 2,420 B |
| ML-DSA-65 | Level 3 | 1,952 B | 4,000 B | 3,293 B |
| ML-DSA-87 | Level 5 | 2,592 B | 4,864 B | 4,595 B |
| SLH-DSA-SHA2-128s | Level 1 | 32 B | 64 B | 7,856 B |
| SLH-DSA-SHA2-128f | Level 1 | 32 B | 64 B | 17,088 B |
| SLH-DSA-SHA2-192s | Level 3 | 48 B | 96 B | 16,224 B |
| SLH-DSA-SHA2-256s | Level 5 | 64 B | 128 B | 29,792 B |
Sign and Verify (C)
/* Generate ML-DSA-65 key pair */
uint8_t pk[QUAC_DILITHIUM3_PUBLIC_KEY_SIZE];
uint8_t sk[QUAC_DILITHIUM3_SECRET_KEY_SIZE];
quac_sign_keygen(dev, QUAC_ALGORITHM_DILITHIUM3,
pk, sizeof(pk), sk, sizeof(sk));
/* Sign */
const uint8_t msg[] = "Quantum-safe payload";
uint8_t sig[QUAC_DILITHIUM3_SIGNATURE_SIZE];
size_t sig_len;
quac_sign(dev, QUAC_ALGORITHM_DILITHIUM3,
sk, sizeof(sk), msg, sizeof(msg) - 1,
sig, sizeof(sig), &sig_len);
/* Verify */
quac_result_t rc = quac_verify(dev, QUAC_ALGORITHM_DILITHIUM3,
pk, sizeof(pk), msg, sizeof(msg) - 1,
sig, sig_len);
printf("Signature: %s\n", QUAC_SUCCEEDED(rc) ? "VALID" : "INVALID");
Choosing Between ML-DSA and SLH-DSA
| Consideration | ML-DSA | SLH-DSA |
|---|---|---|
| Performance | Much faster (hardware: 800K+ ops/s) | Slower (stateless hash-based) |
| Key Size | Larger keys (1.3–2.6 KB public) | Very small keys (32–64 B public) |
| Signature Size | Moderate (2.4–4.6 KB) | Large (7.8–49.9 KB) |
| Security Assumption | Lattice (Module-LWE) | Hash function only |
| Best For | High-throughput TLS, API signing | Long-lived signatures, firmware signing |
QRNG Integration
The QUAC 100 includes a hardware quantum random number generator providing true quantum entropy at up to 500 Mbps conditioned throughput, processed through a NIST SP 800-90B compliant conditioning pipeline.
/* Generate random bytes */
uint8_t nonce[32];
quac_result_t rc = quac_random_bytes(dev, nonce, sizeof(nonce));
/* Check available entropy */
uint32_t bits;
quac_random_available(dev, &bits);
printf("Available entropy: %u bits\n", bits);
/* Reseed with additional entropy (optional) */
uint8_t app_seed[] = "application-specific-seed-data";
quac_random_reseed(dev, app_seed, sizeof(app_seed) - 1);
Via OpenSSL:
# Generate 1 KB of quantum random data
openssl rand -provider quac100 -out random.bin 1024
# Generate hex-encoded random
openssl rand -provider quac100 -hex 32
quac_random_available() or the quac100-diag --entropy CLI command. The QRNG hardware maintains an internal entropy pool that refills at 500 Mbps.
Key Management
The QUAC 100 supports on-device key storage with up to 256 key slots, providing HSM-grade key protection. Keys stored on-device never leave the cryptographic boundary in plaintext.
/* ── Generate and store a key pair on device ── */
quac_key_attr_t attr;
QUAC_INIT_STRUCT(attr);
attr.algorithm = QUAC_ALGORITHM_KYBER768;
attr.type = QUAC_KEY_TYPE_KEYPAIR;
attr.usage = QUAC_KEY_USAGE_ENCAPSULATE | QUAC_KEY_USAGE_DECAPSULATE;
attr.extractable = false; /* Key cannot leave device */
attr.persistent = true; /* Survives reboot */
snprintf(attr.label, sizeof(attr.label), "production-kem-key");
quac_key_handle_t key;
quac_key_generate(dev, &attr, &key);
/* ── Query stored key attributes ── */
quac_key_attr_t stored;
QUAC_INIT_STRUCT(stored);
quac_key_get_attr(dev, key, &stored);
printf("Label: %s Algorithm: %s Extractable: %s\n",
stored.label,
quac_algorithm_name(stored.algorithm),
stored.extractable ? "yes" : "no");
/* ── Import an external key ── */
quac_key_handle_t imported;
quac_key_attr_t imp_attr;
QUAC_INIT_STRUCT(imp_attr);
imp_attr.algorithm = QUAC_ALGORITHM_DILITHIUM3;
imp_attr.type = QUAC_KEY_TYPE_SECRET;
imp_attr.extractable = false;
quac_key_import(dev, &imp_attr, sk_data, sk_len, &imported);
/* ── Destroy a key (secure erase) ── */
quac_key_destroy(dev, key);
Asynchronous Operations
The async API enables non-blocking cryptographic operations, essential for high-throughput server applications. Three patterns are supported: callbacks, polling, and blocking wait.
Callback Pattern
void on_keygen_complete(quac_device_t dev, quac_job_id_t job,
quac_result_t result, void *ctx) {
if (QUAC_SUCCEEDED(result)) {
printf("Job %llu completed successfully\n", (unsigned long long)job);
}
}
/* Submit async keygen */
uint8_t pk[QUAC_KYBER768_PUBLIC_KEY_SIZE];
uint8_t sk[QUAC_KYBER768_SECRET_KEY_SIZE];
quac_job_id_t job;
quac_async_kem_keygen(dev, QUAC_ALGORITHM_KYBER768,
pk, sizeof(pk), sk, sizeof(sk),
on_keygen_complete, NULL, &job);
Polling Pattern
quac_job_id_t job;
quac_async_kem_keygen(dev, QUAC_ALGORITHM_KYBER768,
pk, sizeof(pk), sk, sizeof(sk),
NULL, NULL, &job);
bool done = false;
while (!done) {
quac_async_poll(dev, job, &done);
/* Do other work while waiting ... */
}
/* Check final result */
quac_job_info_t info;
QUAC_INIT_STRUCT(info);
quac_async_get_info(dev, job, &info);
printf("Duration: %llu ns\n",
(unsigned long long)(info.complete_time - info.start_time));
Blocking Wait with Timeout
quac_job_id_t job;
quac_async_kem_keygen(dev, QUAC_ALGORITHM_KYBER768,
pk, sizeof(pk), sk, sizeof(sk),
NULL, NULL, &job);
/* Block for up to 5 seconds */
quac_result_t rc = quac_async_wait(dev, job, 5000);
if (rc == QUAC_ERROR_TIMEOUT) {
quac_async_cancel(dev, job);
fprintf(stderr, "Operation timed out\n");
}
Batch Processing
Batch operations process multiple cryptographic requests in a single DMA transfer, maximizing throughput. The QUAC 100 supports up to 1,024 operations per batch.
#define BATCH_SIZE 256
quac_batch_item_t items[BATCH_SIZE];
/* Prepare 256 KEM keygen operations */
uint8_t pks[BATCH_SIZE][QUAC_KYBER768_PUBLIC_KEY_SIZE];
uint8_t sks[BATCH_SIZE][QUAC_KYBER768_SECRET_KEY_SIZE];
for (int i = 0; i < BATCH_SIZE; i++) {
items[i].algorithm = QUAC_ALGORITHM_KYBER768;
items[i].operation = 0; /* keygen */
items[i].input_data = NULL;
items[i].input_len = 0;
items[i].output_data = pks[i];
items[i].output_len = sizeof(pks[i]);
items[i].result = QUAC_SUCCESS;
}
/* Submit batch */
quac_result_t rc = quac_batch_submit(dev, items, BATCH_SIZE);
if (rc == QUAC_ERROR_BATCH_PARTIAL) {
/* Check individual results */
for (int i = 0; i < BATCH_SIZE; i++) {
if (QUAC_FAILED(items[i].result)) {
fprintf(stderr, "Item %d failed: %s\n",
i, quac_error_string(items[i].result));
}
}
}
Multi-Threading
The QuantaCore SDK is thread-safe with the following guarantees:
| Function Category | Thread Safety |
|---|---|
quac_init(), quac_shutdown() | NOT thread-safe — call from single thread at startup/shutdown |
| Device operations on different handles | Fully thread-safe — no synchronization needed |
| Device operations on same handle | Thread-safe — internal locking, but serialized |
| Async operations | Fully thread-safe — submit from any thread |
| Error functions | Thread-safe — per-thread error state |
Recommended Pattern: Device-per-Thread
#include <pthread.h>
#include <quac100.h>
#define NUM_THREADS 4
void *worker(void *arg) {
int tid = *(int *)arg;
/* Each thread opens its own device handle */
quac_device_t dev;
quac_open(0, &dev);
for (int i = 0; i < 10000; i++) {
uint8_t pk[QUAC_KYBER768_PUBLIC_KEY_SIZE];
uint8_t sk[QUAC_KYBER768_SECRET_KEY_SIZE];
quac_kem_keygen(dev, QUAC_ALGORITHM_KYBER768,
pk, sizeof(pk), sk, sizeof(sk));
}
quac_close(dev);
return NULL;
}
int main(void) {
quac_init(NULL);
pthread_t threads[NUM_THREADS];
int ids[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, worker, &ids[i]);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
quac_shutdown();
return 0;
}
Error Handling
All SDK functions return quac_result_t. Use the convenience macros QUAC_SUCCEEDED() and QUAC_FAILED() for result checking.
Error Categories
| Range | Category | Examples |
|---|---|---|
0x0001–0x00FF | General | Invalid parameter, null pointer, buffer too small, timeout |
0x0100–0x01FF | Device | Not found, open failed, driver error, hardware error |
0x0200–0x02FF | Cryptographic | Invalid algorithm, key generation failed, signing failed |
0x0300–0x03FF | Key Management | Key not found, slot full, export denied |
0x0400–0x04FF | QRNG | Entropy depleted, quality check failed, QRNG failure |
0x0500–0x05FF | Async/Batch | Invalid job ID, queue full, batch partial |
0x0600–0x06FF | Security | Authentication failed, tamper detected, FIPS mode required |
0x0700–0x07FF | Simulator | Simulator-only feature, hardware required |
Comprehensive Error Handling Pattern
quac_result_t rc = quac_kem_keygen(dev, QUAC_ALGORITHM_KYBER768,
pk, sizeof(pk), sk, sizeof(sk));
if (QUAC_FAILED(rc)) {
/* Human-readable message */
fprintf(stderr, "Error: %s (0x%04X)\n", quac_error_string(rc), rc);
/* Extended error detail (thread-local) */
char detail[256];
if (QUAC_SUCCEEDED(quac_get_last_error_detail(detail, sizeof(detail)))) {
fprintf(stderr, "Detail: %s\n", detail);
}
/* Category-specific handling */
switch (rc & 0xFF00) {
case 0x0100: /* Device error — try reset */
quac_reset(dev);
break;
case 0x0400: /* Entropy — wait and retry */
usleep(100000);
break;
default:
break;
}
}
Logging & Diagnostics
The SDK provides configurable logging via callback or environment variable:
Log Callback
void my_logger(int level, const char *msg, void *ctx) {
const char *names[] = {"ERROR","WARN","INFO","DEBUG","TRACE"};
fprintf(stderr, "[QUAC %s] %s\n", names[level], msg);
}
quac_init_options_t opts;
QUAC_INIT_STRUCT(opts);
opts.log_callback = my_logger;
opts.log_user_data = NULL;
opts.log_level = 3; /* 0=error, 1=warn, 2=info, 3=debug, 4=trace */
quac_init(&opts);
Environment Variable
export QUAC_LOG_LEVEL=4 # trace-level
export QUAC_LOG_FILE=/var/log/quac100.log
./my_application
CLI Diagnostics
# Full device health check
quac100-diag --all
# Device info
quac100-diag --info
# Temperature and entropy monitoring
quac100-diag --health --watch 5
# Run FIPS self-test
quac100-diag --self-test
# Entropy quality analysis
quac100-diag --entropy --samples 10000
Performance Optimization
Production deployments can achieve maximum throughput through several optimization strategies:
Expected Throughput (QUAC 100 Hardware)
| Algorithm | Operation | Throughput | Latency |
|---|---|---|---|
| ML-KEM-768 | Keygen | 1,400,000 ops/s | < 700 ns |
| ML-KEM-768 | Encaps | 1,200,000 ops/s | < 830 ns |
| ML-KEM-768 | Decaps | 1,000,000 ops/s | < 1 μs |
| ML-DSA-65 | Sign | 800,000 ops/s | < 1.25 μs |
| ML-DSA-65 | Verify | 900,000 ops/s | < 1.1 μs |
| QRNG | Generate | 500 Mbps | — |
Optimization Techniques
1. Use batch operations — Amortize DMA overhead by grouping 64–256 operations per batch call.
2. Device-per-thread — Each thread should open its own device handle to avoid internal lock contention.
3. Pre-allocate buffers — Use stack-allocated buffers with QUAC_*_SIZE constants rather than dynamic allocation in hot paths.
4. Async pipeline — Submit operations asynchronously and process results in completion callbacks to keep the device pipeline saturated.
5. NUMA awareness — Pin threads to the same NUMA node as the PCIe slot hosting the QUAC 100 for optimal DMA bandwidth.
Benchmarking
# Built-in benchmark suite
quac100-bench --algorithm kyber768 --threads 4 --duration 10
# OpenSSL benchmark
openssl speed -provider quac100 -seconds 10 mlkem768
# Custom benchmark with CSV output
quac100-bench --all --format csv --output results.csv
FIPS 140-3 Mode
The QUAC 100 supports FIPS 140-3 Level 2 validated operation. When FIPS mode is enabled:
| Feature | FIPS Mode | Standard Mode |
|---|---|---|
| Power-on self-test (POST) | Mandatory — device blocks until pass | Optional |
| Conditional self-test | Before every key generation | Optional |
| Approved algorithms only | Enforced — non-approved rejected | All algorithms available |
| Key zeroization | Automatic on destroy | Automatic on destroy |
| Entropy source | QRNG with SP 800-90B health tests | QRNG or system PRNG |
| Audit logging | Enabled — all operations logged | Configurable |
/* Enable FIPS mode at initialization */
quac_init_options_t opts;
QUAC_INIT_STRUCT(opts);
opts.flags = QUAC_INIT_FIPS_MODE;
quac_init(&opts);
/* Or via environment variable */
/* export QUAC_FIPS_MODE=1 */
/* Run self-test programmatically */
quac_result_t rc = quac_self_test(dev);
if (QUAC_FAILED(rc)) {
fprintf(stderr, "FIPS self-test FAILED: %s\n", quac_error_string(rc));
/* Device enters error state — must reset or power cycle */
}
Migration Guide
Migrating existing cryptographic applications to post-quantum algorithms using the QuantaCore SDK:
OpenSSL Migration Path
For applications using OpenSSL, migration is transparent — simply load the QUAC 100 provider and use PQC algorithm names:
| Classical Operation | PQC Equivalent | Change Required |
|---|---|---|
RSA_generate_key() | EVP_PKEY_generate() with ML-DSA-65 | Algorithm name only |
ECDH_compute_key() | EVP_PKEY_encapsulate() with ML-KEM-768 | Switch to KEM paradigm |
RAND_bytes() | RAND_bytes() (automatic via provider) | No change needed |
| ECDSA sign/verify | ML-DSA sign/verify via EVP | Algorithm name only |
PKCS#11 Migration Path
Replace the existing PKCS#11 module path in your application's configuration with the QUAC 100 module. The API structure is identical — only mechanism types and key types change to PQC equivalents.
Hybrid (Classical + PQC) Deployment
During the transition period, many deployments will require hybrid approaches combining classical and post-quantum algorithms. The SDK supports this through the OpenSSL provider which can coexist with the default provider:
# Both providers active — use PQC for new connections,
# classical for backward compatibility
openssl s_server -provider default -provider quac100 \
-cert classical-cert.pem -key classical-key.pem \
-groups X25519MLKEM768