feat: SLH-DSA (FIPS 205) signature algorithm added

Signed-off-by: Leo Galambos <lg@hq.egothor.org>
This commit is contained in:
2025-12-25 01:54:24 +01:00
parent 4da4547a46
commit 8f228c7ada
11 changed files with 1684 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import zeroecho.core.AlgorithmFamily;
import zeroecho.core.KeyUsage;
import zeroecho.core.alg.AbstractCryptoAlgorithm;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.spec.VoidSpec;
/**
* SLH-DSA (FIPS 205) signature algorithm binding for the ZeroEcho framework.
*
* <p>
* SLH-DSA is the NIST-standardized profile of the SPHINCS+ stateless hash-based
* signature scheme. This binding exposes SLH-DSA as a first-class algorithm
* identity while relying on the provider's SLH-DSA JCA implementations.
* </p>
*
* <p>
* This algorithm registers two roles:
* </p>
* <ul>
* <li>{@link KeyUsage#SIGN}: produces SLH-DSA signatures using a
* {@link PrivateKey}.</li>
* <li>{@link KeyUsage#VERIFY}: verifies SLH-DSA signatures using a
* {@link PublicKey}.</li>
* </ul>
*
* <p>
* Both roles are configured with {@link VoidSpec}, as SLH-DSA requires no
* runtime context parameters beyond the key material.
* </p>
*
* @since 1.0
*/
public final class SlhDsaAlgorithm extends AbstractCryptoAlgorithm {
/**
* Creates a new SLH-DSA algorithm instance and registers its capabilities.
*
* @throws IllegalArgumentException if a signature context cannot be initialized
* due to provider errors
*/
public SlhDsaAlgorithm() {
super("SLH-DSA", "SLHDSA");
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.SIGN, SignatureContext.class, PrivateKey.class, VoidSpec.class,
(PrivateKey k, VoidSpec s) -> {
try {
return new SlhDsaSignatureContext(this, k);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init SLH-DSA signer", e);
}
}, () -> VoidSpec.INSTANCE);
capability(AlgorithmFamily.ASYMMETRIC, KeyUsage.VERIFY, SignatureContext.class, PublicKey.class, VoidSpec.class,
(PublicKey k, VoidSpec s) -> {
try {
return new SlhDsaSignatureContext(this, k);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Cannot init SLH-DSA verifier", e);
}
}, () -> VoidSpec.INSTANCE);
registerAsymmetricKeyBuilder(SlhDsaKeyGenSpec.class, new SlhDsaKeyGenBuilder(), SlhDsaKeyGenSpec::defaultSpec);
registerAsymmetricKeyBuilder(SlhDsaPublicKeySpec.class, new SlhDsaPublicKeyBuilder(), null);
registerAsymmetricKeyBuilder(SlhDsaPrivateKeySpec.class, new SlhDsaPrivateKeyBuilder(), null);
}
}

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.lang.reflect.Field;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Locale;
import java.util.Objects;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* Key pair builder for SLH-DSA (FIPS 205) using the Bouncy Castle PQC provider.
*
* <p>
* This builder maps {@link SlhDsaKeyGenSpec} to the appropriate
* {@code org.bouncycastle.jcajce.spec.SLHDSAParameterSpec} constant.
* :contentReference[oaicite:3]{index=3} Reflection is used to avoid a hard
* dependency on any particular set of parameter constants across provider
* versions.
* </p>
*
* @since 1.0
*/
public final class SlhDsaKeyGenBuilder implements AsymmetricKeyBuilder<SlhDsaKeyGenSpec> {
private static final String ALG = "SLH-DSA";
@Override
public KeyPair generateKeyPair(SlhDsaKeyGenSpec spec) throws GeneralSecurityException {
Objects.requireNonNull(spec, "spec");
Object bcParamSpec = resolveBcParameterSpec(spec);
KeyPairGenerator kpg = (spec.providerName() == null) ? KeyPairGenerator.getInstance(ALG)
: KeyPairGenerator.getInstance(ALG, spec.providerName());
if (bcParamSpec != null) {
kpg.initialize((java.security.spec.AlgorithmParameterSpec) bcParamSpec);
}
return kpg.generateKeyPair();
}
@Override
public java.security.PublicKey importPublic(SlhDsaKeyGenSpec spec) {
throw new UnsupportedOperationException("Use SlhDsaPublicKeySpec to import a public key.");
}
@Override
public java.security.PrivateKey importPrivate(SlhDsaKeyGenSpec spec) {
throw new UnsupportedOperationException("Use SlhDsaPrivateKeySpec to import a private key.");
}
private static Object resolveBcParameterSpec(SlhDsaKeyGenSpec spec) throws GeneralSecurityException {
if (spec.explicitParamConstant() != null) {
Object c = fetchStaticField("org.bouncycastle.jcajce.spec.SLHDSAParameterSpec",
spec.explicitParamConstant());
if (c != null) {
return c;
}
throw new GeneralSecurityException("Unknown SLHDSAParameterSpec constant: " + spec.explicitParamConstant());
}
String fam = (spec.hash() == SlhDsaKeyGenSpec.Hash.SHA2) ? "sha2" : "shake";
String bits = Integer.toString(spec.security().bits);
String v = (spec.variant() == SlhDsaKeyGenSpec.Variant.FAST) ? "f" : "s";
// Base constant name: slh_dsa_{sha2|shake}_{128|192|256}{f|s}
String base = "slh_dsa_" + fam + "_" + bits + v;
// Optional pre-hash suffix used by BC:
// _with_sha256/_with_sha512/_with_shake128/_with_shake256
// :contentReference[oaicite:4]{index=4}
String suffix = "";
if (spec.preHash() != SlhDsaKeyGenSpec.PreHash.NONE) {
suffix = "_with_" + spec.preHash().name().toLowerCase(Locale.ROOT);
}
String name = base + suffix;
// Validate supported combinations (fail fast with a clear message).
validateCombination(spec);
Object c = fetchStaticField("org.bouncycastle.jcajce.spec.SLHDSAParameterSpec", name);
if (c != null) {
return c;
}
// As a fallback, attempt base (no pre-hash), then fail.
if (!suffix.isEmpty()) {
Object baseC = fetchStaticField("org.bouncycastle.jcajce.spec.SLHDSAParameterSpec", base);
if (baseC != null) {
return baseC;
}
}
throw new GeneralSecurityException("Cannot resolve SLHDSAParameterSpec constant: " + name);
}
private static void validateCombination(SlhDsaKeyGenSpec spec) throws GeneralSecurityException {
SlhDsaKeyGenSpec.Hash h = spec.hash();
int bits = spec.security().bits;
SlhDsaKeyGenSpec.PreHash p = spec.preHash();
if (p == SlhDsaKeyGenSpec.PreHash.NONE) {
return;
}
if (h == SlhDsaKeyGenSpec.Hash.SHA2) {
if (bits == 128 && p != SlhDsaKeyGenSpec.PreHash.SHA256) {
throw new GeneralSecurityException("SLH-DSA SHA2-128 supports only PreHash.SHA256.");
}
if ((bits == 192 || bits == 256) && p != SlhDsaKeyGenSpec.PreHash.SHA512) {
throw new GeneralSecurityException("SLH-DSA SHA2-192/256 support only PreHash.SHA512.");
}
} else {
if (bits == 128 && p != SlhDsaKeyGenSpec.PreHash.SHAKE128) {
throw new GeneralSecurityException("SLH-DSA SHAKE-128 supports only PreHash.SHAKE128.");
}
if ((bits == 192 || bits == 256) && p != SlhDsaKeyGenSpec.PreHash.SHAKE256) {
throw new GeneralSecurityException("SLH-DSA SHAKE-192/256 support only PreHash.SHAKE256.");
}
}
}
private static Object fetchStaticField(String className, String field) {
try {
Class<?> cls = Class.forName(className);
Field f = cls.getField(field);
return f.get(null);
} catch (ReflectiveOperationException e) {
return null;
}
}
}

