Skip to content

Flow 6 – ML-DSA-87 Sign/Verify (Non-Streaming)

This Flow 6 example demonstrates:

  1. Generating a ML-DSA-87 key (post-quantum signature).
  2. Signing a file in non-streaming mode.
  3. Exporting the complete key data (JSON), which includes the public portion.
  4. Verifying the signature, also in non-streaming mode.

Key Points
- Focuses on signature operations (no encryption here).
- Uses ML-DSA-87, a post-quantum algorithm for digital signatures.
- Non-streaming sign/verify methods: signFile(...) / verifySignature(...).

When to use it

  • Human-readable proofs – the compact-JWS string is plain text, so it can be pasted into Git commits, SBOMs or e-mails and later verified with nothing more than the exported public key.
  • Long-lived authenticity – ML-DSA-87 is a post-quantum algorithm, making these signatures safe even after large-scale quantum computers undermine today’s RSA/ECC certificates.
  • Air-gapped or offline review – because the whole payload is carried in one token, auditors can validate the signature on a disconnected workstation without needing to stream data from the server.

Shared helper – this code imports the utility class from
example_util.md (configuration, authentication, JSON).


Full Java Code

Below is the complete Java code for Flow 6. Copy and paste it into your IDE or project (e.g., co/ankatech/ankasecure/sdk/examples/ExampleScenario6.java) and run the main method.

/* *****************************************************************************
 * FILE: ExampleScenario6.java
 * Copyright © 2025 Anka Technologies.
 * ---------------------------------------------------------------------------
 * Scenario 6 – ML-DSA-87 Bulk Sign / Verify (Compact JWS helpers)
 * ---------------------------------------------------------------------------
 *  1. Generate an ML-DSA-87 post-quantum signature key.
 *  2. Export the key’s public metadata to JSON.
 *  3. Sign a local document with signFile → Compact JWS (UTF-8 text file).
 *  4. Verify that JWS with verifySignature.
 *  5. Display rich server metadata for both calls.
 * ---------------------------------------------------------------------------
 * Transient artefacts live in <temp_files/>.
 * ****************************************************************************/
package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.model.GenerateKeySpec;
import co.ankatech.ankasecure.sdk.model.SignResult;
import co.ankatech.ankasecure.sdk.model.VerifySignatureResult;
import co.ankatech.ankasecure.sdk.util.FileIO;

import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Properties;

import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;

/**
 * <h2>Scenario&nbsp;6 – ML-DSA-87 Bulk Helpers (Compact&nbsp;JWS)</h2>
 *
 * <p>This scenario signs and verifies a document with <strong>non-streaming</strong>
 * helpers that work on RFC&nbsp;7515 <em>Compact&nbsp;JWS</em> tokens:</p>
 * <ol>
 *   <li>Generate an ML-DSA-87 key.</li>
 *   <li>Export its metadata.</li>
 *   <li>Create a Compact JWS on disk via {@link AnkaSecureSdk#signFile(String, Path, Path)}.</li>
 *   <li>Verify it via {@link AnkaSecureSdk#verifySignature(Path)}.</li>
 *   <li>Print server metadata.</li>
 * </ol>
 *
 * <p><b>Implementation notes (Java&nbsp;21+):</b></p>
 * <ul>
 *   <li>All filesystem interactions use the {@link java.nio.file.Path} API.</li>
 *   <li>UTF-8 is explicitly specified for deterministic encoding.</li>
 *   <li>Directory creation delegates to {@link ExampleUtil#ensureTempDir(Path)}.</li>
 * </ul>
 *
 * <p><b>Thread-safety:</b> this class is stateless and immutable.</p>
 *
 * @author ANKATech – Security Engineering
 * @since 2.1.0
 */
public final class ExampleScenario6 {

    /* ====================================================================== */
    /**
     * Entry-point.
     *
     * @param args ignored
     */
    public static void main(final String[] args) {

        System.out.println("===== SCENARIO 6 START =====");
        System.out.println("""
                Purpose :
                  * ML-DSA-87 compact-JWS sign / verify helpers
                  * No streaming – signature token is handled as text
                --------------------------------------------------------------""");

        try {
            ensureTempDir(TEMP_DIR);

            Properties props = loadProperties();
            AnkaSecureSdk sdk = authenticate(props);

            runScenario(sdk);

        } catch (Exception ex) {
            fatal("Scenario 6 failed", ex);
        }

        System.out.println("===== SCENARIO 6 END =====");
    }

    /* ====================================================================== */
    /**
     * Executes all steps of Scenario&nbsp;6.
     *
     * @param sdk an authenticated {@link AnkaSecureSdk} instance
     *
     * @throws Exception if any step fails
     */
    private static void runScenario(final AnkaSecureSdk sdk) throws Exception {

        /* 1 ── prepare document ------------------------------------------ */
        Path doc = TEMP_DIR.resolve("scenario6_doc.txt");
        FileIO.writeUtf8(
                doc,
                "Scenario-6 – ML-DSA-87 compact-JWS sign / verify demo.");
        System.out.println("[1] Document ready       -> " + doc.toAbsolutePath());

        /* 2 ── generate ML-DSA-87 key ------------------------------------ */
        String kid = "sc6_mlds87_" + System.currentTimeMillis();
        sdk.generateKey(new GenerateKeySpec()
                .setKid(kid)
                .setKty("ML-DSA")
                .setAlg("ML-DSA-87"));
        System.out.println("[2] Key generated        -> kid = " + kid);

        /* 3 ── export metadata ------------------------------------------- */
        Path metaFile = TEMP_DIR.resolve("scenario6_key.json");
        FileIO.writeUtf8(
                metaFile,
                toJson(sdk.exportKey(kid)));
        System.out.println("[3] Metadata exported    -> " + metaFile.toAbsolutePath());

        /* 4 ── sign (compact JWS) ---------------------------------------- */
        Path jwsFile = TEMP_DIR.resolve("scenario6.jws");
        SignResult signMeta = sdk.signFile(kid, doc, jwsFile);
        System.out.println("[4] JWS written          -> " + jwsFile.toAbsolutePath());
        printSignMeta(signMeta);

        /* 5 ── verify ---------------------------------------------------- */
        VerifySignatureResult verMeta = sdk.verifySignature(jwsFile);
        System.out.println(MessageFormat.format(
                "[5] Verification valid?  -> {0}", verMeta.isValid()));
        printVerifyMeta(verMeta);

        System.out.println(verMeta.isValid()
                ? "[6] SUCCESS – signature verified."
                : "[6] FAILURE – signature invalid.");
    }

    /**
     * Private constructor prevents instantiation.
     */
    private ExampleScenario6() {
        /* utility class – no instantiation */
    }
}

How to Run

  1. Copy this code to a Java file in your project (e.g., co/ankatech/ankasecure/sdk/examples/ExampleScenario6.java).
  2. Compile (Maven, Gradle, or javac).
  3. Ensure cli.properties is accessible (file or -Dcli.config).
  4. Run the main method, for example:

    java co.ankatech.ankasecure.sdk.examples.ExampleScenario6

Look for output indicating:

  • ML-DSA-87 key generation
  • Non-streaming file signature
  • Key data export to scenario6_keydata.json
  • Signature verification success/failure

© 2025 AnkaTech. All rights reserved.