Skip to content
ecdsa.com
C

liboqs (Open Quantum Safe)

liboqs, the Open Quantum Safe project's C library of post-quantum KEMs and signatures: ML-KEM, ML-DSA and SLH-DSA next to a long list of candidates behind one API — meant, in its authors' words, for research and prototyping.
License
MIT
Checked version
0.16.0 ·
Source
github.com/open-quantum-safe/liboqs
Documentation
openquantumsafe.org/liboqs/

Compatibility at a glance

Post-quantum

EdDSA
none
Post-quantum algorithms
ML-KEMML-DSASLH-DSAFalconHQCClassic-McElieceFrodoKEMBIKENTRUsntrup761KyberMAYOCROSSUOVSNOVAMQOM

liboqs gathers implementations of post-quantum key encapsulation mechanisms (OQS_KEM) and signature schemes (OQS_SIG) behind one C API, and it is the engine of the Open Quantum Safe provider for OpenSSL 3. The facts here were checked on release 0.16.0 (commit 5a1a854b0dc9), built with the default CMake options, which use OpenSSL for the symmetric primitives. The README says, in capitals: "WE DO NOT CURRENTLY RECOMMEND RELYING ON THIS LIBRARY IN A PRODUCTION ENVIRONMENT OR TO PROTECT ANY SENSITIVE DATA." For production ML-KEM and ML-DSA, the OpenSSL, BoringSSL, AWS-LC and Bouncy Castle pages list what their main APIs ship.

The default build enabled 41 KEMs and 221 signature parameter sets, and one parameter set of every family completed a full round trip: the NIST standards ML-KEM, ML-DSA and SLH-DSA (156 SLH-DSA variants, pure and prehash), Falcon, the KEM candidates HQC, Classic McEliece, FrodoKEM, BIKE, NTRU and sntrup761, the pre-standard Kyber, and the additional signature candidates MAYO, CROSS, UOV, SNOVA and MQOM. Name ML-KEM-768, not Kyber768, in new code. No XMSS or LMS parameter set is enabled in the default build.

ML-DSA signing is hedged: two signatures of the same message with the same key differed, so tests must verify signatures rather than compare bytes. FIPS 204 context strings are supported through OQS_SIG_sign_with_ctx_str; a signature made under the context invoice-v1 failed under invoice-v2 and under plain OQS_SIG_verify, which uses the empty context, so both sides must agree on it. A truncated signature, a 3-byte blob and a valid signature over other data all returned the same OQS_ERROR.

ML-KEM decapsulation does not report a damaged ciphertext: with one bit flipped, OQS_KEM_decaps returned OQS_SUCCESS and a different shared secret, as FIPS 203's implicit rejection requires. The mismatch shows up only when the derived keys are used, for example as an AEAD decryption failure. The function also takes no ciphertext length and reads exactly length_ciphertext bytes, 1088 for ML-KEM-768. Try both schemes in the ML-KEM tool and the post-quantum signature tool.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/liboqs/run.sh ran liboqs (Open Quantum Safe) 0.16.0 in the container image debian:trixie-20261005-slim on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA curves

    none

    No ECDSA: every enabled algorithm identifier belongs to a post-quantum family.

    Recorded by the check
    fact ecdsa-curves = none
  • EdDSA

    none

    No EdDSA: liboqs implements only post-quantum schemes.

    Recorded by the check
    fact eddsa = none
  • Post-quantum algorithms

    ML-KEMML-DSASLH-DSAFalconHQCClassic-McElieceFrodoKEMBIKENTRUsntrup761KyberMAYOCROSSUOVSNOVAMQOM

    One parameter set of each listed family completed key generation, encapsulation or signing, and decapsulation or verification in the default build.

    Recorded by the check
    fact pqc = ML-KEM, ML-DSA, SLH-DSA, Falcon, HQC, Classic-McEliece, FrodoKEM, BIKE, NTRU, sntrup761, Kyber, MAYO, CROSS, UOV, SNOVA, MQOM
    evidence pqc: enabled: 41 KEMs, 221 signature schemes, 0 stateful signature schemes
    evidence pqc: enabled parameter sets by identifier prefix: ML-KEM- 3, ML-DSA- 3, SLH_DSA 156, Falcon- 4, HQC- 3, Classic-McEliece- 10, FrodoKEM- 6, eFrodoKEM- 6, BIKE- 3, NTRU- 6, sntrup 1, Kyber 3, MAYO- 4, cross- 18, OV- 12, SNOVA 12, mqom2 12
    evidence pqc: first parameter set of each family run end to end; failures: none
  • Stateful hash-based signatures

    not-built

    No XMSS or LMS parameter set is enabled in the default build.

    Recorded by the check
    fact stateful-signatures = not-built
    evidence stateful-signatures: no XMSS or LMS parameter set is enabled in this build
  • ML-DSA signing mode

    hedged

    Two ML-DSA-65 signatures of one message with one key were different (hedged signing).

    Recorded by the check
    fact ml-dsa-signing = hedged
    evidence ml-dsa-signing: ML-DSA-65: public key 1952 bytes, signature 3309 bytes; two signatures of one message are different
  • ML-DSA context string

    supported

    A signature under the context invoice-v1 verified with it and failed with invoice-v2 and with the empty context.

    Recorded by the check
    fact ml-dsa-context-string = supported
    evidence ml-dsa-context-string: signed with context "invoice-v1": verifies with it yes, with "invoice-v2" no, with OQS_SIG_verify no
    evidence ml-dsa-context-string: an OQS_SIG_sign signature verifies with a zero-length context: yes
  • Malformed ML-DSA signature

    returns-false

    A truncated signature, 3 bytes and a wrong message all returned OQS_ERROR (-1).

    Recorded by the check
    fact ml-dsa-malformed-signature = returns-false
    evidence ml-dsa-malformed-signature: OQS_SIG_verify: truncated signature -1, 3 bytes -1, wrong message -1 (OQS_SUCCESS = 0, OQS_ERROR = -1)
  • Modified ML-KEM ciphertext

    implicit-rejection

    With one ciphertext bit flipped, OQS_KEM_decaps returned OQS_SUCCESS and a different shared secret.

    Recorded by the check
    fact ml-kem-modified-ciphertext = implicit-rejection
    evidence ml-kem-modified-ciphertext: ML-KEM-768 (public key 1184, ciphertext 1088 bytes): one flipped bit, OQS_KEM_decaps returned 0 and the secrets differ
    evidence ml-kem-modified-ciphertext: kem.h: OQS_API OQS_STATUS OQS_KEM_decaps(const OQS_KEM *kem, uint8_t *shared_secret, const uint8_t *ciphertext, const uint8_t *secret_key)

