noqtis brings post-quantum signatures (ML-DSA, SLH-DSA), key encapsulation (ML-KEM, HQC) and hashes to R, through a bundled C library. Keys, signatures and ciphertexts are raw vectors in the encodings the standards define.
library(noqtis)
head(nqalgs())
#> name type pk sk sig ct ss id
#> 1 ML-DSA-44 sig 1312 2560 2420 NA NA 1
#> 2 ML-DSA-65 sig 1952 4032 3309 NA NA 2
#> 3 ML-DSA-87 sig 2592 4896 4627 NA NA 3
#> 4 SLH-DSA-SHA2-128s sig 32 64 7856 NA NA 4
#> 5 SLH-DSA-SHA2-128f sig 32 64 17088 NA NA 5
#> 6 SLH-DSA-SHA2-192s sig 48 96 16224 NA NA 6k <- nqkeygen("ML-DSA-65")
sig <- nqsign(k$sk, "the results in table 2", "ML-DSA-65")
length(sig)
#> [1] 3309
nqverify(k$pk, "the results in table 2", sig, "ML-DSA-65")
#> [1] TRUE
nqverify(k$pk, "the results in table 3", sig, "ML-DSA-65")
#> [1] FALSEA context string binds a signature to one use; verification needs the same context.
A capsule is a signed record of the exact files in a folder. Seal it once, and anyone with the public key can check later that no file was changed, added or removed.
d <- file.path(tempdir(), "analysis")
dir.create(d)
writeLines(c("x,y", "1,2"), file.path(d, "data.csv"))
k <- nqkeygen("ML-DSA-44")
nqseal(d, k$sk, k$pk, "ML-DSA-44", meta = '{"project":"vignette"}')
nqcheck(d, k$pk)
#> check ok detail
#> 1 seal.read TRUE noqtis.sig
#> 2 seal.parse TRUE a JSON object
#> 3 seal.canonical TRUE canonical JSON
#> 4 seal.fields TRUE the fields of noqtis-seal-1
#> 5 seal.format TRUE "noqtis-seal-1"
#> 6 seal.alg TRUE ML-DSA-44
#> 7 seal.context TRUE "noqtis-capsule-1"
#> 8 seal.key TRUE a public key of the right size
#> 9 seal.signature TRUE a signature of the right size
#> 10 manifest.read TRUE noqtis.json
#> 11 manifest.digest TRUE SHA-256 of noqtis.json matches the seal
#> 12 key TRUE the seal's key is the trusted key
#> 13 signature TRUE ML-DSA-44 signature over noqtis.json is valid
#> 14 folder TRUE listed
#> 15 file TRUE data.csv
#> 16 manifest.parse TRUE a JSON object
#> 17 manifest.canonical TRUE canonical JSON
#> 18 manifest.fields TRUE the fields of noqtis-capsule-1
#> 19 manifest.format TRUE "noqtis-capsule-1"
#> 20 manifest.created TRUE 2026-10-31T07:13:17Z
#> 21 manifest.order TRUE files sorted by path, no repeats
writeLines(c("x,y", "1,3"), file.path(d, "data.csv"))
r <- nqcheck(d, k$pk)
r[!r$ok, ]
#> check ok detail
#> 15 file FALSE data.csv: sha256 differsThe package ships a capsule made by the noqtis library’s own tests:
A format 2 capsule can also prove single files to a reader who should not see the others, and records the capsules it was made from:
d2 <- file.path(tempdir(), "model")
dir.create(d2)
writeLines("coef,0.4", file.path(d2, "fit.csv"))
writeLines("private", file.path(d2, "notes.txt"))
nqseal(d2, k$sk, k$pk, "ML-DSA-44", inputs = c(raw = g))
p <- nqprove(d2, "fit.csv")
attr(nqcheck(NULL, k$pk, proof = p), "ok")
#> [1] TRUE
attr(nqcheck(d2, k$pk, lineage = g), "ok")
#> [1] TRUEnqrotate() and nqrevoke() write key
statements that nqcheck(keys = ) follows, and
nqdsse() signs a DSSE envelope, the wrapper in-toto and
SLSA use.
Key generation, signing and encapsulation take their randomness from
the operating system (getrandom(),
arc4random_buf() or BCryptGenRandom()), never
from R’s generator: set.seed() does not make them repeat.
For test vectors, nqkeygen() and nqencap()
take an explicit seed, and nqsign() has
det = TRUE.