View File

@@ -0,0 +1,249 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.util.Objects;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Specification for generating SLH-DSA key pairs (FIPS 205).
*
* <p>
* SLH-DSA is parameterized by hash family (SHA2 or SHAKE), a security strength
* (128/192/256), and a variant (fast vs small). Additionally, Bouncy Castle
* exposes pre-hash variants (e.g., "with SHA-256") as separate parameter specs.
* </p>
*
* <p>
* This spec intentionally restricts the available choices to SLH-DSA parameters
* (i.e., no Haraka and no SPHINCS+ "simple/robust" split).
* </p>
*
* @since 1.0
*/
public final class SlhDsaKeyGenSpec implements AlgorithmKeySpec {
/**
* SLH-DSA hash families (FIPS 205).
*/
public enum Hash {
/** SHA-2 based SLH-DSA parameter sets. */
SHA2,
/** SHAKE based SLH-DSA parameter sets. */
SHAKE
}
/**
* Security levels as defined by NIST PQC (L1, L3, L5).
*/
public enum Security {
/** NIST Level 1 (~128-bit security). */
L1_128(128),
/** NIST Level 3 (~192-bit security). */
L3_192(192),
/** NIST Level 5 (~256-bit security). */
L5_256(256);
/** Claimed security level in bits. */
public final int bits;
Security(int b) {
bits = b;
}
}
/**
* Variant trading performance against signature size.
*
* <p>
* {@code FAST} variants are optimized for speed (larger signatures).
* {@code SMALL} variants reduce signature size at some performance cost.
* </p>
*/
public enum Variant {
/** Larger, faster signatures. */
FAST,
/** Smaller, slower signatures. */
SMALL
}
/**
* Optional pre-hash variant selection.
*
* <p>
* Bouncy Castle exposes "with hash" variants as distinct parameter specs, for
* example {@code slh_dsa_sha2_128s_with_sha256}.
* :contentReference[oaicite:1]{index=1}
* </p>
*
* <p>
* Valid combinations depend on the hash family and security strength:
* </p>
* <ul>
* <li>SHA2-128 uses SHA-256</li>
* <li>SHA2-192/256 use SHA-512</li>
* <li>SHAKE-128 uses SHAKE128</li>
* <li>SHAKE-192/256 use SHAKE256</li>
* </ul>
*/
public enum PreHash {
/** No pre-hash parameter set (pure SLH-DSA). */
NONE,
/** Pre-hash with SHA-256 (only for SHA2-128). */
SHA256,
/** Pre-hash with SHA-512 (only for SHA2-192/256). */
SHA512,
/** Pre-hash with SHAKE128 (only for SHAKE-128). */
SHAKE128,
/** Pre-hash with SHAKE256 (only for SHAKE-192/256). */
SHAKE256
}
private static final SlhDsaKeyGenSpec DEFAULT = new SlhDsaKeyGenSpec("BC", Hash.SHAKE, Security.L5_256,
Variant.SMALL, PreHash.NONE, null);
private final String providerName;
private final Hash hash;
private final Security security;
private final Variant variant;
private final PreHash preHash;
private final String explicitParamConstant; // nullable
private SlhDsaKeyGenSpec(String providerName, Hash hash, Security security, Variant variant, PreHash preHash,
String explicitParamConstant) {
this.providerName = Objects.requireNonNull(providerName, "providerName");
this.hash = Objects.requireNonNull(hash, "hash");
this.security = Objects.requireNonNull(security, "security");
this.variant = Objects.requireNonNull(variant, "variant");
this.preHash = Objects.requireNonNull(preHash, "preHash");
this.explicitParamConstant = explicitParamConstant;
}
/**
* Returns the default SLH-DSA key generation spec.
*
* @return a singleton default specification (SHAKE, L5, SMALL, no pre-hash)
*/
public static SlhDsaKeyGenSpec defaultSpec() {
return DEFAULT;
}
/**
* Creates a new specification with explicit algorithm parameters.
*
* @param providerName JCA provider name (e.g., "BC", "BCPQC")
* @param hash hash family (SHA2 or SHAKE)
* @param security security level
* @param variant variant (FAST vs SMALL)
* @param preHash optional pre-hash selection
* @return a new {@code SlhDsaKeyGenSpec}
*/
public static SlhDsaKeyGenSpec of(String providerName, Hash hash, Security security, Variant variant,
PreHash preHash) {
return new SlhDsaKeyGenSpec(providerName, hash, security, variant, preHash, null);
}
/**
* Returns a copy of this specification with an explicit Bouncy Castle parameter
* constant override.
*
* <p>
* This bypasses automatic mapping. The name must match a static field in
* {@code org.bouncycastle.jcajce.spec.SLHDSAParameterSpec}.
* :contentReference[oaicite:2]{index=2}
* </p>
*
* @param name field name in {@code SLHDSAParameterSpec}
* @return a new {@code SlhDsaKeyGenSpec} with the override
*/
public SlhDsaKeyGenSpec withExplicitParamConstant(String name) {
return new SlhDsaKeyGenSpec(providerName, hash, security, variant, preHash, name);
}
/**
* Returns the provider name used for key generation.
*
* @return provider name (never {@code null})
*/
public String providerName() {
return providerName;
}
/**
* Returns the SLH-DSA hash family.
*
* @return hash family
*/
public Hash hash() {
return hash;
}
/**
* Returns the security level.
*
* @return security level
*/
public Security security() {
return security;
}
/**
* Returns the variant.
*
* @return variant
*/
public Variant variant() {
return variant;
}
/**
* Returns the pre-hash selection.
*
* @return pre-hash selection
*/
public PreHash preHash() {
return preHash;
}
/**
* Returns the explicit parameter constant override, if set.
*
* @return constant name, or {@code null} if automatic mapping is used
*/
public String explicitParamConstant() {
return explicitParamConstant;
}
}

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* Builder for importing SLH-DSA private keys from encoded specifications.
*
* @since 1.0
*/
public final class SlhDsaPrivateKeyBuilder implements AsymmetricKeyBuilder<SlhDsaPrivateKeySpec> {
private static final String ALG = "SLH-DSA";
@Override
public KeyPair generateKeyPair(SlhDsaPrivateKeySpec spec) {
throw new UnsupportedOperationException("Generation not supported by this spec.");
}
@Override
public PublicKey importPublic(SlhDsaPrivateKeySpec spec) {
throw new UnsupportedOperationException("Use SlhDsaPublicKeySpec for public keys.");
}
@Override
public PrivateKey importPrivate(SlhDsaPrivateKeySpec spec) throws GeneralSecurityException {
KeyFactory kf = (spec.providerName() == null) ? KeyFactory.getInstance(ALG)
: KeyFactory.getInstance(ALG, spec.providerName());
return kf.generatePrivate(new PKCS8EncodedKeySpec(spec.encoded()));
}
}

