Flow 6 – ML-DSA-87 Sign/Verify (Non-Streaming)
This Flow 6 example demonstrates:
- Generating a ML-DSA-87 key (post-quantum signature).
- Signing a file in non-streaming mode.
- Exporting the complete key data (JSON), which includes the public portion.
- 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.
/*
* Copyright 2025 ANKATech Solutions Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package co.ankatech.ankasecure.sdk.examples;
import co.ankatech.ankasecure.sdk.AuthenticatedSdk;
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.*;
/**
* Scenario 6 — ML-DSA-87 Bulk Helpers (Compact JWS).
*
* <p>This scenario signs and verifies a document with <strong>non-streaming</strong>
* helpers that operate on RFC 7515 <em>Compact JWS</em> tokens:</p>
* <ol>
* <li>Generate an <code>ML-DSA-87</code> key.</li>
* <li>Export its metadata.</li>
* <li>Create a Compact JWS on disk via {@link AuthenticatedSdk#signFile(String, Path, Path)}.</li>
* <li>Verify it via {@link AuthenticatedSdk#verifySignature(Path)}.</li>
* <li>Print server metadata and validate integrity.</li>
* </ol>
*
* <p><b>Implementation notes (Java 21+):</b></p>
* <ul>
* <li>Filesystem interactions rely on the {@link java.nio.file.Path} API.</li>
* <li>UTF-8 is enforced explicitly for deterministic encoding.</li>
* <li>Temporary artefacts reside under <kbd>temp_files/</kbd>.</li>
* </ul>
*
* <p><b>Thread-safety:</b> this class is stateless and immutable.</p>
*
* @author ANKATech Solutions Inc.
* @since 3.0.0
* @see ExampleUtil
* @see AuthenticatedSdk
*/
public final class ExampleScenario6 {
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario6() { }
/**
* Runs the ML-DSA-87 compact JWS sign/verify scenario.
*
* <p>Loads CLI properties, authenticates against ANKASecure©,
* and delegates to the scenario logic. On any unrecoverable error
* the JVM terminates via
* {@link ExampleUtil#fatal(String, Throwable)}.</p>
*
* @param args command-line arguments (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
* Non-streaming - token handled as text
Steps :
1) Generate ML-DSA-87 key
2) Export public metadata
3) Sign payload (compact JWS)
4) Verify signature
5) Validate integrity
--------------------------------------------------------------""");
try {
ensureTempDir(TEMP_DIR);
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
runScenario(sdk);
} catch (Exception ex) {
fatal("Scenario 6 failed", ex);
}
System.out.println("===== SCENARIO 6 END =====");
}
/**
* Executes the 5-step ML-DSA-87 sign/verify scenario.
*
* <ol>
* <li>Prepare document file.</li>
* <li>Generate ML-DSA-87 signing key.</li>
* <li>Export key metadata.</li>
* <li>Sign document (compact JWS).</li>
* <li>Verify signature and report result.</li>
* </ol>
*
* @param sdk an authenticated {@link AuthenticatedSdk} instance;
* must not be {@code null}
* @throws Exception if any step fails
*/
private static void runScenario(final AuthenticatedSdk 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] Signature valid? : {0}", verMeta.isValid()));
printVerifyMeta(verMeta);
System.out.println(verMeta.isValid()
? "[6] SUCCESS - signature verified."
: "[6] FAILURE - signature invalid.");
}
}
How to Run
- Copy this code to a Java file in your project (e.g.,
co/ankatech/ankasecure/sdk/examples/ExampleScenario6.java). - Compile (Maven, Gradle, or
javac). - Ensure
cli.propertiesis accessible (file or-Dcli.config). -
Run the
mainmethod, 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
Related Flows
- Flow 5 -- ML-KEM512 Encrypt/Decrypt (Non-Streaming)
- Flow 7 -- AES-256 Compact-JWE Encrypt / Decrypt (Non-Streaming)
- Integration Examples Overview
© 2025 AnkaTech. All rights reserved.