noqtis-capsule(5)
NAME
noqtis-capsule —
the noqtis capsule format, version 1
DESCRIPTION
A capsule is a folder that holds the files of a result together with noqtis.json, the manifest, and noqtis.sig, the seal, both at the top of the folder. The manifest lists every file with its size and two digests; the seal signs the manifest's exact bytes with a post-quantum signature.
The key words MUST, MUST NOT, SHOULD and MAY are to be read as in RFC 2119 and RFC 8174.
Canonical JSON
Both files MUST be JSON (RFC 8259) in the canonical form of RFC 8785 (JCS): UTF-8, no whitespace outside strings, object members sorted by the UTF-16 code units of their names, strings with the minimal escapes, and numbers in the shortest form that round-trips. A checker MUST reject a file that is not byte-identical to its own canonical form, has a duplicate member name, or is not valid UTF-8.
The manifest, noqtis.json
An object with exactly these members:
created- A string: the creation time, RFC 3339 in UTC,
‘
YYYY-MM-DDTHH:MM:SSZ’ with optional fractional seconds before the ‘Z’. files- An array with one object per file, sorted by the bytes of
path, with no path twice. Each object has exactly: format- The string ‘
noqtis-capsule-1’. meta- Optional: any JSON object, at most 1 MiB, opaque to noqtis.
Because of the canonical order, the manifest always reads
{"created":...,"files":[{"path":...,"sha256":...,
"sha3_256":...,"size":...},...],"format":"noqtis-capsule-1"
[,"meta":{...}]}
Paths
A path MUST use ‘/’ between
names, MUST NOT start with ‘/’, and
MUST be in normal form: no empty segment and no
‘.’. Every name (segment) MUST be
valid UTF-8 without overlong forms or surrogates, MUST NOT be empty,
‘.’ or
‘..’, and MUST NOT contain a control
character (U+0000 to U+001F, U+007F),
‘/’,
‘\’ or
‘:’. Two paths of one capsule MUST NOT
differ only in ASCII case. A path MUST be at most 4096 bytes.
noqtis.json and noqtis.sig at the top of the folder are not listed. Only regular files are listed; folders are implied by the paths, so an empty folder is not recorded.
The seal, noqtis.sig
An object with exactly these members:
alg- The signature algorithm, as
nqsname() (nqsign(3)) names it, for example ‘ML-DSA-65’ or ‘SLH-DSA-SHAKE-128s’. context- The FIPS 204/205 context string the signature was made with: exactly
‘
noqtis-capsule-1’. A checker refuses any other context, so that a signature made for another purpose (a plain noqtis-sign(1) of the same bytes, say) is never taken for a seal. format- The string ‘
noqtis-seal-1’. manifest_sha256- The SHA-256 of the exact bytes of noqtis.json, 64 lower-case hex digits.
public_key- The signer's public key, base64 (RFC 4648 section 4, padded, no line breaks) of the standard's encoding.
signature- The pure ML-DSA or SLH-DSA signature over the exact bytes of noqtis.json with that context, base64 likewise.
timestamp- Optional: base64 of an RFC 3161 time-stamp token whose message imprint is
the hash (SHA-256, SHA-384 or SHA-512) of the decoded
signaturebytes. noqtis-seal(1)--tsaadds it (nqtsput(3)).
Limits
A checker MUST refuse, with a failing check and never by
truncating: noqtis.json over 512 MiB (its size is
checked before it is read, and again if it grows while being read);
noqtis.sig over 1 MiB; meta
over 1 MiB; more than 2,000,000 files; a path over 4096 bytes; folders more
than 64 levels deep. JSON nesting is limited to 64 levels.
Sealing
A sealer MUST list every entry of the folder without following links and MUST refuse to seal a folder that holds a symbolic link, a Windows reparse point, a special file (device, pipe, socket), a name the rules above refuse, two paths that differ only in ASCII case, or anything over a limit. It MUST verify the new signature before writing the seal.
Checking
A checker MUST:
- check that both files are canonical with exactly the fields above and the
right formats, and that
algis known andpublic_keyandsignaturehave the algorithm's sizes; - check that the SHA-256 of noqtis.json equals
manifest_sha256; - verify
signatureover the exact bytes of noqtis.json withcontext, using the trusted public key when the caller gave one (and then require thatpublic_keyequal it), otherwisepublic_key, and say which; - for every listed file: require it to exist, to be a regular file reached without following a link, and to have the listed size, SHA-256 and SHA3-256;
- fail on every regular file present but not listed, every link, special file or refused name in the folder, every path out of order, listed twice, not in normal form, or differing from another only in case;
- when trust anchors for time-stamping authorities are given, require a
timestampwhose imprint matches and whose token verifies, with the TSA certificate path valid at the token's genTime, under one of them; when none are given, fail atimestampthat is present, since it cannot be checked.
A capsule passes only when every check passes.
Format 2
A format 2 capsule has the manifest format
‘noqtis-capsule-2’ and the seal format
‘noqtis-seal-2’. Its manifest is the
format 1 manifest with these members after format,
in canonical order:
inputs- Optional: 1 to 1024 objects {
capsule,name}:capsuleis the id of a capsule this one was made from, 64 lower-case hex digits;nameis 1 to 255 bytes of UTF-8 with no control characters. merkle- {
alg,leaves,root}:algis ‘sha256’,leavesthe number of file entries androotthe Merkle tree head, 64 lower-case hex digits. meta- Optional, as in format 1.
The tree is the Merkle Tree Hash of RFC 9162, section 2.1.1, over the file entries in manifest order: each leaf is SHA-256 of a 0x00 byte and the entry's canonical bytes as they appear in the manifest, each node SHA-256 of a 0x01 byte and its two children; the tree of no entries is SHA-256 of the empty string.
The
head is the manifest
with the member
‘"files":[...],’ removed:
the bytes before ‘"files"’
and the bytes after the comma that ends the array. The seal signs the head,
not the manifest: it has head_sha256, the SHA-256 of
the head, in place of manifest_sha256, and its
context is
‘noqtis-capsule-2’. A checker MUST,
besides the checks of format 1 made over the head, check that the tree over
the listed entries has the head's root and
leaves.
A capsule's id is the SHA-256 of the bytes its seal signs: the manifest in format 1, the head in format 2.
Proofs
A proof (noqtis-prove(1)) shows that some entries are in a format 2 capsule. It is canonical JSON:
{"entries":[{"entry":E,"index":I,"path":[H,...]},...],
"format":"noqtis-proof-1","head":HEAD,"seal":SEAL}
seal is the seal object,
head the head as a JSON object, and each entry
entry a file entry exactly as in the manifest, at
position index (from 0), with
path its audit path (RFC 9162, section 2.1.3.1),
each node 64 lower-case hex digits, from the leaf up. A checker MUST check
the seal and its signature over the canonical bytes of
head as for a capsule, then each entry's path to the
head's root with the tree size
leaves by the algorithm of RFC 9162, section
2.1.3.2. The tree size is authenticated by the signed head, not by the path.
A proof holds no name, size or digest of the entries it does not prove.
Key statements
A key statement is canonical JSON with the members
alg, at,
format, key,
kind, reason and
signature, and for a rotation
new_alg, new_key and
new_signature: format is
‘noqtis-keys-1’;
kind is
‘rotate’ or
‘revoke’; alg
and key are the key's algorithm and base64 public
key, new_alg and new_key
those of the key that replaces it; at is a time as
‘YYYY-MM-DDTHH:MM:SSZ’;
reason is up to 255 bytes of text, possibly empty.
signature is by key, and
new_signature by new_key,
each with context ‘noqtis-keys-1’ over
the statement without its two signature members, in canonical form. Both
algorithms are post-quantum.
A checker given a trusted key and statements follows them: the
trusted key, the key a valid rotation of it names, and so on. It trusts no
key when two statements are about one key or a rotation leads back to an
earlier one. A seal's key passes when it is in that chain. When that key was
rotated away or revoked at at, the seal passes only
with a verified timestamp whose genTime is earlier.
Identity cards
An identity card is canonical JSON with the members
alg, created,
format, key,
name and signature, and any
of orcid, email,
url and affil:
format is
‘noqtis-idcard-1’;
alg and key are a
post-quantum signature algorithm and the base64 public key;
created is a time as
‘YYYY-MM-DDTHH:MM:SSZ’;
name and affil are 1 to 255
bytes of text with no control character and no space at either end;
orcid is a bare ORCID iD
(‘0000-0002-1825-0097’) whose last
character is its ISO 7064 MOD 11-2 check digit, X for 10;
email is
‘local@domain.tld’ in at most 254
bytes, and url an
‘https://’ address with a host in at
most 1024 bytes, neither with a space. A member not given is left out, never
empty. signature is by key
with context ‘noqtis-idcard-1’ over
the card without its signature member, in canonical
form. A card is at most 256 KiB.
A card is self-signed: it shows that the holder of
key wrote it, not that the name is true. A checker
that also knows the key (pinned, or by the SHA-256 its holder published) and
finds it is the seal's key may say who sealed the capsule.
FILES
- noqtis.json
- the manifest
- noqtis.sig
- the seal
EXAMPLES
A manifest (one line in the file; broken here for width):
{"created":"2026-10-28T23:35:26Z","files":[{"path":"data/in.csv",
"sha256":"5be08c96...","sha3_256":"cec130cc...","size":4},
{"path":"report.txt","sha256":"8e54b0ca...","sha3_256":"cfd6402f...",
"size":2}],"format":"noqtis-capsule-1","meta":{"project":"demo"}}
Its seal:
{"alg":"ML-DSA-65","context":"noqtis-capsule-1",
"format":"noqtis-seal-1","manifest_sha256":"9f322e95...",
"public_key":"1qWycP39...","signature":"..."}
SEE ALSO
noqtis-check(1), noqtis-idcard(1), noqtis-seal(1), nqchk(3), nqchkt(3), nqseal(3)
STANDARDS
JSON Canonicalization Scheme (JCS), RFC 8785, June 2020. Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP), RFC 3161, August 2001. Certificate Transparency Version 2.0, RFC 9162, December 2021. Date and Time on the Internet: Timestamps, RFC 3339, July 2002. The Base16, Base32, and Base64 Data Encodings, RFC 4648, October 2006.
SECURITY CONSIDERATIONS
The signature's context is always
‘noqtis-capsule-1’, and a checker
refuses any other, so a signature its key holder made for another purpose
over the same bytes is never a seal. Key statements and identity cards have
contexts of their own (‘noqtis-keys-1,
‘’), so
none of the three passes for another.noqtis-idcard-1’
A timestamp's TSA certificate path is checked at the token's genTime, and revocation is not checked: a timestamp is only as good as the TSA's key was at that time. If the key is stolen later, tokens with an earlier genTime can be made that still check; pin the TSAs you trust, and stop trusting every token of a TSA that announces a key compromise.