View File

@@ -0,0 +1,182 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Encoded representation of an SLH-DSA private key.
*
* <p>
* {@code SlhDsaPrivateKeySpec} is an immutable value object that wraps a
* PKCS#8-encoded SLH-DSA private key together with the JCA provider name that
* should be used when importing the key.
* </p>
*
* <h2>Encoding</h2>
* <ul>
* <li>The private key material is stored as a defensive copy of the provided
* PKCS#8 byte array.</li>
* <li>Marshalling/unmarshalling uses {@link PairSeq} with Base64 encoding under
* {@code "pkcs8.b64"}.</li>
* </ul>
*
* <h2>Provider selection</h2>
* <p>
* The default provider is {@code "BC"} because SLH-DSA is registered by the
* Bouncy Castle core provider (as opposed to the PQC-only provider).
* </p>
*
* <h2>Security considerations</h2>
* <ul>
* <li>All byte arrays returned by this class are defensive copies.</li>
* <li>This class performs no cryptographic operations.</li>
* <li>Callers must protect serialized private key material at rest and in
* transit.</li>
* </ul>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are immutable and therefore thread-safe.
* </p>
*
* @since 1.0
*/
public final class SlhDsaPrivateKeySpec implements AlgorithmKeySpec {
private final byte[] encodedPkcs8;
private final String providerName;
/**
* Creates a new specification using the default provider {@code "BC"}.
*
* @param encodedPkcs8 PKCS#8-encoded SLH-DSA private key bytes
* @throws IllegalArgumentException if {@code encodedPkcs8} is {@code null}
*/
public SlhDsaPrivateKeySpec(byte[] encodedPkcs8) {
this(encodedPkcs8, "BC");
}
/**
* Creates a new specification using the supplied provider.
*
* <p>
* If {@code providerName} is {@code null}, the default provider {@code "BC"} is
* used.
* </p>
*
* @param encodedPkcs8 PKCS#8-encoded SLH-DSA private key bytes
* @param providerName JCA provider name to use for import; may be {@code null}
* @throws IllegalArgumentException if {@code encodedPkcs8} is {@code null}
*/
public SlhDsaPrivateKeySpec(byte[] encodedPkcs8, String providerName) {
if (encodedPkcs8 == null) {
throw new IllegalArgumentException("encodedPkcs8 must not be null");
}
this.encodedPkcs8 = encodedPkcs8.clone();
this.providerName = (providerName == null ? "BC" : providerName);
}
/**
* Returns a defensive copy of the PKCS#8-encoded private key bytes.
*
* @return a copy of the encoded private key
*/
public byte[] encoded() {
return encodedPkcs8.clone();
}
/**
* Returns the provider name associated with this specification.
*
* @return provider name, never {@code null}
*/
public String providerName() {
return providerName;
}
/**
* Serializes the given spec into a {@link PairSeq}.
*
* <p>
* The encoded key is stored as Base64 without padding under
* {@code "pkcs8.b64"}. The provider is stored under {@code "provider"}.
* </p>
*
* @param spec the private key specification to serialize
* @return serialized representation
* @throws NullPointerException if {@code spec} is {@code null}
*/
public static PairSeq marshal(SlhDsaPrivateKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.encodedPkcs8);
return PairSeq.of("type", "SLHDSA-PRIV", "pkcs8.b64", b64, "provider", spec.providerName);
}
/**
* Deserializes a {@link SlhDsaPrivateKeySpec} from a {@link PairSeq}.
*
* <p>
* Expects key {@code "pkcs8.b64"} (required) and {@code "provider"} (optional;
* defaults to {@code "BC"}).
* </p>
*
* @param p serialized input
* @return reconstructed private key specification
* @throws IllegalArgumentException if {@code "pkcs8.b64"} is missing
* @throws NullPointerException if {@code p} is {@code null}
*/
public static SlhDsaPrivateKeySpec unmarshal(PairSeq p) {
byte[] out = null;
String prov = "BC";
PairSeq.Cursor c = p.cursor();
while (c.next()) {
String k = c.key();
String v = c.value();
switch (k) {
case "pkcs8.b64" -> out = Base64.getDecoder().decode(v);
case "provider" -> prov = v;
default -> {
}
}
}
if (out == null) {
throw new IllegalArgumentException("pkcs8.b64 missing for SLH-DSA private key");
}
return new SlhDsaPrivateKeySpec(out, prov);
}
}

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import zeroecho.core.spi.AsymmetricKeyBuilder;
/**
* Builder for importing SLH-DSA public keys from encoded specifications.
*
* @since 1.0
*/
public final class SlhDsaPublicKeyBuilder implements AsymmetricKeyBuilder<SlhDsaPublicKeySpec> {
private static final String ALG = "SLH-DSA";
@Override
public KeyPair generateKeyPair(SlhDsaPublicKeySpec spec) {
throw new UnsupportedOperationException("Generation not supported by this spec.");
}
@Override
public PublicKey importPublic(SlhDsaPublicKeySpec spec) throws GeneralSecurityException {
KeyFactory kf = (spec.providerName() == null) ? KeyFactory.getInstance(ALG)
: KeyFactory.getInstance(ALG, spec.providerName());
return kf.generatePublic(new X509EncodedKeySpec(spec.encoded()));
}
@Override
public PrivateKey importPrivate(SlhDsaPublicKeySpec spec) {
throw new UnsupportedOperationException("Use SlhDsaPrivateKeySpec for private keys.");
}
}

