nqsign(3)
NAME
nqsign, nqsignd,
nqvrfy, nqskg,
nqskgd, nqssize,
nqsname, nqsfind —
ML-DSA and SLH-DSA signatures
LIBRARY
libnoqtis, -lnoqtis
SYNOPSIS
#include
<noqtis.h>
int
nqskg(nqsalg alg,
uint8_t *pk, size_t pklen,
uint8_t *sk, size_t sklen);
int
nqskgd(nqsalg alg,
const uint8_t *seed, size_t
seedlen, uint8_t *pk, size_t
pklen, uint8_t *sk, size_t
sklen);
int
nqsign(nqsalg alg,
const uint8_t *sk, size_t sklen,
const uint8_t *msg, size_t
msglen, const uint8_t *ctx,
size_t ctxlen, int ph,
uint8_t *sig, size_t siglen,
size_t *outlen);
int
nqsignd(nqsalg alg,
const uint8_t *sk, size_t sklen,
const uint8_t *msg, size_t
msglen, const uint8_t *ctx,
size_t ctxlen, int ph,
const uint8_t *rnd, size_t
rndlen, uint8_t *sig, size_t
siglen, size_t *outlen);
int
nqvrfy(nqsalg alg,
const uint8_t *pk, size_t pklen,
const uint8_t *msg, size_t
msglen, const uint8_t *ctx,
size_t ctxlen, int ph,
const uint8_t *sig, size_t
siglen);
int
nqssize(nqsalg
alg, nqssz
*sz);
const char *
nqsname(nqsalg
alg);
int
nqsfind(const
char *name, nqsalg
*alg);
DESCRIPTION
These functions make keys, sign and verify with the module-lattice signature ML-DSA (FIPS 204), the stateless hash-based signature SLH-DSA (FIPS 205), Ed25519 (RFC 8032), and hybrid ML-DSA + Ed25519. Keys and signatures are byte strings in the encodings the standards define.
Algorithms
nqsalg names the parameter set:
| constant | name | pk | sk | signature |
NQMLDSA44 |
ML-DSA-44 | 1312 | 2560 | 2420 |
NQMLDSA65 |
ML-DSA-65 | 1952 | 4032 | 3309 |
NQMLDSA87 |
ML-DSA-87 | 2592 | 4896 | 4627 |
NQSLH128S |
SLH-DSA-SHA2-128s | 32 | 64 | 7856 |
NQSLH128F |
SLH-DSA-SHA2-128f | 32 | 64 | 17088 |
NQSLH192S |
SLH-DSA-SHA2-192s | 48 | 96 | 16224 |
NQSLH192F |
SLH-DSA-SHA2-192f | 48 | 96 | 35664 |
NQSLH256S |
SLH-DSA-SHA2-256s | 64 | 128 | 29792 |
NQSLH256F |
SLH-DSA-SHA2-256f | 64 | 128 | 49856 |
NQSLHK128S |
SLH-DSA-SHAKE-128s | 32 | 64 | 7856 |
NQSLHK128F |
SLH-DSA-SHAKE-128f | 32 | 64 | 17088 |
NQSLHK192S |
SLH-DSA-SHAKE-192s | 48 | 96 | 16224 |
NQSLHK192F |
SLH-DSA-SHAKE-192f | 48 | 96 | 35664 |
NQSLHK256S |
SLH-DSA-SHAKE-256s | 64 | 128 | 29792 |
NQSLHK256F |
SLH-DSA-SHAKE-256f | 64 | 128 | 49856 |
NQED25519 |
Ed25519 | 32 | 32 | 64 |
NQMDED44 |
ML-DSA-44+Ed25519 | 1344 | 2592 | 2484 |
NQMDED65 |
ML-DSA-65+Ed25519 | 1984 | 4064 | 3373 |
NQMDED87 |
ML-DSA-87+Ed25519 | 2624 | 4928 | 4691 |
Sizes are in bytes. The ‘s’
sets give small signatures and slow signing, the
‘f’ sets fast signing and larger
signatures.
NQED25519 is Ed25519 (RFC
8032): classical, not post-quantum, for interoperation (DSSE envelopes,
say); a capsule is never sealed with it alone. No context and
ph 0 is Ed25519; a context is Ed25519ctx;
ph NQS512 is Ed25519ph (no
other hash). It is deterministic:
nqsignd()
takes no randomness (rnd may
be NULL with
rndlen 0). Verification
refuses a public key of small or mixed order, a scalar S not below the group
order, and any non-canonical encoding.
The hybrid sets NQMDED44,
NQMDED65 and NQMDED87 sign
with ML-DSA and Ed25519 at once: the public key is the ML-DSA public key
followed by the Ed25519 one, the secret key likewise, the signature the
ML-DSA signature followed by the Ed25519 one, and a signature verifies only
when both parts do. Each part signs with the context
‘noqtis-hybrid:’ followed by the
caller's, so the caller's context is at most 241 bytes, and neither part
passes as a plain signature under the caller's context. The pre-hash variant
is HashML-DSA with SHA-512 and Ed25519ph. The key-generation seed of a
hybrid is the ML-DSA xi followed by the Ed25519 private key, 64 bytes.
nqssize()
stores the sizes of alg in *sz,
a nqssz with the members pk,
sk and sig.
nqsname()
returns the name of alg as in the table, or
NULL for a value that is not an
nqsalg.
nqsfind()
is its inverse: it stores in *alg the algorithm whose
name is exactly name (case matters).
Keys
nqskg()
makes a key pair from the operating system's random source
(nqrand(3)),
writing the public key to pk and the secret key to
sk.
nqskgd()
makes it from the standard's seed instead: the 32-byte xi for ML-DSA, or
SK.seed || SK.prf || PK.seed (3n bytes: 48, 72 or 96) for SLH-DSA. It exists
for test vectors and for keys that must be derived again; the seed is as
secret as the key. The seed may overlap pk or
sk, and on any error both are cleared.
Signing and verifying
nqsign() signs the
msglen bytes at msg with the
secret key sk of sklen bytes and
writes the signature to sig, a buffer of
siglen bytes; the signature's length is stored in
*outlen when outlen is not
NULL. Signing is hedged: fresh randomness is mixed
in, so signatures of one message differ.
ctx is the FIPS 204/205 context string,
ctxlen bytes, at most 255;
NULL and 0 give the empty context. A signature
verifies only under the context it was made with, which keeps signatures
made for one purpose from being accepted for another.
ph selects the interface: 0 for pure ML-DSA or SLH-DSA, or an nqhalg (nqhash(3)) for HashML-DSA or HashSLH-DSA, where msg is still the message and is hashed by the function. Any of the twelve hashes may be used; for SHAKE128 and SHAKE256 the digest is 32 and 64 bytes.
nqsignd()
is
nqsign()
with the standard's per-signature randomness rnd given
by the caller: exactly 32 bytes for ML-DSA, n bytes (16, 24 or 32) for
SLH-DSA. All zero bytes give the deterministic variant of the standard.
nqvrfy()
checks that sig of siglen bytes
is a signature of msg under the public key
pk with the context ctx and the
interface ph used to sign.
Buffers
Output buffers may be larger than the algorithm's size; only the defined number of bytes is written. Input keys and signatures must have exactly the algorithm's size. The signature buffer may overlap the message (signing in place).
RETURN VALUES
Every function except nqsname() returns
NQOK (0) on success or a negative error code:
- [
NQEARG] - alg is not an nqsalg; a
pointer is
NULLwith a nonzero length, or an output pointer isNULL; ctxlen exceeds 255; ph is neither 0 nor an nqhalg; or, fornqskgd() andnqsignd(), seed or rnd isNULLor not the standard's length.nqsfind() returns it for aNULLargument. - [
NQEBUF] - pklen, sklen or siglen is smaller than the algorithm's size.
- [
NQEDEC] - A key or signature given as input does not have the algorithm's length or encoding.
- [
NQEVRFY] nqvrfy(): the signature does not verify. A verification never returnsNQOKunless the signature is valid.- [
NQERNG] nqskg() ornqsign(): the operating system's random source failed.- [
NQENOSUP] nqsfind(): no algorithm has that name.
On any failure, nqsign() and
nqsignd() set *outlen to 0,
and when signing itself fails they wipe the signature buffer.
nqsname() returns NULL for
an unknown algorithm.
EXAMPLES
Make a key pair, sign and verify (the program docs/examples/sign.c of the source tree):
#include <noqtis.h>
#include <stdio.h>
int main(void) {
static uint8_t pk[1952], sk[4032], sig[3309];
const uint8_t msg[] = "results v1";
size_t n = 0;
int rc = nqskg(NQMLDSA65, pk, sizeof pk, sk, sizeof sk);
if (rc == NQOK)
rc = nqsign(NQMLDSA65, sk, sizeof sk, msg, sizeof msg - 1, NULL, 0, 0,
sig, sizeof sig, &n);
nqwipe(sk, sizeof sk);
if (rc == NQOK)
rc = nqvrfy(NQMLDSA65, pk, sizeof pk, msg, sizeof msg - 1, NULL, 0, 0,
sig, n);
printf("%s\n", nqerr(rc));
return rc == NQOK ? 0 : 1;
}
Sizes at run time, for code that takes the algorithm as a parameter:
nqsalg alg;
nqssz sz;
if (nqsfind("SLH-DSA-SHAKE-128s", &alg) == NQOK &&
nqssize(alg, &sz) == NQOK)
printf("%s: %zu-byte signatures\n", nqsname(alg), sz.sig);
Build with ‘cc prog.c $(pkg-config
--cflags --libs noqtis)’.
SEE ALSO
noqtis-sign(1), noqtis-verify(1), noqtis(3), nqerr(3), nqhash(3), nqwipe(3)
STANDARDS
FIPS 204: Module-Lattice-Based Digital Signature Standard, NIST, August 2024. FIPS 205: Stateless Hash-Based Digital Signature Standard, NIST, August 2024.
SECURITY CONSIDERATIONS
Key generation and signing run in constant time with respect to
the secret key, seed and randomness; ML-DSA signing is also first-order
masked (two shares). Wipe secret keys with
nqwipe(3) when
done. Outside test vectors, prefer nqsign() to
nqsignd(): the hedged variant keeps fault and
side-channel attacks that rely on repeated signatures harder.