Usage

Run an ML-KEM-768 key exchange and sign and verify with ML-DSA-65 under a context string.
#include <oqs/oqs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    OQS_init();
    /* ML-KEM-768: the receiver publishes pk, the sender encapsulates a fresh secret to it. */
    OQS_KEM *kem = OQS_KEM_new(OQS_KEM_alg_ml_kem_768);
    uint8_t *pk = malloc(kem->length_public_key), *sk = malloc(kem->length_secret_key);
    uint8_t *ct = malloc(kem->length_ciphertext), sent[32], received[32];
    OQS_KEM_keypair(kem, pk, sk);
    OQS_KEM_encaps(kem, ct, sent, pk);
    /* Decapsulation never reports a damaged ciphertext: it yields a different secret instead. */
    OQS_KEM_decaps(kem, received, ct, sk);
    printf("ML-KEM-768 secrets match: %d\n", memcmp(sent, received, 32) == 0);

    /* ML-DSA-65 with a FIPS 204 context string that ties the signature to one purpose. */
    OQS_SIG *sig = OQS_SIG_new(OQS_SIG_alg_ml_dsa_65);
    uint8_t *vk = malloc(sig->length_public_key), *dk = malloc(sig->length_secret_key);
    uint8_t *s = malloc(sig->length_signature);
    const uint8_t msg[] = "order #1042 approved", ctx[] = "invoice-v1";
    size_t len = 0;
    OQS_SIG_keypair(sig, vk, dk);
    OQS_SIG_sign_with_ctx_str(sig, s, &len, msg, sizeof msg - 1, ctx, sizeof ctx - 1, dk);
    OQS_STATUS rc = OQS_SIG_verify_with_ctx_str(sig, msg, sizeof msg - 1, s, len, ctx, sizeof ctx - 1, vk);
    printf("ML-DSA-65 verified: %d (%zu-byte signature)\n", rc == OQS_SUCCESS, len);

    OQS_MEM_secure_free(sk, kem->length_secret_key);
    OQS_MEM_secure_free(dk, sig->length_secret_key);
    free(pk), free(ct), free(vk), free(s);
    OQS_KEM_free(kem), OQS_SIG_free(sig);
    OQS_destroy();
    return 0;
}

The snippet harness runs every sample verbatim in the container image debian:trixie-20261005-slim, and the run fails unless the output matches.

Setup
apt-get -qq update >/dev/null && apt-get -qq install -y --no-install-recommends build-essential cmake ninja-build git ca-certificates libssl-dev >/dev/null 2>&1 && git clone -q --depth 1 --branch 0.16.0 https://github.com/open-quantum-safe/liboqs /oqs 2>/dev/null && test "$(git -C /oqs rev-parse HEAD)" = 5a1a854b0dc9f2141bdc771c555ee60c37950183 && cmake -S /oqs -B /oqs/build -G Ninja -DOQS_BUILD_ONLY_LIB=ON >/dev/null 2>&1 && ninja -C /oqs/build install >/dev/null
Command
cc -o usage usage.c -loqs -lcrypto && ./usage
Output must match (regular expression)
ML-KEM-768 secrets match: 1\nML-DSA-65 verified: 1 \(3309-byte signature\)

Libraries