View File

@@ -0,0 +1,174 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.util.Base64;
import zeroecho.core.marshal.PairSeq;
import zeroecho.core.spec.AlgorithmKeySpec;
/**
* Encoded representation of an SLH-DSA public key.
*
* <p>
* {@code SlhDsaPublicKeySpec} is an immutable value object that wraps an X.509
* (SubjectPublicKeyInfo) encoded SLH-DSA public key together with the JCA
* provider name that should be used when importing the key.
* </p>
*
* <h2>Encoding</h2>
* <ul>
* <li>The public key bytes are stored as a defensive copy of the provided X.509
* byte array.</li>
* <li>Marshalling/unmarshalling uses {@link PairSeq} with Base64 encoding under
* {@code "x509.b64"}.</li>
* </ul>
*
* <h2>Provider selection</h2>
* <p>
* The default provider is {@code "BC"} because SLH-DSA is registered by the
* Bouncy Castle core provider.
* </p>
*
* <h2>Thread-safety</h2>
* <p>
* Instances are immutable and can be safely shared across threads.
* </p>
*
* @since 1.0
*/
public final class SlhDsaPublicKeySpec implements AlgorithmKeySpec {
private final byte[] encodedX509;
private final String providerName;
/**
* Creates a new specification using the default provider {@code "BC"}.
*
* @param encodedX509 X.509-encoded SLH-DSA public key bytes
* @throws IllegalArgumentException if {@code encodedX509} is {@code null}
*/
public SlhDsaPublicKeySpec(byte[] encodedX509) {
this(encodedX509, "BC");
}
/**
* Creates a new specification using the supplied provider.
*
* <p>
* If {@code providerName} is {@code null}, the default provider {@code "BC"} is
* used.
* </p>
*
* @param encodedX509 X.509-encoded SLH-DSA public key bytes
* @param providerName JCA provider name to use for import; may be {@code null}
* @throws IllegalArgumentException if {@code encodedX509} is {@code null}
*/
public SlhDsaPublicKeySpec(byte[] encodedX509, String providerName) {
if (encodedX509 == null) {
throw new IllegalArgumentException("encodedX509 must not be null");
}
this.encodedX509 = encodedX509.clone();
this.providerName = (providerName == null ? "BC" : providerName);
}
/**
* Returns a defensive copy of the X.509-encoded public key bytes.
*
* @return a copy of the encoded public key
*/
public byte[] encoded() {
return encodedX509.clone();
}
/**
* Returns the provider name associated with this specification.
*
* @return provider name, never {@code null}
*/
public String providerName() {
return providerName;
}
/**
* Serializes the given spec into a {@link PairSeq}.
*
* <p>
* The encoded key is stored as Base64 without padding under {@code "x509.b64"}.
* The provider is stored under {@code "provider"}.
* </p>
*
* @param spec the public key specification to serialize
* @return serialized representation
* @throws NullPointerException if {@code spec} is {@code null}
*/
public static PairSeq marshal(SlhDsaPublicKeySpec spec) {
String b64 = Base64.getEncoder().withoutPadding().encodeToString(spec.encodedX509);
return PairSeq.of("type", "SLHDSA-PUB", "x509.b64", b64, "provider", spec.providerName);
}
/**
* Deserializes a {@link SlhDsaPublicKeySpec} from a {@link PairSeq}.
*
* <p>
* Expects key {@code "x509.b64"} (required) and {@code "provider"} (optional;
* defaults to {@code "BC"}).
* </p>
*
* @param p serialized input
* @return reconstructed public key specification
* @throws IllegalArgumentException if {@code "x509.b64"} is missing
* @throws NullPointerException if {@code p} is {@code null}
*/
public static SlhDsaPublicKeySpec unmarshal(PairSeq p) {
byte[] out = null;
String prov = "BC";
PairSeq.Cursor c = p.cursor();
while (c.next()) {
String k = c.key();
String v = c.value();
switch (k) {
case "x509.b64" -> out = Base64.getDecoder().decode(v);
case "provider" -> prov = v;
default -> {
}
}
}
if (out == null) {
throw new IllegalArgumentException("x509.b64 missing for SLH-DSA public key");
}
return new SlhDsaPublicKeySpec(out, prov);
}
}

