QuantaCore SDK Developer Guide

Advanced integration patterns, standard interfaces, asynchronous processing, performance optimization, and production best practices for the QuantaCore SDK v1.0.0.

Last updated: January 2025SDK v1.0.0Doc ID: QUAC100-SDK-DEV-002

SDK Architecture

The QuantaCore SDK is organized as a layered architecture providing multiple integration points for different application requirements.

LayerComponentUse Case
Application LayerLanguage Bindings (Python, Rust, Go, Java, C#, Node.js)High-level, idiomatic APIs for each language
Integration LayerOpenSSL 3.x Provider, PKCS#11 v2.40 Module, Windows CNG ProviderDrop-in replacement for existing cryptographic stacks
Core APIlibquac100 (C/C++)Direct hardware access, maximum control and performance
Abstraction LayerDevice Manager, DMA Engine, Job SchedulerMulti-device management, I/O optimization, async dispatch
Driver LayerLinux DKMS Module, Windows KMDF DriverOS-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.

Simulator Mode: Every layer supports the software simulator, enabling full development and testing without physical hardware. Set 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;
}
Thread Safety: 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

VariableDescription
OPENSSL_MODULESDirectory containing provider modules
OPENSSL_CONFPath to OpenSSL configuration file
QUAC_DEVICEDevice index (0–N) or "simulator"
QUAC_LOG_LEVEL0=off, 1=error, 2=warn, 3=info, 4=debug
QUAC_FIPS_MODEEnable 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

MechanismType ValueDescription
CKM_ML_KEM_512_KEY_PAIR_GEN0x80000401ML-KEM-512 key generation
CKM_ML_KEM_768_KEY_PAIR_GEN0x80000402ML-KEM-768 key generation
CKM_ML_KEM_1024_KEY_PAIR_GEN0x80000403ML-KEM-1024 key generation
CKM_ML_KEM_768_ENCAPS0x80000412ML-KEM-768 encapsulation
CKM_ML_KEM_768_DECAPS0x80000422ML-KEM-768 decapsulation
CKM_ML_DSA_44_KEY_PAIR_GEN0x80000501ML-DSA-44 key generation
CKM_ML_DSA_65_KEY_PAIR_GEN0x80000502ML-DSA-65 key generation
CKM_ML_DSA_87_KEY_PAIR_GEN0x80000503ML-DSA-87 key generation
CKM_ML_DSA_650x80000512ML-DSA-65 sign/verify
CKM_QUAC_QRNG0x80000601QRNG 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 SetNIST SecurityPublic KeySecret KeyCiphertextShared Secret
ML-KEM-512Level 1800 B1,632 B768 B32 B
ML-KEM-768Level 31,184 B2,400 B1,088 B32 B
ML-KEM-1024Level 51,568 B3,168 B1,568 B32 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:

AlgorithmNIST SecurityPublic KeySecret KeySignature
ML-DSA-44Level 21,312 B2,528 B2,420 B
ML-DSA-65Level 31,952 B4,000 B3,293 B
ML-DSA-87Level 52,592 B4,864 B4,595 B
SLH-DSA-SHA2-128sLevel 132 B64 B7,856 B
SLH-DSA-SHA2-128fLevel 132 B64 B17,088 B
SLH-DSA-SHA2-192sLevel 348 B96 B16,224 B
SLH-DSA-SHA2-256sLevel 564 B128 B29,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

ConsiderationML-DSASLH-DSA
PerformanceMuch faster (hardware: 800K+ ops/s)Slower (stateless hash-based)
Key SizeLarger keys (1.3–2.6 KB public)Very small keys (32–64 B public)
Signature SizeModerate (2.4–4.6 KB)Large (7.8–49.9 KB)
Security AssumptionLattice (Module-LWE)Hash function only
Best ForHigh-throughput TLS, API signingLong-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
Entropy Monitoring: In production, monitor entropy availability using 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));
        }
    }
}
Performance Tip: Batch operations amortize DMA transfer overhead. For high-throughput scenarios, batches of 64–256 operations typically achieve optimal throughput. Beyond 256, gains diminish due to device queue depth limits.

Multi-Threading

The QuantaCore SDK is thread-safe with the following guarantees:

Function CategoryThread Safety
quac_init(), quac_shutdown()NOT thread-safe — call from single thread at startup/shutdown
Device operations on different handlesFully thread-safe — no synchronization needed
Device operations on same handleThread-safe — internal locking, but serialized
Async operationsFully thread-safe — submit from any thread
Error functionsThread-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

RangeCategoryExamples
0x0001–0x00FFGeneralInvalid parameter, null pointer, buffer too small, timeout
0x0100–0x01FFDeviceNot found, open failed, driver error, hardware error
0x0200–0x02FFCryptographicInvalid algorithm, key generation failed, signing failed
0x0300–0x03FFKey ManagementKey not found, slot full, export denied
0x0400–0x04FFQRNGEntropy depleted, quality check failed, QRNG failure
0x0500–0x05FFAsync/BatchInvalid job ID, queue full, batch partial
0x0600–0x06FFSecurityAuthentication failed, tamper detected, FIPS mode required
0x0700–0x07FFSimulatorSimulator-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)

AlgorithmOperationThroughputLatency
ML-KEM-768Keygen1,400,000 ops/s< 700 ns
ML-KEM-768Encaps1,200,000 ops/s< 830 ns
ML-KEM-768Decaps1,000,000 ops/s< 1 μs
ML-DSA-65Sign800,000 ops/s< 1.25 μs
ML-DSA-65Verify900,000 ops/s< 1.1 μs
QRNGGenerate500 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:

FeatureFIPS ModeStandard Mode
Power-on self-test (POST)Mandatory — device blocks until passOptional
Conditional self-testBefore every key generationOptional
Approved algorithms onlyEnforced — non-approved rejectedAll algorithms available
Key zeroizationAutomatic on destroyAutomatic on destroy
Entropy sourceQRNG with SP 800-90B health testsQRNG or system PRNG
Audit loggingEnabled — all operations loggedConfigurable
/* 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 OperationPQC EquivalentChange Required
RSA_generate_key()EVP_PKEY_generate() with ML-DSA-65Algorithm name only
ECDH_compute_key()EVP_PKEY_encapsulate() with ML-KEM-768Switch to KEM paradigm
RAND_bytes()RAND_bytes() (automatic via provider)No change needed
ECDSA sign/verifyML-DSA sign/verify via EVPAlgorithm 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