Use from Java
The Java binding calls the shared library libnoqtis through the
Foreign Function and Memory API of JDK 22 and later: no JNI code to
compile and no dependencies. It runs on Linux, macOS and Windows
(64-bit).
Build it
Section titled “Build it”From the repository:
sh bindings/java/build.sh test examplesThis builds the shared library with CMake and bindings/java/build/noqtis.jar
with javac and jar alone (no Maven or Gradle, nothing downloaded),
then runs the tests and the three examples on this page. Run a program
with the jar on the class path, the library where Java finds it, and
native access enabled:
java --enable-native-access=ALL-UNNAMED \ -Dnoqtis.library=build/lib/libnoqtis.so -cp build/noqtis.jar:. MyAppWithout -Dnoqtis.library (or NOQTIS_LIBRARY) it loads libnoqtis
from the system’s library path, where an installed noqtis puts it.
Seal and check a folder
Section titled “Seal and check a folder”This is examples/SealExample.java, less the lines at its end that
remove the folder:
import io.github.rootcoder007.noqtis.Capsule;import io.github.rootcoder007.noqtis.Sign;import java.nio.file.Files;import java.nio.file.Path;
public class SealExample { public static void main(String[] args) throws Exception { // a folder with some results Path d = Files.createTempDirectory("noqtis-java-demo"); Files.createDirectories(d.resolve("data")); Files.writeString(d.resolve("data/in.csv"), "a,b\n1,2\n"); Files.writeString(d.resolve("report.txt"), "r\n");
// seal it, then check it with the public key pinned Sign.KeyPair k = Sign.keygen("ML-DSA-65"); Capsule.seal(d, "ML-DSA-65", k.sk(), k.pk(), "{\"project\":\"tutorial\"}"); Capsule.Opts o = Capsule.Opts.none().withTrust(k.pk()); Capsule.Report r = Capsule.check(d, o); System.out.println("check: " + r.ok() + " (" + r.rows().size() + " checks)");
// change a file: the check fails and says why Files.writeString(d.resolve("report.txt"), "r\nedited\n"); r = Capsule.check(d, o); System.out.println("after edit: " + r.ok()); for (Capsule.Row row : r.failed()) { System.out.println("FAIL " + row.check() + " " + row.detail()); } }}It prints:
check: true (22 checks)after edit: falseFAIL file report.txt: size differsCapsule.sealwritesnoqtis.jsonandnoqtis.siginto the folder;metais JSON text (an object), signed with the manifest and never interpreted.Capsule.seal2seals in format 2, with the capsules it was made from.Capsule.checkreturns aReportalso when the capsule fails:ok()is true only when every check passed, androws()lists every check. WithoutwithTrustit accepts the key in the seal, and thekeyrow says so.Capsule.inspect,capid,proveandcheckProofdo whatnoqtis inspect, the capsule id,noqtis proveandnoqtis check --proofdo.
Sign and verify
Section titled “Sign and verify”examples/SignExample.java:
import io.github.rootcoder007.noqtis.Sign;import java.nio.charset.StandardCharsets;
public class SignExample { public static void main(String[] args) { Sign.KeyPair k = Sign.keygen("ML-DSA-65"); Sign.Opts o = Sign.Opts.none().withCtx("app-v1".getBytes(StandardCharsets.UTF_8)); byte[] msg = "the results in table 2".getBytes(StandardCharsets.UTF_8); byte[] sig = Sign.sign("ML-DSA-65", k.sk(), msg, o); System.out.println("signature: " + sig.length + " bytes"); System.out.println("verify: " + Sign.verify("ML-DSA-65", k.pk(), msg, sig, o)); byte[] other = "other".getBytes(StandardCharsets.UTF_8); System.out.println("other message: " + Sign.verify("ML-DSA-65", k.pk(), other, sig, o)); }}signature: 3309 bytesverify: trueother message: falseverify returns false for a bad signature; only a bad argument
throws. Every error is a NoqtisException with the library’s code
(code(), kind()) and message (nqerr()).
Identity cards
Section titled “Identity cards”An identity card ties your signing key to who you are, signed by the
key itself; noqtis check --card reads it too.
examples/CardExample.java:
import io.github.rootcoder007.noqtis.IdCard;import io.github.rootcoder007.noqtis.NoqtisException;import io.github.rootcoder007.noqtis.Sign;import java.nio.charset.StandardCharsets;
public class CardExample { public static void main(String[] args) { Sign.KeyPair k = Sign.keygen("ML-DSA-65"); IdCard.Fields f = IdCard.Fields.of("Ada Lovelace") .withOrcid("https://orcid.org/0000-0002-1825-0097") .withUrl("https://example.org/ada"); byte[] card = IdCard.make("ML-DSA-65", k.sk(), k.pk(), f, null);
IdCard.Card c = IdCard.verify(card, k.pk()); // null unless it verifies and is k's System.out.println("name: " + c.name()); System.out.println("orcid: " + c.orcid());
// a changed card does not verify; a wrong check digit is refused String t = new String(card, StandardCharsets.UTF_8).replace("Ada", "Eve"); System.out.println("changed: " + IdCard.verify(t.getBytes(StandardCharsets.UTF_8), null)); try { IdCard.make("ML-DSA-65", k.sk(), k.pk(), f.withOrcid("0000-0002-1825-0098"), null); } catch (NoqtisException e) { System.out.println("refused: " + e.kind()); } }}name: Ada Lovelaceorcid: 0000-0002-1825-0097changed: nullrefused: ARGThe card holds the bare ORCID iD. It is self-signed: it shows that the
holder of the key wrote it, not that the name is true, so give the key’s
SHA-256 (keySha256()) where people already know you, or let readers
pin your public key. IdCard.verify returns null for a card that
does not verify or is for another key, and throws DECODE for input
that is not a card.
Kemhaskeygen,encapanddecap;Noqtishashash,hmac,random,versionandalgs;KeyFilereads and writes the CLI’s key files.- Secret keys are plain
byte[]: overwrite them when that matters.
The full API is in the binding’s README.