View File

@@ -0,0 +1,283 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core.alg.slhdsa;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bouncycastle.jcajce.interfaces.SLHDSAPublicKey;
import org.bouncycastle.jcajce.spec.SLHDSAParameterSpec;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.alg.common.sig.GenericJcaSignatureContext;
import zeroecho.core.context.SignatureContext;
import zeroecho.core.tag.ThrowingBiPredicate.VerificationBiPredicate;
/**
* Streaming signature context for SLH-DSA (FIPS 205).
*
* <p>
* {@code SlhDsaSignatureContext} adapts a JCA {@link Signature} engine for use
* within the ZeroEcho streaming signature infrastructure. It supports both
* signing and verification and delegates the low-level mechanics to
* {@link GenericJcaSignatureContext}.
* </p>
*
* <h2>Provider and algorithm</h2>
* <ul>
* <li>JCA algorithm: {@code "SLH-DSA"}.</li>
* <li>Provider: {@code "BC"} (Bouncy Castle core provider).</li>
* </ul>
*
* <h2>Streaming contract</h2>
* <ul>
* <li><b>SIGN</b>: the wrapped stream emits the message body and appends a
* detached signature trailer at EOF.</li>
* <li><b>VERIFY</b>: the wrapped stream emits the body only; verification is
* performed at EOF against a caller-supplied expected tag.</li>
* </ul>
*
* <h2>Security</h2>
* <p>
* This class never logs secrets, key material, plaintext, or signature bytes.
* </p>
*
* @since 1.0
*/
public final class SlhDsaSignatureContext implements SignatureContext {
private static final String ALG = "SLH-DSA";
private static final String PROVIDER = "BC";
private static final Pattern PARAM_PATTERN = Pattern
.compile("^slh-dsa-(sha2|shake)-(128|192|256)([fs])(?:-with-[a-z0-9]+)?$");
private final GenericJcaSignatureContext delegate;
/**
* Creates a signing context bound to a private key.
*
* <p>
* The produced signature length is resolved by probing the JCA engine via
* {@link GenericJcaSignatureContext.SignLengthResolver#probeWith(String, String)}.
* </p>
*
* @param algorithm the parent algorithm instance; must not be {@code null}
* @param privateKey SLH-DSA private key; must not be {@code null}
* @throws GeneralSecurityException if the JCA signature engine cannot be
* initialized
*/
public SlhDsaSignatureContext(final CryptoAlgorithm algorithm, final PrivateKey privateKey)
throws GeneralSecurityException {
this.delegate = new GenericJcaSignatureContext(algorithm, privateKey,
GenericJcaSignatureContext.jcaFactory(ALG, PROVIDER),
GenericJcaSignatureContext.SignLengthResolver.probeWith(ALG, PROVIDER));
}
/**
* Creates a verification context bound to a public key.
*
* <p>
* The expected signature length is derived from the public key parameter set
* via {@link SLHDSAParameterSpec#getName()} and the canonical SLH-DSA sizes.
* </p>
*
* @param algorithm the parent algorithm instance; must not be {@code null}
* @param publicKey SLH-DSA public key; must not be {@code null}
* @throws GeneralSecurityException if the key is invalid or the parameter set
* is unsupported
*/
public SlhDsaSignatureContext(final CryptoAlgorithm algorithm, final PublicKey publicKey)
throws GeneralSecurityException {
this.delegate = new GenericJcaSignatureContext(algorithm, publicKey,
GenericJcaSignatureContext.jcaFactory(ALG, PROVIDER), SlhDsaSignatureContext::sigLenFromPublicKey);
}
/**
* Resolves the canonical SLH-DSA signature length (bytes) from a public key.
*
* <p>
* The Bouncy Castle public key exposes an {@link SLHDSAParameterSpec} whose
* {@linkplain SLHDSAParameterSpec#getName() name} encodes the family
* (SHA2/SHAKE), security level (128/192/256) and variant (s/f). The resolver
* normalizes the returned name to lowercase and replaces underscores with
* hyphens to tolerate provider naming differences.
* </p>
*
* @param pk SLH-DSA public key
* @return signature length in bytes for the key's parameter set
* @throws GeneralSecurityException if the key type or parameter specification
* is missing or unrecognized
*/
private static int sigLenFromPublicKey(PublicKey pk) throws GeneralSecurityException {
if (!(pk instanceof SLHDSAPublicKey slhPk)) {
throw new GeneralSecurityException("Expected a BouncyCastle SLH-DSA public key (BC)");
}
SLHDSAParameterSpec ps = slhPk.getParameterSpec();
if (ps == null) {
throw new GeneralSecurityException("Missing SLH-DSA parameter spec on public key");
}
String name = ps.getName();
if (name == null || name.isEmpty()) {
throw new GeneralSecurityException("Unknown SLH-DSA parameter (no name)");
}
String normalized = name.toLowerCase(Locale.ROOT).replace('_', '-');
Matcher m = PARAM_PATTERN.matcher(normalized);
if (!m.matches()) {
throw new GeneralSecurityException("Cannot parse SLH-DSA parameter from: " + name);
}
int level = Integer.parseInt(m.group(2));
char var = m.group(3).charAt(0);
boolean isSmall = var == 's';
return switch (level) {
case 128 -> isSmall ? 7_856 : 17_088;
case 192 -> isSmall ? 16_224 : 35_664;
case 256 -> isSmall ? 29_792 : 49_856;
default -> throw new GeneralSecurityException("Unsupported SLH-DSA level: " + level);
};
}
/**
* Returns the parent {@link CryptoAlgorithm} associated with this context.
*
* @return the algorithm instance
*/
@Override
public CryptoAlgorithm algorithm() {
return delegate.algorithm();
}
/**
* Returns the key bound to this context.
*
* @return signing {@link PrivateKey} or verification {@link PublicKey}
*/
@Override
public java.security.Key key() {
return delegate.key();
}
/**
* Closes this context and releases any underlying resources.
*
* <p>
* Once closed, the context must not be reused.
* </p>
*/
@Override
public void close() {
delegate.close();
}
/**
* Wraps an input stream such that bytes read from the returned stream update
* the underlying signature engine.
*
* <p>
* In SIGN mode, the wrapper appends the signature trailer at EOF. In VERIFY
* mode, the wrapper compares the computed signature at EOF against the expected
* tag configured via {@link #setExpectedTag(byte[])}.
* </p>
*
* @param upstream input stream providing message bytes; must not be
* {@code null}
* @return wrapped stream that performs signing or verification as bytes are
* read
* @throws IOException if wrapping fails
*/
@Override
public InputStream wrap(InputStream upstream) throws IOException {
return delegate.wrap(upstream);
}
/**
* Returns the signature (tag) length in bytes for the parameter set in use.
*
* @return signature length in bytes
*/
@Override
public int tagLength() {
return delegate.tagLength();
}
/**
* Supplies the expected signature (tag) for VERIFY mode.
*
* <p>
* Implementations may defensively copy the provided array. Callers should treat
* this value as sensitive and avoid logging or persisting it unless explicitly
* required by the application.
* </p>
*
* @param expected expected signature bytes; must not be {@code null}
*/
@Override
public void setExpectedTag(byte[] expected) {
delegate.setExpectedTag(expected);
}
/**
* Sets the verification approach used at EOF to compare the computed and
* expected signatures.
*
* @param strategy verification predicate; must not be {@code null}
*/
@Override
public void setVerificationApproach(VerificationBiPredicate<Signature> strategy) {
delegate.setVerificationApproach(strategy);
}
/**
* Returns the verification predicate core that can be used to select a
* particular mismatch handling strategy (e.g., throw on mismatch).
*
* @return the verification predicate core
*/
@Override
public VerificationBiPredicate<Signature> getVerificationCore() {
return delegate.getVerificationCore();
}
}

