Python API
noqtis
Section titled “noqtis”noqtis: post-quantum signatures, KEMs, hashes and sealed capsules.
Python binding of the noqtis C library (FIPS 203, 204, 205, 202, 180-4 and HQC), compiled into the package; no runtime dependencies.
>>> import noqtis>>> pk, sk = noqtis.sign.keygen("ML-DSA-44")>>> noqtis.seal("results", sk, pk, meta={"project": "x"}) # doctest: +SKIP>>> noqtis.check("results", pk).ok # doctest: +SKIPTrue
algs(kind: Optional[str]=None) -> List[Alg]Every algorithm with its sizes; kind ‘sign’ or ‘kem’ filters.
hashes
Section titled “hashes”hashes() -> List[str]The hash names hash, hmac and prehash= take.
hash(alg: str, data: Bytes, n: Optional[int]=None) -> bytesThe digest of data. alg is ‘SHA-256’, ‘SHA3-512’,
‘SHAKE128’, … (hashlib spellings such as ‘sha3_256’ work too). n
is the output length for SHAKE (default 32 for SHAKE128, 64 for
SHAKE256); other hashes have a fixed length and take no n.
hmac(alg: str, key: Bytes, data: Bytes) -> bytesHMAC of data under key with a SHA-2 or SHA-3 hash.
noqtis.Row
Section titled “noqtis.Row”class Row(NamedTuple)One check of a capsule: its name, whether it passed, and why.
noqtis.Report
Section titled “noqtis.Report”class Report()What check found: rows lists every check; ok is True
only when the library passed the capsule as a whole.
failed: The rows that did not pass.
seal(dir: PathLike, sk: Bytes, pk: Bytes, meta: Union[None, str, Mapping[str, Any]]=None, alg: Optional[AlgLike]=None, format: Optional[int]=None, inputs: Optional[Iterable[Any]]=None) -> NoneWrites noqtis.json and noqtis.sig into dir, covering every
regular file in it. meta is a JSON object (a dict, or its text).
alg defaults to the algorithm of the key pair. The creation time
is SOURCE_DATE_EPOCH when set, else the clock.
format=2 writes capsule format 2: the manifest carries a Merkle
tree over its files, so that prove can show some files without
the others. inputs (format 2, the default with it) records the
capsules this one was made from: each a capsule folder (named by its
last component) or a (name, folder) pair, or (name, id) with
the 32-byte id from capid. Check the inputs before you name them;
check(lineage=...) follows them back.
capid(dir: PathLike) -> bytesThe capsule’s id (32 bytes): the SHA-256 of what its seal signs,
as seal(inputs=...) records it. Nothing is checked: check
the capsule first.
check(dir: Optional[PathLike]=None, pk: Optional[Bytes]=None, anchors: Optional[Iterable[Bytes]]=None, only: Union[None, str, Iterable[str]]=None, lineage: Union[None, PathLike, Iterable[PathLike]]=None, keys: Optional[Iterable[Union[Bytes, PathLike]]]=None, proof: Union[None, Bytes, PathLike]=None) -> ReportChecks dir against its seal. With pk the seal must be by
that key; without, the key in the seal is trusted (a row says so).
anchors are DER TSA certificates (at most 32): the seal must then
carry an RFC 3161 timestamp that verifies under one of them.
only checks just these files (paths as the manifest lists them,
‘/’ between folders); “partial” rows say what was left out.
lineage are capsule folders in which the inputs of a format 2
capsule are found by id and checked, to 64 levels (“lineage” rows).
keys are key statements (from rotate or revoke, as bytes
or a file path; at most 32) and need pk: the seal may then be by
a key pk was rotated to, and must carry a timestamp from before
its key was rotated away or revoked.
proof (bytes, or a file path) checks a proof from prove
instead of a folder: its seal, its signature and each proven entry;
with dir too, the proven files there. only, lineage and
keys do not apply to a proof.
prove(dir: PathLike, paths: Union[str, Iterable[str]]) -> bytesA proof (noqtis-proof-1, JSON) that paths are files of the
format 2 capsule in dir: the seal, the manifest’s head and each
file’s entry and Merkle audit path, and nothing of the other files.
check(proof=...) checks it. Check the capsule first.
noqtis.File
Section titled “noqtis.File”class File(NamedTuple)One file a manifest lists: its path, size in bytes and SHA-256 (64 hex digits).
files(dir: PathLike) -> List[File]The files the capsule’s manifest lists, in manifest order, read
without checking anything (check does that).
rotate
Section titled “rotate”rotate(sk: Bytes, pk: Bytes, nsk: Bytes, npk: Bytes, at: Union[None, int, float, datetime.datetime]=None, reason: Optional[str]=None, alg: Optional[AlgLike]=None, nalg: Optional[AlgLike]=None) -> bytesA key statement (noqtis-keys-1, JSON) that the key pair (sk, pk)
is replaced by (nsk, npk) from at (seconds since 1970 or an aware
datetime; default now), signed by both keys. reason is up to 255
bytes of text. alg and nalg default to the pairs’ algorithms;
both must be post-quantum (Ed25519 alone is refused). Give it to
check(keys=...).
revoke
Section titled “revoke”revoke(sk: Bytes, pk: Bytes, at: Union[None, int, float, datetime.datetime]=None, reason: Optional[str]=None, alg: Optional[AlgLike]=None) -> bytesA key statement (noqtis-keys-1, JSON) that the key pair (sk, pk)
is revoked from at (default now), signed by it; as rotate.
noqtis.Card
Section titled “noqtis.Card”class Card(NamedTuple)An identity card that verified: who it names (None for a field it
has not), when it was made, and its key: the algorithm, the public key
and its SHA-256 in hex, as noqtis inspect shows a seal’s.
idcard
Section titled “idcard”idcard(sk: Bytes, pk: Bytes, name: str, orcid: Optional[str]=None, email: Optional[str]=None, url: Optional[str]=None, affil: Optional[str]=None, at: Union[None, int, float, datetime.datetime]=None, alg: Optional[AlgLike]=None) -> bytesAn identity card (noqtis-idcard-1, JSON): the public key pk
with name and, when given, an ORCID iD (its check digit verified;
its https://orcid.org/ address is stored bare), an e-mail address, an
https:// web page and an affiliation, made at at (default now) and
signed by sk under the card’s own context. A field out of its
limits (see noqtis-idcard(1)) raises ArgError; Ed25519 alone raises
UnsupportedError.
idcheck
Section titled “idcheck”idcheck(card: Union[Bytes, str, 'os.PathLike[str]'], pk: Optional[Bytes]=None) -> Optional[Card]The card’s fields when it verifies (and, with pk, is for that
key); None when its signature does not verify or it is for another
key. card is the card’s bytes or the path of a card file. Input
that is not a card raises DecodeError. A card is self-signed: compare
key_sha256 with the one its holder published, or pass pk.
noqtis.sign
Section titled “noqtis.sign”Signatures: ML-DSA (FIPS 204), SLH-DSA (FIPS 205), Ed25519 (RFC 8032) and the hybrids ML-DSA-44/65/87+Ed25519, which need both parts to verify.
>>> from noqtis import sign>>> pk, sk = sign.keygen("ML-DSA-65")>>> sig = sign.sign("ML-DSA-65", sk, b"data")>>> sign.verify("ML-DSA-65", pk, b"data", sig)True
noqtis.sign.rndlen
Section titled “noqtis.sign.rndlen”noqtis.sign.rndlen(alg: AlgLike) -> intBytes of per-signature randomness: 32 for ML-DSA and the hybrids (ML-DSA-65+Ed25519, …), n for SLH-DSA, 0 for Ed25519.
noqtis.sign.keygen
Section titled “noqtis.sign.keygen”noqtis.sign.keygen(alg: AlgLike, seed: Optional[Bytes]=None) -> Tuple[bytes, bytes]A new key pair (pk, sk). With seed (xi for ML-DSA, 32 bytes;
SK.seed || SK.prf || PK.seed for SLH-DSA, 3n bytes; the 32-byte
private key for Ed25519; xi || the Ed25519 key, 64 bytes, for a
hybrid) the pair is the standard’s deterministic one; without, it
comes from the OS CSPRNG.
noqtis.sign.sign
Section titled “noqtis.sign.sign”noqtis.sign.sign(alg: AlgLike, sk: Bytes, msg: Bytes, ctx: Bytes=b'', deterministic: bool=False, prehash: Optional[str]=None, rnd: Optional[Bytes]=None) -> bytesThe signature of msg. ctx is the context string (at most
255 bytes). deterministic uses all-zero randomness (the standard’s
deterministic variant); rnd gives the randomness explicitly.
prehash names a hash (‘SHA-512’, ‘SHAKE256’, …) for HashML-DSA /
HashSLH-DSA: msg is still the message, hashed here.
noqtis.sign.verify
Section titled “noqtis.sign.verify”noqtis.sign.verify(alg: AlgLike, pk: Bytes, msg: Bytes, sig: Bytes, ctx: Bytes=b'', prehash: Optional[str]=None) -> boolTrue when sig is a valid signature of msg under pk;
False for any invalid or malformed signature. A public key of the
wrong size raises DecodeError; a context over 255 bytes, ArgError.
noqtis.kem
Section titled “noqtis.kem”Key encapsulation: ML-KEM (FIPS 203) and HQC.
>>> from noqtis import kem>>> pk, sk = kem.keygen("ML-KEM-768")>>> ct, ss = kem.encap("ML-KEM-768", pk)>>> kem.decap("ML-KEM-768", sk, ct) == ssTrue
noqtis.kem.keygen
Section titled “noqtis.kem.keygen”noqtis.kem.keygen(alg: AlgLike, seed: Optional[Bytes]=None) -> Tuple[bytes, bytes]A new key pair (pk, sk); with seed (d || z for ML-KEM) the
standard’s deterministic one.
noqtis.kem.encap
Section titled “noqtis.kem.encap”noqtis.kem.encap(alg: AlgLike, pk: Bytes, seed: Optional[Bytes]=None) -> Tuple[bytes, bytes](ciphertext, shared secret) for pk; seed (m for ML-KEM)
makes it deterministic, for test vectors.
noqtis.kem.decap
Section titled “noqtis.kem.decap”noqtis.kem.decap(alg: AlgLike, sk: Bytes, ct: Bytes) -> bytesThe shared secret in ct. A tampered ciphertext gives an
unrelated pseudorandom secret, not an error (implicit rejection); only
a ciphertext or key of the wrong size raises.
noqtis.dsse
Section titled “noqtis.dsse”DSSE envelopes (the in-toto and SLSA signing wrapper), signed with any noqtis signature algorithm.
>>> from noqtis import dsse, sign>>> pk, sk = sign.keygen("ML-DSA-65")>>> env = dsse.sign("ML-DSA-65", sk, pk, "application/vnd.in-toto+json",… b’{“a”:1}’)
>>> dsse.verify("ML-DSA-65", pk, env)Signed(type=‘application/vnd.in-toto+json’, payload=b’{“a”:1}’)
noqtis.Signed
Section titled “noqtis.Signed”class Signed(NamedTuple)What a verified envelope carries: its payloadType and payload.
noqtis.dsse.sign
Section titled “noqtis.dsse.sign”noqtis.dsse.sign(alg: AlgLike, sk: Bytes, pk: Bytes, type: str, payload: Union[Bytes, str]) -> bytesA DSSE envelope (canonical JSON) of payload (bytes, or a str as
its UTF-8) with payloadType
type (1 to 255 printable ASCII bytes, no space, quote or
backslash), signed by the key pair (sk, pk) over the envelope’s
pre-authentication encoding; its keyid is the hex SHA-256 of pk. The
signature is checked before the envelope is returned.
noqtis.dsse.verify
Section titled “noqtis.dsse.verify”noqtis.dsse.verify(alg: AlgLike, pk: Bytes, env: Union[Bytes, str]) -> Optional[Signed]The envelope’s (type, payload) when one of its signatures (at most
16) verifies under pk; None when none does. An env that is
not a DSSE envelope raises DecodeError.
noqtis.keyfile
Section titled “noqtis.keyfile”The CLI’s key files (DESIGN.md, “Key files”): a header line
noqtis-key-1 <algorithm> <pub|sec> and the key bytes as padded base64
in lines of 64 characters, as noqtis keygen writes them.
decode is as strict as the CLI’s reader: the exact header, \n or
\r\n line ends and nothing else outside the base64 alphabet,
canonical padding, and the algorithm’s exact key size. Python’s base64
is not constant time; decode secret key files where timing is not
observable.
>>> pk, sk = noqtis.sign.keygen("ML-DSA-44") # doctest: +SKIP>>> noqtis.keyfile.write("me", "ML-DSA-44", "sec", sk) # doctest: +SKIP>>> noqtis.keyfile.read("me").alg # doctest: +SKIP‘ML-DSA-44’
noqtis.Key
Section titled “noqtis.Key”class Key(NamedTuple)A decoded key file: the algorithm’s name, ‘pub’ or ‘sec’, the key.
noqtis.keyfile.encode
Section titled “noqtis.keyfile.encode”noqtis.keyfile.encode(alg: str, kind: str, key: Bytes) -> strThe key file text for key: kind is ‘pub’ or ‘sec’.
noqtis.keyfile.decode
Section titled “noqtis.keyfile.decode”noqtis.keyfile.decode(text: Union[str, Bytes]) -> KeyThe algorithm, kind and key of key file text (str or bytes).
noqtis.keyfile.read
Section titled “noqtis.keyfile.read”noqtis.keyfile.read(file: object, kind: Optional[str]=None) -> KeyReads a key file; kind ‘pub’ or ‘sec’ requires that kind.
noqtis.keyfile.write
Section titled “noqtis.keyfile.write”noqtis.keyfile.write(file: object, alg: str, kind: str, key: Bytes, force: bool=False) -> NoneWrites a key file. A secret key file is created owner-only, never
through a link; an existing file is kept unless force, which
removes it first so the new one never inherits its permissions.
Errors
Section titled “Errors”noqtis.NoqtisError
Section titled “noqtis.NoqtisError”class NoqtisError(Exception)A noqtis call failed; code is the library’s NQE* code.
noqtis.ArgError
Section titled “noqtis.ArgError”class ArgError(NoqtisError, ValueError)An argument is out of range (NQEARG).
noqtis.BufError
Section titled “noqtis.BufError”class BufError(NoqtisError)An output buffer is too small (NQEBUF).
noqtis.VerifyError
Section titled “noqtis.VerifyError”class VerifyError(NoqtisError)A signature, MAC or digest does not verify (NQEVRFY).
noqtis.DecodeError
Section titled “noqtis.DecodeError”class DecodeError(NoqtisError, ValueError)Input is not in the encoding the standard defines (NQEDEC).
noqtis.RandError
Section titled “noqtis.RandError”class RandError(NoqtisError)The operating system’s random source failed (NQERNG).
noqtis.MemError
Section titled “noqtis.MemError”class MemError(NoqtisError, MemoryError)An allocation failed (NQEMEM).
noqtis.FileError
Section titled “noqtis.FileError”class FileError(NoqtisError)A file could not be read or written (NQEIO).
noqtis.UnsupportedError
Section titled “noqtis.UnsupportedError”class UnsupportedError(NoqtisError)An algorithm or option noqtis does not provide (NQENOSUP).
noqtis.LimitError
Section titled “noqtis.LimitError”class LimitError(NoqtisError, ValueError)Input exceeds a size or depth limit (NQELIM).
noqtis.StateError
Section titled “noqtis.StateError”class StateError(NoqtisError)A context was used out of order (NQESTATE).