View File

@@ -0,0 +1,121 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
/**
* SLH-DSA (FIPS&nbsp;205) signature algorithm binding.
*
* <p>
* This package provides a ZeroEcho binding for <em>SLH-DSA</em>, the
* NIST-standardized profile of the stateless hash-based signature scheme
* SPHINCS+. While SLH-DSA is derived from SPHINCS+, it is exposed here as a
* distinct algorithm identity with a restricted, standards-compliant parameter
* space.
* </p>
*
* <h2>Algorithm identity</h2>
* <ul>
* <li>JCA algorithm name: {@code "SLH-DSA"}</li>
* <li>ZeroEcho algorithm id: {@code "SLHDSA"}</li>
* <li>Provider: Bouncy Castle core provider ({@code "BC"})</li>
* </ul>
*
* <h2>Supported parameter space</h2>
* <p>
* The supported parameters correspond exactly to the SLH-DSA profiles defined
* by FIPS&nbsp;205:
* </p>
* <ul>
* <li>Hash families: SHA2 and SHAKE</li>
* <li>Security levels: 128, 192, 256 bits (NIST levels L1, L3, L5)</li>
* <li>Variants: {@code FAST} (larger, faster signatures) and {@code SMALL}
* (smaller, slower signatures)</li>
* <li>Optional pre-hash variants as defined by the standard and exposed by the
* provider</li>
* </ul>
*
* <p>
* Non-standard SPHINCS+ variants (e.g. Haraka, simple/robust modes) are
* intentionally excluded from this package.
* </p>
*
* <h2>Key management</h2>
* <ul>
* <li>Key generation is configured via
* {@link zeroecho.core.alg.slhdsa.SlhDsaKeyGenSpec} and performed by
* {@link zeroecho.core.alg.slhdsa.SlhDsaKeyGenBuilder}.</li>
* <li>Encoded public and private keys are represented by
* {@link zeroecho.core.alg.slhdsa.SlhDsaPublicKeySpec} and
* {@link zeroecho.core.alg.slhdsa.SlhDsaPrivateKeySpec}.</li>
* <li>All key specifications are immutable and use defensive copies.</li>
* </ul>
*
* <h2>Streaming signature model</h2>
* <p>
* Signatures are processed through the ZeroEcho streaming signature
* infrastructure:
* </p>
* <ul>
* <li>In {@link zeroecho.core.KeyUsage#SIGN} mode, the signature is appended as
* a fixed-length trailer to the output stream.</li>
* <li>In {@link zeroecho.core.KeyUsage#VERIFY} mode, the wrapped stream emits
* only the message body; verification is performed at end-of-stream against a
* caller-supplied expected signature.</li>
* </ul>
*
* <p>
* The canonical signature length is derived from the public key parameters by
* {@link zeroecho.core.alg.slhdsa.SlhDsaSignatureContext} and matches the
* standard SLH-DSA signature sizes defined by FIPS&nbsp;205.
* </p>
*
* <h2>Security considerations</h2>
* <ul>
* <li>No sensitive material (private keys, signatures, message contents) is
* logged or exposed by this package.</li>
* <li>All byte arrays returned to callers are defensive copies.</li>
* <li>Verification failures are surfaced exclusively via the configured
* verification strategy.</li>
* </ul>
*
* <h2>Relationship to SPHINCS+</h2>
* <p>
* The {@code slhdsa} package represents the standardized SLH-DSA profile only.
* A separate {@code sphincsplus} package may expose the full SPHINCS+ design
* space and experimental variants. No API-level equivalence between the two
* packages is assumed.
* </p>
*
* @since 1.0
*/
package zeroecho.core.alg.slhdsa;

View File

@@ -19,5 +19,6 @@ zeroecho.core.alg.ntruprime.NtrulPrimeAlgorithm
zeroecho.core.alg.ntruprime.SntruPrimeAlgorithm
zeroecho.core.alg.rsa.RsaAlgorithm
zeroecho.core.alg.saber.SaberAlgorithm
zeroecho.core.alg.slhdsa.SlhDsaAlgorithm
zeroecho.core.alg.sphincsplus.SphincsPlusAlgorithm
zeroecho.core.alg.xdh.XdhAlgorithm