forked from I2P_Developers/i2p.i2p
Draft: WIP: Preliminary implementation of proposal 169
This MR is for review and testing only. Do not merge. The final code may be split up into multiple MRs (for example one for MLDSA and one for MLKEM) over the next 6-12 months, and will also start with a revision containing unmodified bouncycastle code as a base for future merges. This MR bundles code from bouncycastle 1.80 (license: MIT), modified to reduce and eliminate unneeded dependencies. There is no support for building with an external bouncycastle jar for Debian yet, as 1.80 is only in sid. The MLDSA code is in core and the MLKEM code is in router. Small wrappers are provided to bridge between our data structures and the bouncycastle API. Implemented portions of proposal 169, briefly tested unless otherwise noted: - SigType/EncType/SigAlgo/EncAlgo enums for new types - KeyGenerator and DSAEngine support - Hybrid handshake support in Noise - SHA3-128, SHA3-256, SHAKE128, SHAKE256 - All three MLKEM variants 512/768/1024 (only 512 tested) - All six MLDSA variants 44/56/87 hybrid/PQ-only - MLKEM hybrid ratchet - MLKEM types in leasesets - MLDSA and hybrid signatures for destinations and private key files - MLDSA and hybrid signature handling in streaming - MLKEM-only and muxed destination types (5; 6; 7; 5,4; 6,4; 7,4) (muxed untested, probably broken) - i2ptunnel UI support for selecting new types (requires routerconsole.advanced=true) - Addressbook support for MLDSA dests - Repliable datagram support (untested) - SAM support (untested) Live network test considerations: MLKEM hybrid ratchet is backward compatible with the current network and may be tested today, either with MLKEM-only or MLKEM + X25519 dual support. MLDSA destinations may be used for clients and for connections to compatible servers running this code. MLDSA server destinations cannot be tested (except in loopback) because their leasesets cannot be published to floodfills, as the floodfills cannot verify the signatures. Unimplemented portions of proposal 169: - MLKEM or MLDSA routers - NTCP2 mods for routers - SSU2 mods for routers - MLDSA or hybrid-signed SU3 files (ph variant) TODO: - Test vectors, unit tests - MLKEM KeyFactory threads - Debian bouncycastle dependency Standalone tests: A standalone ratchet test with hybrid support is in i2p.scripts java-utils/RatchetTest.java Private key files and leasesets for MLDSA and hybrid signatures are linked from http://zzz.i2p/topics/3294
This commit is contained in:
16
LICENSE.txt
16
LICENSE.txt
@@ -100,6 +100,10 @@ Public domain except as listed below:
|
||||
- Apache Software licence (see licenses/LICENSE-Apache2.0.txt)
|
||||
- DWTFYWTPL
|
||||
|
||||
ML-DSA:
|
||||
Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
|
||||
See licenses/LICENSE-Bouncycastle.txt
|
||||
|
||||
|
||||
Router (router.jar):
|
||||
Public domain except as listed below:
|
||||
@@ -128,6 +132,10 @@ Public domain except as listed below:
|
||||
Copyright (C) 2006 The Android Open Source Project
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
ML-KEM:
|
||||
Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
|
||||
See licenses/LICENSE-Bouncycastle.txt
|
||||
|
||||
|
||||
Installer:
|
||||
(not included in distribution packages)
|
||||
@@ -182,10 +190,10 @@ Launchers:
|
||||
Copyright (c) 2002-2018 EPFL, Lausanne / Lightbend, Inc. , unless otherwise specified.
|
||||
See licenses/LICENSE-Scala.md
|
||||
|
||||
Java Service Wrapper Community Edition 3.5.44:
|
||||
Java Service Wrapper Community Edition 3.5.60:
|
||||
(Windows: 3.5.25)
|
||||
(not included in most distribution packages)
|
||||
Copyright (C) 1999-2020 Tanuki Software, Ltd. All Rights Reserved.
|
||||
Copyright (C) 1999-2024 Tanuki Software, Ltd. All Rights Reserved.
|
||||
See licenses/LICENSE-Wrapper.txt
|
||||
|
||||
|
||||
@@ -316,6 +324,10 @@ Applications:
|
||||
Copyright (c) 2013-2023 David J. Bradshaw
|
||||
See licenses/LICENSE-Iframe-resizer.txt
|
||||
|
||||
Router Console country coordinates:
|
||||
Adapted from Google Dataset Publishing Language to convert to Mercator
|
||||
CC BY 4.0 https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
SAM (sam.jar):
|
||||
Public domain.
|
||||
|
||||
|
@@ -596,7 +596,45 @@
|
||||
<option title="<%=intl._t("This is the default, recommended option")%>" value="7" <%=(currentSigType==7 ? " selected=\"selected\"" : disabled)%> >
|
||||
Ed25519-SHA-512</option>
|
||||
<%
|
||||
} // isAvailable
|
||||
}
|
||||
if (editBean.isAdvanced()) {
|
||||
if (editBean.isSigTypeAvailable(12)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="12" <%=(currentSigType==12 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA44</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(13)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="13" <%=(currentSigType==13 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA65</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(14)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="14" <%=(currentSigType==14 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA87</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(15)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="15" <%=(currentSigType==15 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA44_Ed25519</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(16)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="16" <%=(currentSigType==16 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA65_Ed25519</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(17)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="17" <%=(currentSigType==17 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA87_Ed25519</option>
|
||||
<%
|
||||
} // isAvailable
|
||||
} // isAdvanced
|
||||
%>
|
||||
</select>
|
||||
</td>
|
||||
@@ -604,6 +642,9 @@
|
||||
<%
|
||||
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
||||
boolean has4 = editBean.hasEncType(curTunnel, 4);
|
||||
boolean has5 = editBean.hasEncType(curTunnel, 5);
|
||||
boolean has6 = editBean.hasEncType(curTunnel, 6);
|
||||
boolean has7 = editBean.hasEncType(curTunnel, 7);
|
||||
%>
|
||||
<tr>
|
||||
<th colspan="2" <%=ehdisabled%>>
|
||||
@@ -617,7 +658,25 @@
|
||||
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
||||
ECIES-X25519</option>
|
||||
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
<%=intl._t("Both encryption types")%></option>
|
||||
ECIES-X25519 + ElGamal-2048</option>
|
||||
<%
|
||||
if (editBean.isAdvanced()) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="5" <%=((has5 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM512-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="6" <%=((has6 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM768-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="7" <%=((has7 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM1024-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="5,4" <%=((has5 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM512-X25519 + ECIES-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="6,4" <%=((has6 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM768-X25519 + ECIES-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="7,4" <%=((has7 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM1024-X25519 + ECIES-X25519</option>
|
||||
<%
|
||||
} // isAdvanced()
|
||||
%>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -668,7 +668,46 @@
|
||||
if (editBean.isSigTypeAvailable(11)) { %>
|
||||
<option title="<%=intl._t("Recommended for blinded and encrypted destinations")%>" value="11" <%=(currentSigType==11 ? " selected=\"selected\"" : disabled)%> >
|
||||
Red25519-SHA-512</option>
|
||||
<% } // isAvailable
|
||||
<%
|
||||
}
|
||||
if (editBean.isAdvanced()) {
|
||||
if (editBean.isSigTypeAvailable(12)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="12" <%=(currentSigType==12 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA44</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(13)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="13" <%=(currentSigType==13 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA65</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(14)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="14" <%=(currentSigType==14 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA87</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(15)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="15" <%=(currentSigType==15 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA44_Ed25519</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(16)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="16" <%=(currentSigType==16 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA65_Ed25519</option>
|
||||
<%
|
||||
}
|
||||
if (editBean.isSigTypeAvailable(17)) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="17" <%=(currentSigType==17 ? " selected=\"selected\"" : disabled)%> >
|
||||
MLDSA87_Ed25519</option>
|
||||
<%
|
||||
} // isAvailable
|
||||
} // isAdvanced
|
||||
%>
|
||||
</select>
|
||||
</td>
|
||||
@@ -676,6 +715,9 @@
|
||||
<%
|
||||
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
||||
boolean has4 = editBean.hasEncType(curTunnel, 4);
|
||||
boolean has5 = editBean.hasEncType(curTunnel, 5);
|
||||
boolean has6 = editBean.hasEncType(curTunnel, 6);
|
||||
boolean has7 = editBean.hasEncType(curTunnel, 7);
|
||||
String edisabled = canChangeEncType ? "" : " disabled=\"disabled\" ";
|
||||
%>
|
||||
<tr>
|
||||
@@ -690,7 +732,25 @@
|
||||
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
||||
ECIES-X25519</option>
|
||||
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
<%=intl._t("Both encryption types")%></option>
|
||||
ECIES-X25519 + ElGamal-2048</option>
|
||||
<%
|
||||
if (editBean.isAdvanced()) {
|
||||
%>
|
||||
<option title="<%=intl._t("experimental")%>" value="5" <%=((has5 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM512-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="6" <%=((has6 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM768-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="7" <%=((has7 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM1024-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="5,4" <%=((has5 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM512-X25519 + ECIES-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="6,4" <%=((has6 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM768-X25519 + ECIES-X25519</option>
|
||||
<option title="<%=intl._t("experimental")%>" value="7,4" <%=((has7 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||
MLKEM1024-X25519 + ECIES-X25519</option>
|
||||
<%
|
||||
} // isAdvanced()
|
||||
%>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -867,9 +867,9 @@
|
||||
additionalparam="-notimestamp"
|
||||
doctitle="I2P Javadocs for Release ${release.number} Build ${i2p.build.number}${build.qualifier}${build.extra} (API ${api.version})"
|
||||
windowtitle="I2P Anonymous Network - Java Documentation - API Version ${api.version}">
|
||||
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.i2p.apache.http.conn.ssl:net.i2p.apache.http.conn.util:net.i2p.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20:org.minidns:org.minidns.*" />
|
||||
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.i2p.apache.http.conn.ssl:net.i2p.apache.http.conn.util:net.i2p.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20:org.minidns:org.minidns.*:org.bouncycastle:org.bouncycastle.*" />
|
||||
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
|
||||
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*" />
|
||||
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*:org.bouncycastle.pqc.crypto.mlkem" />
|
||||
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.web.*:net.i2p.router.update:edu.internet2.ndt:net.i2p.router.news:com.vuze.*" />
|
||||
<!-- apps and bridges starting here, alphabetical please -->
|
||||
<group title="Addressbook Application" packages="net.i2p.addressbook:net.i2p.router.naming:net.metanotion:net.metanotion.*" />
|
||||
|
@@ -21,6 +21,7 @@ import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -95,29 +96,13 @@ public class I2PClientImpl implements I2PClient {
|
||||
*/
|
||||
public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException {
|
||||
Destination d = new Destination();
|
||||
// Don't generate ElGamal keys anymore, they are unused since release 0.6 2005
|
||||
//Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair();
|
||||
//PublicKey publicKey = (PublicKey) keypair[0];
|
||||
//PrivateKey privateKey = (PrivateKey) keypair[1];
|
||||
// repeating pattern to be used in pubkey and padding, so the
|
||||
// destination will be compressible
|
||||
byte[] rand = new byte[PADDING_ENTROPY];
|
||||
RandomSource.getInstance().nextBytes(rand);
|
||||
byte[] pk = new byte[PublicKey.KEYSIZE_BYTES];
|
||||
for (int i = 0; i < pk.length; i += PADDING_ENTROPY) {
|
||||
System.arraycopy(rand, 0, pk, i, Math.min(PADDING_ENTROPY, pk.length - i));
|
||||
}
|
||||
PublicKey publicKey = new PublicKey(pk);
|
||||
// Unused private key.
|
||||
// Could be all zeros, but make it random so SAM doesn't show a string of AAAA
|
||||
byte[] prk = new byte[PrivateKey.KEYSIZE_BYTES];
|
||||
RandomSource.getInstance().nextBytes(prk);
|
||||
PrivateKey privateKey = new PrivateKey(prk);
|
||||
|
||||
SimpleDataStructure signingKeys[];
|
||||
boolean isPQ;
|
||||
if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
KeyCertificate kcert = cert.toKeyCertificate();
|
||||
SigType type = kcert.getSigType();
|
||||
isPQ = type.isPQ();
|
||||
try {
|
||||
signingKeys = KeyGenerator.getInstance().generateSigningKeys(type);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@@ -125,7 +110,35 @@ public class I2PClientImpl implements I2PClient {
|
||||
}
|
||||
} else {
|
||||
signingKeys = KeyGenerator.getInstance().generateSigningKeys();
|
||||
isPQ = false;
|
||||
}
|
||||
|
||||
// Don't generate ElGamal keys anymore, they are unused since release 0.6 2005
|
||||
//Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair();
|
||||
//PublicKey publicKey = (PublicKey) keypair[0];
|
||||
//PrivateKey privateKey = (PrivateKey) keypair[1];
|
||||
// repeating pattern to be used in pubkey and padding, so the
|
||||
// destination will be compressible
|
||||
byte[] rand;
|
||||
PublicKey publicKey;
|
||||
if (isPQ) {
|
||||
rand = null;
|
||||
publicKey = PublicKey.NULL;
|
||||
} else {
|
||||
rand = new byte[PADDING_ENTROPY];
|
||||
RandomSource.getInstance().nextBytes(rand);
|
||||
byte[] pk = new byte[PublicKey.KEYSIZE_BYTES];
|
||||
for (int i = 0; i < pk.length; i += PADDING_ENTROPY) {
|
||||
System.arraycopy(rand, 0, pk, i, Math.min(PADDING_ENTROPY, pk.length - i));
|
||||
}
|
||||
publicKey = new PublicKey(pk);
|
||||
}
|
||||
// Unused private key.
|
||||
// Could be all zeros, but make it random so SAM doesn't show a string of AAAA
|
||||
byte[] prk = new byte[PrivateKey.KEYSIZE_BYTES];
|
||||
RandomSource.getInstance().nextBytes(prk);
|
||||
PrivateKey privateKey = new PrivateKey(prk);
|
||||
|
||||
SigningPublicKey signingPubKey = (SigningPublicKey) signingKeys[0];
|
||||
SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeys[1];
|
||||
d.setPublicKey(publicKey);
|
||||
@@ -135,7 +148,9 @@ public class I2PClientImpl implements I2PClient {
|
||||
KeyCertificate kcert = cert.toKeyCertificate();
|
||||
SigType type = kcert.getSigType();
|
||||
int len = type.getPubkeyLen();
|
||||
if (len < 128) {
|
||||
if (isPQ) {
|
||||
System.arraycopy(signingPubKey.getData(), 384, kcert.getPayload(), KeyCertificate.HEADER_LENGTH, len - 384);
|
||||
} else if (len < 128) {
|
||||
int padLen = 128 - len;
|
||||
byte[] pad = new byte[padLen];
|
||||
// pad with the same pattern as in the public key
|
||||
|
@@ -490,6 +490,8 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
Properties rv = new Properties();
|
||||
for (String key : options.stringPropertyNames()) {
|
||||
if (key.startsWith("java.") ||
|
||||
key.startsWith("javax.") ||
|
||||
(key.startsWith("i2p.streaming.") && !key.equals("i2p.streaming.profile")) ||
|
||||
key.startsWith("user.") ||
|
||||
key.startsWith("os.") ||
|
||||
key.startsWith("sun.") ||
|
||||
@@ -596,9 +598,14 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
// but i2pd does, and also, this code is used from PrivateKeyFile to
|
||||
// read in router.keys.dat files and we will support EncTypes for RouterIdentities
|
||||
EncType etype = _myDestination.getPublicKey().getType();
|
||||
if (etype != EncType.ELGAMAL_2048)
|
||||
if (etype == EncType.ELGAMAL_2048) {
|
||||
_privateKey.readBytes(destKeyStream);
|
||||
} else if (etype == EncType.NONE) {
|
||||
DataHelper.skip(destKeyStream, 256);
|
||||
} else {
|
||||
_privateKey = new PrivateKey(etype);
|
||||
_privateKey.readBytes(destKeyStream);
|
||||
_privateKey.readBytes(destKeyStream);
|
||||
}
|
||||
SigType dtype = _myDestination.getSigningPublicKey().getType();
|
||||
_signingPrivateKey = new SigningPrivateKey(dtype);
|
||||
_signingPrivateKey.readBytes(destKeyStream);
|
||||
|
@@ -45,6 +45,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||
import net.i2p.crypto.eddsa.EdDSAKey;
|
||||
import net.i2p.crypto.eddsa.RedDSAEngine;
|
||||
import net.i2p.crypto.pqc.MLDSA;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
@@ -109,7 +110,7 @@ public final class DSAEngine {
|
||||
try {
|
||||
rv = altVerifySig(signature, signedData, offset, size, verifyingKey);
|
||||
if ((!rv) && _log.shouldLog(Log.WARN))
|
||||
_log.warn(type + " Sig Verify Fail");
|
||||
_log.warn(type + " Sig Verify Fail", new Exception());
|
||||
return rv;
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -516,6 +517,8 @@ public final class DSAEngine {
|
||||
throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType());
|
||||
if (type == SigType.DSA_SHA1)
|
||||
return altVerifySigSHA1(signature, data, offset, len, verifyingKey);
|
||||
if (type.isPQ())
|
||||
return MLDSA.verify(signature, data, offset, len, verifyingKey);
|
||||
|
||||
PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
|
||||
byte[] sigbytes = SigUtil.toJavaSig(signature);
|
||||
@@ -619,6 +622,8 @@ public final class DSAEngine {
|
||||
SigType type = privateKey.getType();
|
||||
if (type == SigType.DSA_SHA1)
|
||||
return altSignSHA1(data, offset, len, privateKey);
|
||||
if (type.isPQ())
|
||||
return MLDSA.sign(data, offset, len, privateKey);
|
||||
|
||||
PrivateKey privKey = SigUtil.toJavaKey(privateKey);
|
||||
byte[] sigbytes;
|
||||
|
@@ -11,7 +11,16 @@ public enum EncAlgo {
|
||||
EC("EC"),
|
||||
|
||||
/** @since 0.9.38 */
|
||||
ECIES("ECIES");
|
||||
ECIES("ECIES"),
|
||||
|
||||
/** @since 0.9.80 */
|
||||
ECIES_MLKEM("ECIES-MLKEM"),
|
||||
|
||||
/** @since 0.9.80 */
|
||||
ECIES_MLKEM_INT("ECIES-MLKEM-Internal"),
|
||||
|
||||
NONE("NONE");
|
||||
|
||||
|
||||
private final String name;
|
||||
|
||||
|
@@ -52,14 +52,91 @@ public enum EncType {
|
||||
* Pubkey 32 bytes; privkey 32 bytes
|
||||
* @since 0.9.38
|
||||
*/
|
||||
ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38");
|
||||
ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38"),
|
||||
|
||||
/**
|
||||
* Proposal 169.
|
||||
* Pubkey 32 bytes; privkey 32 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM512_X25519(5, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* Proposal 169.
|
||||
* Pubkey 32 bytes; privkey 32 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM768_X25519(6, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* Proposal 169.
|
||||
* Pubkey 32 bytes; privkey 32 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM1024_X25519(7, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only
|
||||
* Proposal 169.
|
||||
* Pubkey 0 bytes; privkey 0 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
NONE(255, 0, 0, EncAlgo.NONE, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only (Alice side)
|
||||
* Proposal 169.
|
||||
* Pubkey 800 bytes; privkey 1632 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM512_X25519_INT(100005, 800, 1632, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only (Alice side)
|
||||
* Proposal 169.
|
||||
* Pubkey 1184 bytes; privkey 2400 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM768_X25519_INT(100006, 1184, 2400, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only (Alice side)
|
||||
* Proposal 169.
|
||||
* Pubkey 1568 bytes; privkey 3168 bytes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM1024_X25519_INT(100007, 1568, 3168, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only (Bob side ciphertext)
|
||||
* Proposal 169.
|
||||
* Pubkey 800 bytes; privkey 0
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM512_X25519_CT(100008, 768, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only (Bob side ciphertext)
|
||||
* Proposal 169.
|
||||
* Pubkey 1184 bytes; privkey 0
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM768_X25519_CT(100009, 1088, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.80"),
|
||||
|
||||
/**
|
||||
* For internal use only (Bob side ciphertext)
|
||||
* Proposal 169.
|
||||
* Pubkey 1568 bytes; privkey 0
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLKEM1024_X25519_CT(100010, 1568, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.80");
|
||||
|
||||
|
||||
private final int code, pubkeyLen, privkeyLen;
|
||||
private final EncAlgo base;
|
||||
private final String algoName, since;
|
||||
private final AlgorithmParameterSpec params;
|
||||
private final boolean isAvail;
|
||||
private final boolean isAvail, isPQ;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -68,7 +145,7 @@ public enum EncType {
|
||||
*/
|
||||
EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo,
|
||||
String transformation, AlgorithmParameterSpec pSpec, String supportedSince) {
|
||||
if (pubLen > 256)
|
||||
if (pubLen > 256 && baseAlgo != EncAlgo.ECIES_MLKEM_INT)
|
||||
throw new IllegalArgumentException("fixup PublicKey for longer keys");
|
||||
code = cod;
|
||||
pubkeyLen = pubLen;
|
||||
@@ -78,6 +155,7 @@ public enum EncType {
|
||||
params = pSpec;
|
||||
since = supportedSince;
|
||||
isAvail = x_isAvailable();
|
||||
isPQ = base == EncAlgo.ECIES_MLKEM;
|
||||
}
|
||||
|
||||
/** the unique identifier for this type */
|
||||
@@ -120,11 +198,17 @@ public enum EncType {
|
||||
}
|
||||
|
||||
private boolean x_isAvailable() {
|
||||
if (ELGAMAL_2048 == this)
|
||||
return true;
|
||||
// EC types are placeholders for now
|
||||
if (base == EncAlgo.EC)
|
||||
return false;
|
||||
switch (base) {
|
||||
case ELGAMAL:
|
||||
return true;
|
||||
|
||||
// EC types are placeholders for now
|
||||
case EC:
|
||||
// internal types
|
||||
case NONE:
|
||||
case ECIES_MLKEM_INT:
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
getParams();
|
||||
} catch (InvalidParameterSpecException e) {
|
||||
@@ -154,6 +238,14 @@ public enum EncType {
|
||||
return type.isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.80
|
||||
* @return true if this is a PQ type
|
||||
*/
|
||||
public boolean isPQ() {
|
||||
return isPQ;
|
||||
}
|
||||
|
||||
private static final EncType[] BY_CODE;
|
||||
|
||||
static {
|
||||
|
@@ -38,6 +38,7 @@ import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.RedKeyPairGenerator;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import net.i2p.crypto.provider.I2PProvider;
|
||||
import net.i2p.crypto.pqc.MLDSA;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
@@ -196,6 +197,9 @@ public final class KeyGenerator {
|
||||
break;
|
||||
|
||||
case ECIES_X25519:
|
||||
case MLKEM512_X25519:
|
||||
case MLKEM768_X25519:
|
||||
case MLKEM1024_X25519:
|
||||
byte[] bpriv = new byte[32];
|
||||
do {
|
||||
_context.random().nextBytes(bpriv);
|
||||
@@ -238,6 +242,9 @@ public final class KeyGenerator {
|
||||
break;
|
||||
|
||||
case ECIES_X25519:
|
||||
case MLKEM512_X25519:
|
||||
case MLKEM768_X25519:
|
||||
case MLKEM1024_X25519:
|
||||
data = new byte[32];
|
||||
Curve25519.eval(data, 0, priv.getData(), null);
|
||||
break;
|
||||
@@ -295,7 +302,8 @@ public final class KeyGenerator {
|
||||
if (type == SigType.DSA_SHA1)
|
||||
return generateSigningKeys();
|
||||
java.security.KeyPair kp;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
|
||||
SigAlgo algo = type.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA) {
|
||||
net.i2p.crypto.eddsa.KeyPairGenerator kpg;
|
||||
if (type == SigType.RedDSA_SHA512_Ed25519)
|
||||
kpg = new RedKeyPairGenerator();
|
||||
@@ -303,6 +311,8 @@ public final class KeyGenerator {
|
||||
kpg = new net.i2p.crypto.eddsa.KeyPairGenerator();
|
||||
kpg.initialize(type.getParams(), _context.random());
|
||||
kp = kpg.generateKeyPair();
|
||||
} else if (type.isPQ()) {
|
||||
return MLDSA.generateKeys(type);
|
||||
} else {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName());
|
||||
try {
|
||||
@@ -342,9 +352,21 @@ public final class KeyGenerator {
|
||||
}
|
||||
java.security.PublicKey pubkey = kp.getPublic();
|
||||
java.security.PrivateKey privkey = kp.getPrivate();
|
||||
|
||||
SimpleDataStructure[] keys = new SimpleDataStructure[2];
|
||||
keys[0] = SigUtil.fromJavaKey(pubkey, type);
|
||||
keys[1] = SigUtil.fromJavaKey(privkey, type);
|
||||
SigningPublicKey pub = SigUtil.fromJavaKey(pubkey, type);
|
||||
SigningPrivateKey priv = SigUtil.fromJavaKey(privkey, type);
|
||||
SigningPublicKey pub2 = priv.toPublic();
|
||||
if (!pub.equals(pub2))
|
||||
throw new GeneralSecurityException("mismatch");
|
||||
java.security.PublicKey pubkey2 = SigUtil.toJavaKey(pub);
|
||||
java.security.PrivateKey privkey2 = SigUtil.toJavaKey(priv);
|
||||
if (!pubkey.equals(pubkey2))
|
||||
throw new GeneralSecurityException("mismatch");
|
||||
if (!privkey.equals(privkey2))
|
||||
throw new GeneralSecurityException("mismatch");
|
||||
keys[0] = pub;
|
||||
keys[1] = priv;
|
||||
return keys;
|
||||
}
|
||||
|
||||
@@ -393,6 +415,10 @@ public final class KeyGenerator {
|
||||
EdDSAPublicKey epub = new EdDSAPublicKey(new EdDSAPublicKeySpec(epriv.getA(), epriv.getParams()));
|
||||
return SigUtil.fromJavaKey(epub, type);
|
||||
|
||||
case MLDSA:
|
||||
case EdDSA_MLDSA:
|
||||
return MLDSA.toPublic(priv);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported algorithm");
|
||||
}
|
||||
@@ -475,7 +501,16 @@ public final class KeyGenerator {
|
||||
if (pubkey.equals(pubkey2))
|
||||
System.out.println(type + " private-to-public test PASSED");
|
||||
else
|
||||
System.out.println(type + " private-to-public test FAILED");
|
||||
throw new GeneralSecurityException(type + " private-to-public test FAILED");
|
||||
if (!type.isPQ()) {
|
||||
SigningPublicKey pubkey3 = SigUtil.fromJavaKey(SigUtil.toJavaKey(pubkey));
|
||||
if (!pubkey.equals(pubkey3))
|
||||
throw new GeneralSecurityException(type + " Java conversion test FAILED");
|
||||
SigningPrivateKey privkey3 = SigUtil.fromJavaKey(SigUtil.toJavaKey(privkey));
|
||||
if (!privkey.equals(privkey3))
|
||||
throw new GeneralSecurityException(type + " Java conversion test FAILED");
|
||||
}
|
||||
|
||||
//System.out.println("privkey " + keys[1]);
|
||||
MessageDigest md = type.getDigestInstance();
|
||||
for (int i = 0; i < runs; i++) {
|
||||
@@ -486,14 +521,26 @@ public final class KeyGenerator {
|
||||
hash.setData(sha);
|
||||
long start = System.nanoTime();
|
||||
Signature sig = DSAEngine.getInstance().sign(src, privkey);
|
||||
Signature sig2 = DSAEngine.getInstance().sign(hash, privkey);
|
||||
if (sig == null)
|
||||
throw new GeneralSecurityException("signature generation failed");
|
||||
if (sig2 == null)
|
||||
throw new GeneralSecurityException("signature generation (H) failed");
|
||||
Signature sig2;
|
||||
if (!type.isPQ()) {
|
||||
sig2 = DSAEngine.getInstance().sign(hash, privkey);
|
||||
if (sig2 == null)
|
||||
throw new GeneralSecurityException("signature generation (H) failed");
|
||||
} else {
|
||||
// not supported, just sign the hash data so the speed comparison is valid
|
||||
sig2 = DSAEngine.getInstance().sign(sha, privkey);
|
||||
}
|
||||
long mid = System.nanoTime();
|
||||
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, pubkey);
|
||||
boolean ok2 = DSAEngine.getInstance().verifySignature(sig2, hash, pubkey);
|
||||
boolean ok2;
|
||||
if (!type.isPQ()) {
|
||||
ok2 = DSAEngine.getInstance().verifySignature(sig2, hash, pubkey);
|
||||
} else {
|
||||
// not supported, just verify the hash data so the speed comparison is valid
|
||||
ok2 = DSAEngine.getInstance().verifySignature(sig2, sha, pubkey);
|
||||
}
|
||||
long end = System.nanoTime();
|
||||
stime += mid - start;
|
||||
vtime += end - mid;
|
||||
|
@@ -710,9 +710,14 @@ public class SU3File {
|
||||
for (SigType t : EnumSet.allOf(SigType.class)) {
|
||||
if (!t.isAvailable())
|
||||
continue;
|
||||
if (t == SigType.EdDSA_SHA512_Ed25519 ||
|
||||
t == SigType.RedDSA_SHA512_Ed25519)
|
||||
switch(t) {
|
||||
case EdDSA_SHA512_Ed25519:
|
||||
case RedDSA_SHA512_Ed25519:
|
||||
case MLDSA44_Ed25519:
|
||||
case MLDSA65_Ed25519:
|
||||
case MLDSA87_Ed25519:
|
||||
continue; // not supported by keytool, and does double hashing right now
|
||||
}
|
||||
buf.append(" ").append(t).append("\t(code: ").append(t.getCode()).append(')');
|
||||
if (t.getCode() == DEFAULT_SIG_CODE)
|
||||
buf.append(" DEFAULT");
|
||||
|
@@ -18,7 +18,15 @@ public enum SigAlgo {
|
||||
* For local use only, not for use in the network.
|
||||
* @since 0.9.25
|
||||
*/
|
||||
ElGamal("ElGamal")
|
||||
ElGamal("ElGamal"),
|
||||
/**
|
||||
* @since 0.9.80
|
||||
*/
|
||||
EdDSA_MLDSA("EdDSA-MLDSA"),
|
||||
/**
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA("MLDSA")
|
||||
;
|
||||
|
||||
private final String name;
|
||||
|
@@ -74,6 +74,87 @@ public enum SigType {
|
||||
RedDSA_SHA512_Ed25519(11, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.39"),
|
||||
|
||||
/**
|
||||
* MLDSA only
|
||||
* Pubkey 1312 bytes; privkey 2560 bytes; hash 64 bytes; sig 2420 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA44(12, 1312, 2560, 64, 2420, SigAlgo.MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.17", "0.9.80"),
|
||||
|
||||
/**
|
||||
* MLDSA only
|
||||
* Pubkey 1952 bytes; privkey 4032 bytes; hash 64 bytes; sig 4032 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA65(13, 1952, 4032, 64, 3309, SigAlgo.MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.18", "0.9.80"),
|
||||
|
||||
/**
|
||||
* MLDSA only
|
||||
* Pubkey 2592 bytes; privkey 4896 bytes; hash 64 bytes; sig 4627 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA87(14, 2592, 4896, 64, 4627, SigAlgo.MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.19", "0.9.80"),
|
||||
|
||||
/**
|
||||
* Hybrid
|
||||
* Pubkey 1344 bytes; privkey 2592 bytes; hash 64 bytes; sig 2484 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA44_Ed25519(15, 1344, 2592, 64, 2484, SigAlgo.EdDSA_MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.17", "0.9.80"),
|
||||
|
||||
/**
|
||||
* Hybrid
|
||||
* Pubkey 1984 bytes; privkey 4064 bytes; hash 64 bytes; sig 4096 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA65_Ed25519(16, 1984, 4064, 64, 3373, SigAlgo.EdDSA_MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.18", "0.9.80"),
|
||||
|
||||
/**
|
||||
* Hybrid
|
||||
* Pubkey 2624 bytes; privkey 4928 bytes; hash 64 bytes; sig 4691 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA87_Ed25519(17, 2624, 4928, 64, 4691, SigAlgo.EdDSA_MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.19", "0.9.80"),
|
||||
|
||||
/**
|
||||
* Hybrid
|
||||
* Pubkey 1344 bytes; privkey 2592 bytes; hash 64 bytes; sig 2484 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA44_Ed25519ph(18, 1344, 2592, 64, 2484, SigAlgo.EdDSA_MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.17", "0.9.80"),
|
||||
|
||||
/**
|
||||
* Hybrid
|
||||
* Pubkey 1984 bytes; privkey 4064 bytes; hash 64 bytes; sig 4096 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA65_Ed25519ph(19, 1984, 4064, 64, 3373, SigAlgo.EdDSA_MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.18", "0.9.80"),
|
||||
|
||||
/**
|
||||
* Hybrid
|
||||
* Pubkey 2624 bytes; privkey 4928 bytes; hash 64 bytes; sig 4691 bytes
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
MLDSA87_Ed25519ph(20, 2624, 4928, 64, 4691, SigAlgo.EdDSA_MLDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "2.16.840.1.101.3.4.3.19", "0.9.80"),
|
||||
|
||||
;
|
||||
|
||||
// TESTING....................
|
||||
@@ -119,7 +200,7 @@ public enum SigType {
|
||||
private final SigAlgo base;
|
||||
private final String digestName, algoName, oid, since;
|
||||
private final AlgorithmParameterSpec params;
|
||||
private final boolean isAvail;
|
||||
private final boolean isAvail, isPQ;
|
||||
|
||||
SigType(int cod, int pubLen, int privLen, int hLen, int sLen, SigAlgo baseAlgo,
|
||||
String mdName, String aName, AlgorithmParameterSpec pSpec, String oid, String supportedSince) {
|
||||
@@ -135,6 +216,7 @@ public enum SigType {
|
||||
this.oid = oid;
|
||||
since = supportedSince;
|
||||
isAvail = x_isAvailable();
|
||||
isPQ = base == SigAlgo.EdDSA_MLDSA || base == SigAlgo.MLDSA;
|
||||
}
|
||||
|
||||
/** the unique identifier for this type */
|
||||
@@ -222,6 +304,15 @@ public enum SigType {
|
||||
private boolean x_isAvailable() {
|
||||
if (DSA_SHA1 == this)
|
||||
return true;
|
||||
SigAlgo algo = getBaseAlgorithm();
|
||||
if (algo == SigAlgo.MLDSA || algo == SigAlgo.EdDSA_MLDSA) {
|
||||
try {
|
||||
Class.forName("org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters", false, ClassLoader.getSystemClassLoader());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
getParams();
|
||||
if (getBaseAlgorithm() != SigAlgo.EdDSA) {
|
||||
@@ -275,6 +366,14 @@ public enum SigType {
|
||||
return type.isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.80
|
||||
* @return true if this is a PQ type
|
||||
*/
|
||||
public boolean isPQ() {
|
||||
return isPQ;
|
||||
}
|
||||
|
||||
private static final SigType[] BY_CODE;
|
||||
|
||||
static {
|
||||
@@ -313,6 +412,12 @@ public enum SigType {
|
||||
return EdDSA_SHA512_Ed25519ph;
|
||||
if (uc.equals("REDDSA_SHA512_ED25519"))
|
||||
return RedDSA_SHA512_Ed25519;
|
||||
if (uc.equals("MLSDA44_ED25519"))
|
||||
return MLDSA44_Ed25519;
|
||||
if (uc.equals("MLSDA65_ED25519"))
|
||||
return MLDSA65_Ed25519;
|
||||
if (uc.equals("MLSDA87_ED25519"))
|
||||
return MLDSA87_Ed25519;
|
||||
return valueOf(uc);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
try {
|
||||
|
116
core/java/src/net/i2p/crypto/pqc/BouncyUtil.java
Normal file
116
core/java/src/net/i2p/crypto/pqc/BouncyUtil.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package net.i2p.crypto.pqc;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Convert between i2p and bouncycastle classes
|
||||
*
|
||||
* @since 0.9.69
|
||||
*/
|
||||
final class BouncyUtil {
|
||||
|
||||
/**
|
||||
* @return JAVA key!
|
||||
*/
|
||||
public static MLDSAPublicKeyParameters toBouncyKey(SigningPublicKey pk)
|
||||
throws GeneralSecurityException {
|
||||
SigType type = pk.getType();
|
||||
switch(type) {
|
||||
case MLDSA44:
|
||||
return new MLDSAPublicKeyParameters(MLDSAParameters.ml_dsa_44, pk.getData());
|
||||
case MLDSA65:
|
||||
return new MLDSAPublicKeyParameters(MLDSAParameters.ml_dsa_65, pk.getData());
|
||||
case MLDSA87:
|
||||
return new MLDSAPublicKeyParameters(MLDSAParameters.ml_dsa_87, pk.getData());
|
||||
default:
|
||||
throw new InvalidKeyException("unsupported key: " + pk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hedged (randomized) signatures. Ref: FIPS 204 sec. 3.4.
|
||||
* @return ParametersWithRandom
|
||||
*/
|
||||
public static CipherParameters toBouncyKey(SigningPrivateKey pk)
|
||||
throws GeneralSecurityException {
|
||||
return toBouncyKey(pk, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hedged true for randomized signatures, false for unit tests or to get private key only
|
||||
* @return ParametersWithRandom or MLDSAPrivateKeyParameters
|
||||
*/
|
||||
public static CipherParameters toBouncyKey(SigningPrivateKey pk, boolean hedged)
|
||||
throws GeneralSecurityException {
|
||||
CipherParameters rv;
|
||||
SigType type = pk.getType();
|
||||
switch(type) {
|
||||
case MLDSA44:
|
||||
rv = new MLDSAPrivateKeyParameters(MLDSAParameters.ml_dsa_44, pk.getData());
|
||||
break;
|
||||
case MLDSA65:
|
||||
rv = new MLDSAPrivateKeyParameters(MLDSAParameters.ml_dsa_65, pk.getData());
|
||||
break;
|
||||
case MLDSA87:
|
||||
rv = new MLDSAPrivateKeyParameters(MLDSAParameters.ml_dsa_87, pk.getData());
|
||||
break;
|
||||
default:
|
||||
throw new InvalidKeyException("unsupported key: " + pk);
|
||||
}
|
||||
if (hedged)
|
||||
rv = new ParametersWithRandom(rv, RandomSource.getInstance());
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static SigningPublicKey fromBouncyKey(MLDSAPublicKeyParameters pk)
|
||||
throws GeneralSecurityException {
|
||||
MLDSAParameters params = pk.getParameters();
|
||||
if (params == MLDSAParameters.ml_dsa_44)
|
||||
return fromBouncyKey(pk, SigType.MLDSA44);
|
||||
else if (params == MLDSAParameters.ml_dsa_65)
|
||||
return fromBouncyKey(pk, SigType.MLDSA65);
|
||||
else if (params == MLDSAParameters.ml_dsa_87)
|
||||
return fromBouncyKey(pk, SigType.MLDSA87);
|
||||
else
|
||||
throw new InvalidKeyException("unsupported key: " + pk);
|
||||
}
|
||||
|
||||
public static SigningPublicKey fromBouncyKey(MLDSAPublicKeyParameters pk, SigType type)
|
||||
throws GeneralSecurityException {
|
||||
return new SigningPublicKey(type, pk.getEncoded());
|
||||
}
|
||||
|
||||
public static SigningPrivateKey fromBouncyKey(MLDSAPrivateKeyParameters pk)
|
||||
throws GeneralSecurityException {
|
||||
MLDSAParameters params = pk.getParameters();
|
||||
if (params == MLDSAParameters.ml_dsa_44)
|
||||
return fromBouncyKey(pk, SigType.MLDSA44);
|
||||
else if (params == MLDSAParameters.ml_dsa_65)
|
||||
return fromBouncyKey(pk, SigType.MLDSA65);
|
||||
else if (params == MLDSAParameters.ml_dsa_87)
|
||||
return fromBouncyKey(pk, SigType.MLDSA87);
|
||||
else
|
||||
throw new InvalidKeyException("unsupported key: " + pk);
|
||||
}
|
||||
|
||||
public static SigningPrivateKey fromBouncyKey(MLDSAPrivateKeyParameters pk, SigType type)
|
||||
throws GeneralSecurityException {
|
||||
return new SigningPrivateKey(type, pk.getEncoded());
|
||||
}
|
||||
|
||||
|
||||
public static void clearCaches() {
|
||||
}
|
||||
}
|
184
core/java/src/net/i2p/crypto/pqc/HybridUtil.java
Normal file
184
core/java/src/net/i2p/crypto/pqc/HybridUtil.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package net.i2p.crypto.pqc;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import net.i2p.crypto.EncAlgo;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
|
||||
/**
|
||||
* Convert between hybrid types and separate types
|
||||
*
|
||||
* @since 0.9.69
|
||||
*/
|
||||
final class HybridUtil {
|
||||
|
||||
/**
|
||||
* @return two keys
|
||||
*/
|
||||
public static SigningPublicKey[] split(SigningPublicKey pk) {
|
||||
SigType type = pk.getType();
|
||||
if (type.getBaseAlgorithm() != SigAlgo.EdDSA_MLDSA)
|
||||
throw new IllegalArgumentException("unsupported key: " + pk);
|
||||
SigningPublicKey[] rv = new SigningPublicKey[2];
|
||||
byte[] d = new byte[32];
|
||||
System.arraycopy(pk.getData(), 0, d, 0, 32);
|
||||
rv[0] = new SigningPublicKey(SigType.EdDSA_SHA512_Ed25519, d);
|
||||
d = new byte[pk.length() - 32];
|
||||
System.arraycopy(pk.getData(), 32, d, 0, d.length);
|
||||
switch(type) {
|
||||
case MLDSA44_Ed25519:
|
||||
rv[1] = new SigningPublicKey(SigType.MLDSA44, d);
|
||||
break;
|
||||
case MLDSA65_Ed25519:
|
||||
rv[1] = new SigningPublicKey(SigType.MLDSA65, d);
|
||||
break;
|
||||
case MLDSA87_Ed25519:
|
||||
rv[1] = new SigningPublicKey(SigType.MLDSA87, d);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported key: " + pk);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return two keys
|
||||
*/
|
||||
public static SigningPrivateKey[] split(SigningPrivateKey pk) {
|
||||
SigType type = pk.getType();
|
||||
if (type.getBaseAlgorithm() != SigAlgo.EdDSA_MLDSA)
|
||||
throw new IllegalArgumentException("unsupported key: " + pk);
|
||||
SigningPrivateKey[] rv = new SigningPrivateKey[2];
|
||||
byte[] d = new byte[32];
|
||||
System.arraycopy(pk.getData(), 0, d, 0, 32);
|
||||
rv[0] = new SigningPrivateKey(SigType.EdDSA_SHA512_Ed25519, d);
|
||||
d = new byte[pk.length() - 32];
|
||||
System.arraycopy(pk.getData(), 32, d, 0, d.length);
|
||||
switch(type) {
|
||||
case MLDSA44_Ed25519:
|
||||
rv[1] = new SigningPrivateKey(SigType.MLDSA44, d);
|
||||
break;
|
||||
case MLDSA65_Ed25519:
|
||||
rv[1] = new SigningPrivateKey(SigType.MLDSA65, d);
|
||||
break;
|
||||
case MLDSA87_Ed25519:
|
||||
rv[1] = new SigningPrivateKey(SigType.MLDSA87, d);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported key: " + pk);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return two signatures
|
||||
*/
|
||||
public static Signature[] split(Signature pk) {
|
||||
SigType type = pk.getType();
|
||||
if (type.getBaseAlgorithm() != SigAlgo.EdDSA_MLDSA)
|
||||
throw new IllegalArgumentException("unsupported key: " + pk);
|
||||
Signature[] rv = new Signature[2];
|
||||
byte[] d = new byte[64];
|
||||
System.arraycopy(pk.getData(), 0, d, 0, 64);
|
||||
rv[0] = new Signature(SigType.EdDSA_SHA512_Ed25519, d);
|
||||
d = new byte[pk.length() - 64];
|
||||
System.arraycopy(pk.getData(), 64, d, 0, d.length);
|
||||
switch(type) {
|
||||
case MLDSA44_Ed25519:
|
||||
rv[1] = new Signature(SigType.MLDSA44, d);
|
||||
break;
|
||||
case MLDSA65_Ed25519:
|
||||
rv[1] = new Signature(SigType.MLDSA65, d);
|
||||
break;
|
||||
case MLDSA87_Ed25519:
|
||||
rv[1] = new Signature(SigType.MLDSA87, d);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported key: " + pk);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pk1 the Ed25519 key
|
||||
* @param pk2 the hybrid key
|
||||
* @return combined key
|
||||
*/
|
||||
public static SigningPublicKey combine(SigningPublicKey pk1, SigningPublicKey pk2) {
|
||||
SigType type = pk2.getType();
|
||||
if (type.getBaseAlgorithm() != SigAlgo.MLDSA ||
|
||||
pk1.getType() != SigType.EdDSA_SHA512_Ed25519)
|
||||
throw new IllegalArgumentException("unsupported key combo: " + pk1 + ' ' + pk2);
|
||||
byte[] d = new byte[32 + pk2.length()];
|
||||
System.arraycopy(pk1.getData(), 0, d, 0, 32);
|
||||
System.arraycopy(pk2.getData(), 0, d, 32, pk2.length());
|
||||
switch(type) {
|
||||
case MLDSA44:
|
||||
return new SigningPublicKey(SigType.MLDSA44_Ed25519, d);
|
||||
case MLDSA65:
|
||||
return new SigningPublicKey(SigType.MLDSA65_Ed25519, d);
|
||||
case MLDSA87:
|
||||
return new SigningPublicKey(SigType.MLDSA87_Ed25519, d);
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported key: " + pk2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pk1 the Ed25519 key
|
||||
* @param pk2 the hybrid key
|
||||
* @return combined key
|
||||
*/
|
||||
public static SigningPrivateKey combine(SigningPrivateKey pk1, SigningPrivateKey pk2) {
|
||||
SigType type = pk2.getType();
|
||||
if (type.getBaseAlgorithm() != SigAlgo.MLDSA ||
|
||||
pk1.getType() != SigType.EdDSA_SHA512_Ed25519)
|
||||
throw new IllegalArgumentException("unsupported key combo: " + pk1 + ' ' + pk2);
|
||||
byte[] d = new byte[32 + pk2.length()];
|
||||
System.arraycopy(pk1.getData(), 0, d, 0, 32);
|
||||
System.arraycopy(pk2.getData(), 0, d, 32, pk2.length());
|
||||
switch(type) {
|
||||
case MLDSA44:
|
||||
return new SigningPrivateKey(SigType.MLDSA44_Ed25519, d);
|
||||
case MLDSA65:
|
||||
return new SigningPrivateKey(SigType.MLDSA65_Ed25519, d);
|
||||
case MLDSA87:
|
||||
return new SigningPrivateKey(SigType.MLDSA87_Ed25519, d);
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported key: " + pk2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sig1 the Ed25519 sig
|
||||
* @param sig2 the hybrid sig
|
||||
* @return combined signature
|
||||
*/
|
||||
public static Signature combine(Signature sig1, Signature sig2) {
|
||||
SigType type = sig2.getType();
|
||||
if (type.getBaseAlgorithm() != SigAlgo.MLDSA ||
|
||||
sig1.getType() != SigType.EdDSA_SHA512_Ed25519)
|
||||
throw new IllegalArgumentException("unsupported key combo: " + sig1 + ' ' + sig2);
|
||||
byte[] d = new byte[64 + sig2.length()];
|
||||
System.arraycopy(sig1.getData(), 0, d, 0, 64);
|
||||
System.arraycopy(sig2.getData(), 0, d, 64, sig2.length());
|
||||
switch(type) {
|
||||
case MLDSA44:
|
||||
return new Signature(SigType.MLDSA44_Ed25519, d);
|
||||
case MLDSA65:
|
||||
return new Signature(SigType.MLDSA65_Ed25519, d);
|
||||
case MLDSA87:
|
||||
return new Signature(SigType.MLDSA87_Ed25519, d);
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported key: " + sig2);
|
||||
}
|
||||
}
|
||||
}
|
170
core/java/src/net/i2p/crypto/pqc/MLDSA.java
Normal file
170
core/java/src/net/i2p/crypto/pqc/MLDSA.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package net.i2p.crypto.pqc;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAKeyPairGenerator;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAKeyGenerationParameters;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mldsa.MLDSASigner;
|
||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Wrapper around bouncycastle
|
||||
*
|
||||
* @since 0.9.69
|
||||
*/
|
||||
public final class MLDSA {
|
||||
|
||||
public static final String BC_VERSION;
|
||||
|
||||
static {
|
||||
String v;
|
||||
try {
|
||||
double d = (new BouncyCastlePQCProvider()).getVersion();
|
||||
v = Double.toString(d);
|
||||
} catch (Exception e) {
|
||||
v = "n/a";
|
||||
}
|
||||
BC_VERSION = v;
|
||||
}
|
||||
|
||||
public static Signature sign(byte[] data, int offset, int len,
|
||||
SigningPrivateKey privateKey) throws GeneralSecurityException {
|
||||
SigType type = privateKey.getType();
|
||||
SigAlgo algo = type.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA)
|
||||
return signHybrid(data, offset, len, privateKey);
|
||||
if (algo != SigAlgo.MLDSA)
|
||||
throw new IllegalArgumentException();
|
||||
MLDSASigner jsig = new MLDSASigner();
|
||||
jsig.init(true, BouncyUtil.toBouncyKey(privateKey));
|
||||
jsig.update(data, offset, len);
|
||||
try {
|
||||
byte[] sig = jsig.generateSignature();
|
||||
return new Signature(type, sig);
|
||||
} catch (CryptoException ce) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verify(Signature signature, byte[] data, int offset, int len, SigningPublicKey verifyingKey)
|
||||
throws GeneralSecurityException {
|
||||
SigType type = signature.getType();
|
||||
SigAlgo algo = type.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA)
|
||||
return verifyHybrid(signature, data, offset, len, verifyingKey);
|
||||
if (algo != SigAlgo.MLDSA)
|
||||
throw new IllegalArgumentException();
|
||||
MLDSASigner jsig = new MLDSASigner();
|
||||
jsig.init(false, BouncyUtil.toBouncyKey(verifyingKey));
|
||||
jsig.update(data, offset, len);
|
||||
return jsig.verifySignature(signature.getData());
|
||||
}
|
||||
|
||||
public static SimpleDataStructure[] generateKeys(SigType type)
|
||||
throws GeneralSecurityException {
|
||||
SigAlgo algo = type.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA)
|
||||
return generateHybrid(type);
|
||||
MLDSAParameters param;
|
||||
switch(type) {
|
||||
case MLDSA44:
|
||||
param = MLDSAParameters.ml_dsa_44;
|
||||
break;
|
||||
case MLDSA65:
|
||||
param = MLDSAParameters.ml_dsa_65;
|
||||
break;
|
||||
case MLDSA87:
|
||||
param = MLDSAParameters.ml_dsa_87;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidKeyException("unsupported type: " + type);
|
||||
}
|
||||
MLDSAKeyPairGenerator kpg = new MLDSAKeyPairGenerator();
|
||||
kpg.init(new MLDSAKeyGenerationParameters(RandomSource.getInstance(), param));
|
||||
AsymmetricCipherKeyPair pair = kpg.generateKeyPair();
|
||||
MLDSAPublicKeyParameters pubkey = (MLDSAPublicKeyParameters) pair.getPublic();
|
||||
MLDSAPrivateKeyParameters privkey = (MLDSAPrivateKeyParameters) pair.getPrivate();
|
||||
SimpleDataStructure[] keys = new SimpleDataStructure[2];
|
||||
SigningPublicKey pub = BouncyUtil.fromBouncyKey(pubkey, type);
|
||||
SigningPrivateKey priv = BouncyUtil.fromBouncyKey(privkey, type);
|
||||
keys[0] = pub;
|
||||
keys[1] = priv;
|
||||
return keys;
|
||||
}
|
||||
|
||||
public static SigningPublicKey toPublic(SigningPrivateKey priv)
|
||||
throws GeneralSecurityException {
|
||||
SigAlgo algo = priv.getType().getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA)
|
||||
return toPublicHybrid(priv);
|
||||
CipherParameters cp = BouncyUtil.toBouncyKey(priv, false);
|
||||
MLDSAPrivateKeyParameters pkp = (MLDSAPrivateKeyParameters) cp;
|
||||
byte[] pub = pkp.getPublicKey();
|
||||
return new SigningPublicKey(priv.getType(), pub);
|
||||
}
|
||||
|
||||
private static Signature signHybrid(byte[] data, int offset, int len,
|
||||
SigningPrivateKey privateKey) throws GeneralSecurityException {
|
||||
SigningPrivateKey[] k = HybridUtil.split(privateKey);
|
||||
Signature s1 = I2PAppContext.getGlobalContext().dsa().sign(data, offset, len, k[0]);
|
||||
Signature s2 = sign(data, offset, len, k[1]);
|
||||
return HybridUtil.combine(s1, s2);
|
||||
}
|
||||
|
||||
private static boolean verifyHybrid(Signature signature, byte[] data, int offset, int len, SigningPublicKey verifyingKey)
|
||||
throws GeneralSecurityException {
|
||||
Signature[] s = HybridUtil.split(signature);
|
||||
SigningPublicKey[] k = HybridUtil.split(verifyingKey);
|
||||
boolean rv = I2PAppContext.getGlobalContext().dsa().verifySignature(s[0], data, offset, len, k[0]);
|
||||
if (rv)
|
||||
rv = verify(s[1], data, offset, len, k[1]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static SimpleDataStructure[] generateHybrid(SigType type)
|
||||
throws GeneralSecurityException {
|
||||
SimpleDataStructure[] rv = new SimpleDataStructure[2];
|
||||
SimpleDataStructure[] k1 = I2PAppContext.getGlobalContext().keyGenerator().generateSigningKeys(SigType.EdDSA_SHA512_Ed25519);
|
||||
SimpleDataStructure[] k2;
|
||||
switch(type) {
|
||||
case MLDSA44_Ed25519:
|
||||
k2 = generateKeys(SigType.MLDSA44);
|
||||
break;
|
||||
case MLDSA65_Ed25519:
|
||||
k2 = generateKeys(SigType.MLDSA65);
|
||||
break;
|
||||
case MLDSA87_Ed25519:
|
||||
k2 = generateKeys(SigType.MLDSA87);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unsupported type: " + type);
|
||||
}
|
||||
rv[0] = HybridUtil.combine((SigningPublicKey) k1[0], (SigningPublicKey) k2[0]);
|
||||
rv[1] = HybridUtil.combine((SigningPrivateKey) k1[1], (SigningPrivateKey) k2[1]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static SigningPublicKey toPublicHybrid(SigningPrivateKey priv)
|
||||
throws GeneralSecurityException {
|
||||
SigningPrivateKey[] k = HybridUtil.split(priv);
|
||||
SigningPublicKey p1 = k[0].toPublic();
|
||||
SigningPublicKey p2 = toPublic(k[1]);
|
||||
return HybridUtil.combine(p1, p2);
|
||||
}
|
||||
}
|
@@ -13,6 +13,9 @@ import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
@@ -62,11 +65,19 @@ public class Destination extends KeysAndCert {
|
||||
// convert SPK to new SPK and padding
|
||||
// EncTypes 1-3 allowed in Destinations, see proposal 145
|
||||
KeyCertificate kcert = c.toKeyCertificate();
|
||||
byte[] pad1 = pk.getPadding(kcert);
|
||||
byte[] pad2 = sk.getPadding(kcert);
|
||||
padding = combinePadding(pad1, pad2);
|
||||
pk = pk.toTypedKey(kcert);
|
||||
sk = sk.toTypedKey(kcert);
|
||||
boolean noPubKey = kcert.getEncType() == EncType.NONE &&
|
||||
kcert.getSigType().isPQ();
|
||||
if (noPubKey) {
|
||||
sk = sk.toTypedKey(pk, kcert);
|
||||
pk = PublicKey.NULL;
|
||||
padding = null;
|
||||
} else {
|
||||
byte[] pad1 = pk.getPadding(kcert);
|
||||
byte[] pad2 = sk.getPadding(kcert);
|
||||
pk = pk.toTypedKey(kcert);
|
||||
sk = sk.toTypedKey(kcert);
|
||||
padding = combinePadding(pad1, pad2);
|
||||
}
|
||||
c = kcert;
|
||||
} else {
|
||||
padding = null;
|
||||
@@ -120,14 +131,22 @@ public class Destination extends KeysAndCert {
|
||||
*/
|
||||
public int writeBytes(byte target[], int offset) {
|
||||
int cur = offset;
|
||||
System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES);
|
||||
cur += PublicKey.KEYSIZE_BYTES;
|
||||
cur = writePaddingBytes(target, cur);
|
||||
int spkTrunc = Math.min(SigningPublicKey.KEYSIZE_BYTES, _signingKey.length());
|
||||
boolean noPubKey = _publicKey.getType() == EncType.NONE &&
|
||||
_signingKey.getType().isPQ();
|
||||
int spkTrunc;
|
||||
if (!noPubKey) {
|
||||
System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES);
|
||||
cur += PublicKey.KEYSIZE_BYTES;
|
||||
cur = writePaddingBytes(target, cur);
|
||||
spkTrunc = Math.min(SigningPublicKey.KEYSIZE_BYTES, _signingKey.length());
|
||||
} else {
|
||||
spkTrunc = 384;
|
||||
}
|
||||
System.arraycopy(_signingKey.getData(), 0, target, cur, spkTrunc);
|
||||
cur += spkTrunc;
|
||||
cur += _certificate.writeBytes(target, cur);
|
||||
return cur - offset;
|
||||
int rv = cur - offset;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +178,7 @@ public class Destination extends KeysAndCert {
|
||||
}
|
||||
|
||||
public int size() {
|
||||
int rv = PublicKey.KEYSIZE_BYTES + _signingKey.length();
|
||||
int rv = _signingKey.length();
|
||||
if (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
// cert data included in keys
|
||||
rv += 7;
|
||||
@@ -170,7 +189,10 @@ public class Destination extends KeysAndCert {
|
||||
if (padding != null)
|
||||
rv += padding.length;
|
||||
}
|
||||
if (_publicKey.getType() != EncType.NONE)
|
||||
rv += PublicKey.KEYSIZE_BYTES;
|
||||
} else {
|
||||
rv += PublicKey.KEYSIZE_BYTES;
|
||||
rv += _certificate.size();
|
||||
}
|
||||
return rv;
|
||||
|
@@ -5,6 +5,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
@@ -108,15 +109,22 @@ public class KeyCertificate extends Certificate {
|
||||
if (spk == null || spk.getData() == null)
|
||||
throw new IllegalArgumentException();
|
||||
SigType type = spk.getType();
|
||||
boolean noPubKey = type.isPQ();
|
||||
int len = type.getPubkeyLen();
|
||||
int extra = Math.max(0, len - 128);
|
||||
int off = noPubKey ? 384 : 128;
|
||||
int extra = Math.max(0, len - off);
|
||||
_payload = new byte[HEADER_LENGTH + extra];
|
||||
int code = type.getCode();
|
||||
_payload[0] = (byte) (code >> 8);
|
||||
_payload[1] = (byte) (code & 0xff);
|
||||
// 2 and 3 always 0, it is the only crypto code for now
|
||||
// 2 and 3 always 0 unless PQ
|
||||
if (noPubKey) {
|
||||
code = EncType.NONE.getCode();
|
||||
_payload[2] = (byte) (code >> 8);
|
||||
_payload[3] = (byte) (code & 0xff);
|
||||
}
|
||||
if (extra > 0)
|
||||
System.arraycopy(spk.getData(), 128, _payload, HEADER_LENGTH, extra);
|
||||
System.arraycopy(spk.getData(), off, _payload, HEADER_LENGTH, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,8 +143,11 @@ public class KeyCertificate extends Certificate {
|
||||
pk == null || pk.getData() == null)
|
||||
throw new IllegalArgumentException();
|
||||
SigType type = spk.getType();
|
||||
boolean noPubKey = pk.getType() == EncType.NONE &&
|
||||
type.isPQ();
|
||||
int off = noPubKey ? 384 : 128;
|
||||
int len = type.getPubkeyLen();
|
||||
int extra = Math.max(0, len - 128);
|
||||
int extra = Math.max(0, len - off);
|
||||
_payload = new byte[HEADER_LENGTH + extra];
|
||||
int code = type.getCode();
|
||||
_payload[0] = (byte) (code >> 8);
|
||||
@@ -145,7 +156,7 @@ public class KeyCertificate extends Certificate {
|
||||
_payload[2] = (byte) (code >> 8);
|
||||
_payload[3] = (byte) (code & 0xff);
|
||||
if (extra > 0)
|
||||
System.arraycopy(spk.getData(), 128, _payload, HEADER_LENGTH, extra);
|
||||
System.arraycopy(spk.getData(), off, _payload, HEADER_LENGTH, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +170,10 @@ public class KeyCertificate extends Certificate {
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
public KeyCertificate(SigType type) {
|
||||
this(type, EncType.ELGAMAL_2048);
|
||||
this(type, (type.getBaseAlgorithm() == SigAlgo.EdDSA_MLDSA ||
|
||||
type.getBaseAlgorithm() == SigAlgo.MLDSA) ?
|
||||
EncType.NONE :
|
||||
EncType.ELGAMAL_2048);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +192,10 @@ public class KeyCertificate extends Certificate {
|
||||
public KeyCertificate(SigType type, EncType etype) {
|
||||
super(CERTIFICATE_TYPE_KEY, null);
|
||||
int len = type.getPubkeyLen();
|
||||
int extra = Math.max(0, len - 128);
|
||||
boolean noPubKey = etype == EncType.NONE &&
|
||||
type.isPQ();
|
||||
int off = noPubKey ? 384 : 128;
|
||||
int extra = Math.max(0, len - off);
|
||||
_payload = new byte[HEADER_LENGTH + extra];
|
||||
int code = type.getCode();
|
||||
_payload[0] = (byte) (code >> 8);
|
||||
|
@@ -16,6 +16,7 @@ import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.util.ByteArrayStream;
|
||||
|
||||
@@ -168,12 +169,20 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
// convert PK and SPK to new PK and SPK and padding
|
||||
KeyCertificate kcert = cert.toKeyCertificate();
|
||||
_publicKey = pk.toTypedKey(kcert);
|
||||
_signingKey = spk.toTypedKey(kcert);
|
||||
byte[] pad1 = pk.getPadding(kcert);
|
||||
byte[] pad2 = spk.getPadding(kcert);
|
||||
_padding = combinePadding(pad1, pad2);
|
||||
compressPadding();
|
||||
boolean noPubKey = kcert.getEncType() == EncType.NONE &&
|
||||
kcert.getSigType().isPQ();
|
||||
if (noPubKey) {
|
||||
_signingKey = spk.toTypedKey(pk, kcert);
|
||||
_publicKey = PublicKey.NULL;
|
||||
_padding = null;
|
||||
} else {
|
||||
_signingKey = spk.toTypedKey(kcert);
|
||||
_publicKey = pk.toTypedKey(kcert);
|
||||
byte[] pad1 = pk.getPadding(kcert);
|
||||
byte[] pad2 = spk.getPadding(kcert);
|
||||
_padding = combinePadding(pad1, pad2);
|
||||
compressPadding();
|
||||
}
|
||||
_certificate = kcert;
|
||||
} else {
|
||||
_publicKey = pk;
|
||||
@@ -200,10 +209,11 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
/**
|
||||
* This only does the padding, does not compress the unused 256 byte LS public key.
|
||||
* Savings is 288 bytes for RI and 64 bytes for LS.
|
||||
* @since 0.9.62
|
||||
* @since 0.9.62, protected since 0.9.80
|
||||
*/
|
||||
private void compressPadding() {
|
||||
_paddingBlocks = 0;
|
||||
protected void compressPadding() {
|
||||
if (_paddingBlocks > 0)
|
||||
throw new IllegalStateException();
|
||||
// > 32 and a mult. of 32
|
||||
if (_padding == null || _padding.length <= 32 || (_padding.length & (PAD_COMP_LEN - 1)) != 0)
|
||||
return;
|
||||
@@ -242,7 +252,10 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ((_certificate == null) || (_publicKey == null) || (_signingKey == null))
|
||||
throw new DataFormatException("Not enough data to format the router identity");
|
||||
_publicKey.writeBytes(out);
|
||||
boolean noPubKey = _publicKey.getType() == EncType.NONE &&
|
||||
_signingKey.getType().isPQ();
|
||||
if (!noPubKey)
|
||||
_publicKey.writeBytes(out);
|
||||
if (_padding != null) {
|
||||
if (_paddingBlocks <= 1) {
|
||||
out.write(_padding);
|
||||
@@ -252,10 +265,10 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
}
|
||||
}
|
||||
} else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES ||
|
||||
_publicKey.length() < PublicKey.KEYSIZE_BYTES) {
|
||||
throw new DataFormatException("No padding set");
|
||||
(!noPubKey && _publicKey.length() < PublicKey.KEYSIZE_BYTES)) {
|
||||
throw new DataFormatException("No padding set, sk " + _signingKey + " pk " + _publicKey);
|
||||
}
|
||||
_signingKey.writeTruncatedBytes(out);
|
||||
_signingKey.writeTruncatedBytes(out, noPubKey);
|
||||
_certificate.writeBytes(out);
|
||||
}
|
||||
|
||||
@@ -263,7 +276,7 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
public boolean equals(Object object) {
|
||||
if (object == this) return true;
|
||||
if ((object == null) || !(object instanceof KeysAndCert)) return false;
|
||||
KeysAndCert ident = (KeysAndCert) object;
|
||||
KeysAndCert ident = (KeysAndCert) object;
|
||||
return
|
||||
DataHelper.eq(_signingKey, ident._signingKey)
|
||||
&& DataHelper.eq(_publicKey, ident._publicKey)
|
||||
@@ -289,6 +302,9 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
String cls = getClass().getSimpleName();
|
||||
buf.append('[').append(cls).append(": ");
|
||||
byte[] arr = toByteArray();
|
||||
if (arr != null)
|
||||
buf.append("\n\tSize: ").append(arr.length);
|
||||
buf.append("\n\tHash: ");
|
||||
if (cls.equals("Destination"))
|
||||
buf.append(getHash().toBase32());
|
||||
|
@@ -29,6 +29,11 @@ import net.i2p.util.SimpleByteCache;
|
||||
public class PrivateKey extends SimpleDataStructure implements Destroyable {
|
||||
private static final EncType DEF_TYPE = EncType.ELGAMAL_2048;
|
||||
public final static int KEYSIZE_BYTES = DEF_TYPE.getPrivkeyLen();
|
||||
/**
|
||||
* A key with type 255 and data length zero, for use with hybrid sigtypes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final PrivateKey NULL = new Dummy();
|
||||
|
||||
private final EncType _type;
|
||||
// cache
|
||||
@@ -178,4 +183,28 @@ public class PrivateKey extends SimpleDataStructure implements Destroyable {
|
||||
PrivateKey p = (PrivateKey) obj;
|
||||
return _type == p._type && Arrays.equals(_data, p._data);
|
||||
}
|
||||
|
||||
/**
|
||||
* For use with hybrid sigtypes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static class Dummy extends PrivateKey {
|
||||
public Dummy() { super(EncType.NONE, new byte[0]); }
|
||||
@Override
|
||||
public int length() { return 0; }
|
||||
@Override
|
||||
public void readBytes(java.io.InputStream in) {}
|
||||
@Override
|
||||
public void writeBytes(java.io.OutputStream out) {}
|
||||
@Override
|
||||
public PublicKey toPublic() { return PublicKey.NULL; }
|
||||
@Override
|
||||
public void destroy() {}
|
||||
@Override
|
||||
public String toString() { return "[PrivateKey NULL size: 0]"; }
|
||||
@Override
|
||||
public int hashCode() { return 9999999; }
|
||||
@Override
|
||||
public boolean equals(Object obj) { return this == obj; }
|
||||
}
|
||||
}
|
||||
|
@@ -234,12 +234,21 @@ public class PrivateKeyFile {
|
||||
EncType ptype = EncType.parseEncType(etype);
|
||||
if (ptype == null)
|
||||
throw new IllegalArgumentException("Encryption type " + etype + " is not supported");
|
||||
if (ptype != EncType.NONE) {
|
||||
SigAlgo algo = type.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA || algo == SigAlgo.MLDSA)
|
||||
throw new IllegalArgumentException("MLDSA sig types must use NULL enc type");
|
||||
}
|
||||
d = pkf.createIfAbsent(type, ptype);
|
||||
} else if (stype != null) {
|
||||
SigType type = SigType.parseSigType(stype);
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("Signature type " + stype + " is not supported");
|
||||
d = pkf.createIfAbsent(type);
|
||||
SigAlgo algo = type.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA || algo == SigAlgo.MLDSA)
|
||||
d = pkf.createIfAbsent(type, EncType.NONE);
|
||||
else
|
||||
d = pkf.createIfAbsent(type);
|
||||
} else {
|
||||
d = pkf.createIfAbsent();
|
||||
}
|
||||
@@ -469,6 +478,7 @@ public class PrivateKeyFile {
|
||||
buf.append(" DEFAULT");
|
||||
buf.append('\n');
|
||||
}
|
||||
buf.append(" NULL\t\t(code: 255) use with MLDSA Sig Types only");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
|
||||
@@ -612,8 +622,10 @@ public class PrivateKeyFile {
|
||||
System.arraycopy(rand, 0, bpub, i, Math.min(PADDING_ENTROPY, bpub.length - i));
|
||||
}
|
||||
pub = new PublicKey(ptype, bpub);
|
||||
byte[] bpriv = new byte[ptype.getPrivkeyLen()];
|
||||
priv = new PrivateKey(ptype, bpriv);
|
||||
// private key unused
|
||||
byte[] bpriv = new byte[PrivateKey.KEYSIZE_BYTES];
|
||||
ctx.random().nextBytes(bpriv);
|
||||
priv = new PrivateKey(bpriv);
|
||||
} else {
|
||||
// routers use the encryption key
|
||||
KeyPair keypair = ctx.keyGenerator().generatePKIKeys(ptype);
|
||||
@@ -624,18 +636,22 @@ public class PrivateKeyFile {
|
||||
SigningPublicKey spub = (SigningPublicKey)signingKeypair[0];
|
||||
SigningPrivateKey spriv = (SigningPrivateKey)signingKeypair[1];
|
||||
Certificate cert;
|
||||
boolean noPubKey = ptype == EncType.NONE;
|
||||
if (type != SigType.DSA_SHA1 || ptype != EncType.ELGAMAL_2048) {
|
||||
// TODO long sig types
|
||||
if (type.getPubkeyLen() > 128)
|
||||
if (type.getPubkeyLen() > 128 && !noPubKey)
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
cert = new KeyCertificate(type, ptype);
|
||||
if (noPubKey)
|
||||
cert = new KeyCertificate(spub, PublicKey.NULL);
|
||||
else
|
||||
cert = new KeyCertificate(type, ptype);
|
||||
} else {
|
||||
cert = Certificate.NULL_CERT;
|
||||
}
|
||||
byte[] padding;
|
||||
int padLen = (SigningPublicKey.KEYSIZE_BYTES - spub.length()) +
|
||||
(PublicKey.KEYSIZE_BYTES - pub.length());
|
||||
if (padLen > 0) {
|
||||
if (padLen > 0 && !noPubKey) {
|
||||
padding = new byte[padLen];
|
||||
for (int i = 0; i < padLen; i += PADDING_ENTROPY) {
|
||||
System.arraycopy(rand, 0, padding, i, Math.min(PADDING_ENTROPY, padLen - i));
|
||||
@@ -643,10 +659,14 @@ public class PrivateKeyFile {
|
||||
} else {
|
||||
padding = null;
|
||||
}
|
||||
pub.writeBytes(out);
|
||||
if (padding != null)
|
||||
out.write(padding);
|
||||
spub.writeBytes(out);
|
||||
if (noPubKey) {
|
||||
spub.writeTruncatedBytes(out, true);
|
||||
} else {
|
||||
pub.writeBytes(out);
|
||||
if (padding != null)
|
||||
out.write(padding);
|
||||
spub.writeBytes(out);
|
||||
}
|
||||
cert.writeBytes(out);
|
||||
priv.writeBytes(out);
|
||||
spriv.writeBytes(out);
|
||||
|
@@ -14,6 +14,8 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
* Defines the PublicKey as defined by the I2P data structure spec.
|
||||
@@ -29,6 +31,11 @@ import net.i2p.crypto.EncType;
|
||||
public class PublicKey extends SimpleDataStructure {
|
||||
private static final EncType DEF_TYPE = EncType.ELGAMAL_2048;
|
||||
public final static int KEYSIZE_BYTES = DEF_TYPE.getPubkeyLen();
|
||||
/**
|
||||
* A key with type 255 and data length zero, for use with hybrid sigtypes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final PublicKey NULL = new Dummy();
|
||||
private static final int CACHE_SIZE = 1024;
|
||||
|
||||
private static final SDSCache<PublicKey> _cache = new SDSCache<PublicKey>(PublicKey.class, KEYSIZE_BYTES, CACHE_SIZE);
|
||||
@@ -156,6 +163,11 @@ public class PublicKey extends SimpleDataStructure {
|
||||
// unknown type, keep the 256 bytes of data
|
||||
if (newType == null)
|
||||
return new PublicKey(null, _data);
|
||||
SigType st = kcert.getSigType();
|
||||
boolean noPubKey = newType == EncType.NONE &&
|
||||
st.isPQ();
|
||||
if (noPubKey)
|
||||
return NULL;
|
||||
int newLen = newType.getPubkeyLen();
|
||||
if (newLen == KEYSIZE_BYTES)
|
||||
return new PublicKey(newType, _data);
|
||||
@@ -240,4 +252,24 @@ public class PublicKey extends SimpleDataStructure {
|
||||
PublicKey s = (PublicKey) obj;
|
||||
return _type == s._type && Arrays.equals(_data, s._data);
|
||||
}
|
||||
|
||||
/**
|
||||
* For use with hybrid sigtypes
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static class Dummy extends PublicKey {
|
||||
public Dummy() { super(EncType.NONE, new byte[0]); }
|
||||
@Override
|
||||
public int length() { return 0; }
|
||||
@Override
|
||||
public void readBytes(java.io.InputStream in) {}
|
||||
@Override
|
||||
public void writeBytes(java.io.OutputStream out) {}
|
||||
@Override
|
||||
public String toString() { return "[PublicKey NULL size: 0]"; }
|
||||
@Override
|
||||
public int hashCode() { return 9999999; }
|
||||
@Override
|
||||
public boolean equals(Object obj) { return this == obj; }
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@ import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.Blinding;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigAlgo;
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
@@ -117,6 +119,7 @@ public class SigningPublicKey extends SimpleDataStructure {
|
||||
/**
|
||||
* Up-convert this from an untyped (type 0) SPK to a typed SPK based on the Key Cert given.
|
||||
* The type of the returned key will be null if the kcert sigtype is null.
|
||||
* NOT FOR MLDSA keys, will throw IAE.
|
||||
*
|
||||
* @throws IllegalArgumentException if this is already typed to a different type
|
||||
* @since 0.9.12 changed from public to package private in 0.9.66, not for external use
|
||||
@@ -137,6 +140,9 @@ public class SigningPublicKey extends SimpleDataStructure {
|
||||
if (ctype == 0) {
|
||||
// prohibit excess key data
|
||||
// TODO non-zero crypto type if added
|
||||
SigAlgo algo = newType.getBaseAlgorithm();
|
||||
if (algo == SigAlgo.EdDSA_MLDSA || algo == SigAlgo.MLDSA)
|
||||
throw new IllegalArgumentException("Not for MLDSA keys");
|
||||
int sz = 7;
|
||||
if (newLen > KEYSIZE_BYTES)
|
||||
sz += newLen - KEYSIZE_BYTES;
|
||||
@@ -157,6 +163,51 @@ public class SigningPublicKey extends SimpleDataStructure {
|
||||
return new SigningPublicKey(newType, newData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Up-convert this from an untyped (type 0) SPK to a typed SPK based on the Key Cert given.
|
||||
* The type of the returned key will be null if the kcert sigtype is null.
|
||||
* ONLY FOR MLDSA keys, will throw IAE.
|
||||
*
|
||||
* @throws IllegalArgumentException if this is already typed to a different type
|
||||
* @since 0.9.80
|
||||
*/
|
||||
SigningPublicKey toTypedKey(PublicKey pk, KeyCertificate kcert) {
|
||||
if (_data == null)
|
||||
throw new IllegalStateException();
|
||||
SigType newType = kcert.getSigType();
|
||||
if (_type == newType)
|
||||
return this;
|
||||
if (_type != SigType.DSA_SHA1)
|
||||
throw new IllegalArgumentException("Cannot convert " + _type + " to " + newType);
|
||||
// unknown type, keep the 128 bytes of data
|
||||
if (newType == null)
|
||||
return new SigningPublicKey(null, _data);
|
||||
int newLen = newType.getPubkeyLen();
|
||||
int ctype = kcert.getCryptoTypeCode();
|
||||
if (ctype == EncType.NONE.getCode()) { // 255
|
||||
// prohibit excess key data
|
||||
SigAlgo algo = newType.getBaseAlgorithm();
|
||||
if (algo != SigAlgo.EdDSA_MLDSA && algo != SigAlgo.MLDSA)
|
||||
throw new IllegalArgumentException("Only for MLDSA keys");
|
||||
int sz = 7 + newLen - (KEYSIZE_BYTES + PublicKey.KEYSIZE_BYTES);
|
||||
if (kcert.size() != sz)
|
||||
throw new IllegalArgumentException("Excess data in key certificate");
|
||||
} else {
|
||||
throw new IllegalArgumentException("PK must be NULL type 255");
|
||||
}
|
||||
byte[] newData = new byte[newLen];
|
||||
if (newLen <= KEYSIZE_BYTES) {
|
||||
throw new IllegalArgumentException("SPK must be long");
|
||||
} else {
|
||||
// full 384 bytes + fragment in kcert
|
||||
System.arraycopy(pk.getData(), 0, newData, 0, PublicKey.KEYSIZE_BYTES);
|
||||
System.arraycopy(_data, 0, newData, PublicKey.KEYSIZE_BYTES, KEYSIZE_BYTES);
|
||||
int off = KEYSIZE_BYTES + PublicKey.KEYSIZE_BYTES;
|
||||
System.arraycopy(kcert.getPayload(), KeyCertificate.HEADER_LENGTH, newData, off, newLen - off);
|
||||
}
|
||||
return new SigningPublicKey(newType, newData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the portion of this (type 0) SPK that is really padding based on the Key Cert type given,
|
||||
* if any
|
||||
@@ -188,13 +239,24 @@ public class SigningPublicKey extends SimpleDataStructure {
|
||||
* @since 0.9.12 changed from public to package private in 0.9.66, not for external use
|
||||
*/
|
||||
void writeTruncatedBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
writeTruncatedBytes(out, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the data up to a max of 128 or 384 bytes.
|
||||
* If longer, the rest will be written in the KeyCertificate.
|
||||
* @param isHybrid if true limit is 384, elase 128
|
||||
* @since 0.9.80
|
||||
*/
|
||||
void writeTruncatedBytes(OutputStream out, boolean isHybrid) throws DataFormatException, IOException {
|
||||
// we don't use _type here so we can write the data even for unknown type
|
||||
//if (_type.getPubkeyLen() <= KEYSIZE_BYTES)
|
||||
if (_data == null) throw new DataFormatException("No data to write out");
|
||||
if (_data.length <= KEYSIZE_BYTES)
|
||||
int max = isHybrid ? KEYSIZE_BYTES + PublicKey.KEYSIZE_BYTES : KEYSIZE_BYTES;
|
||||
if (_data.length <= max)
|
||||
out.write(_data);
|
||||
else
|
||||
out.write(_data, 0, KEYSIZE_BYTES);
|
||||
out.write(_data, 0, max);
|
||||
}
|
||||
|
||||
/**
|
||||
|
22
core/java/src/org/bouncycastle/asn1/ASN1Encoding.java
Normal file
22
core/java/src/org/bouncycastle/asn1/ASN1Encoding.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.bouncycastle.asn1;
|
||||
|
||||
/**
|
||||
* Supported encoding formats.
|
||||
*/
|
||||
public interface ASN1Encoding
|
||||
{
|
||||
/**
|
||||
* DER - distinguished encoding rules.
|
||||
*/
|
||||
static final String DER = "DER";
|
||||
|
||||
/**
|
||||
* DL - definite length encoding.
|
||||
*/
|
||||
static final String DL = "DL";
|
||||
|
||||
/**
|
||||
* BER - basic encoding rules.
|
||||
*/
|
||||
static final String BER = "BER";
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package org.bouncycastle.asn1;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Class representing the ASN.1 OBJECT IDENTIFIER type.
|
||||
*/
|
||||
public class ASN1ObjectIdentifier
|
||||
{
|
||||
public byte[] getEncoded(String encoding) throws IOException
|
||||
{
|
||||
/*
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||
toASN1Primitive().encodeTo(bOut, encoding);
|
||||
return bOut.toByteArray();
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
}
|
4
core/java/src/org/bouncycastle/asn1/package-info.java
Normal file
4
core/java/src/org/bouncycastle/asn1/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* A library for parsing and writing ASN.1 objects. Support is provided for DER and BER encoding.
|
||||
*/
|
||||
package org.bouncycastle.asn1;
|
@@ -0,0 +1,61 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
/**
|
||||
* a holding class for public/private parameter pairs.
|
||||
*/
|
||||
public class AsymmetricCipherKeyPair
|
||||
{
|
||||
private AsymmetricKeyParameter publicParam;
|
||||
private AsymmetricKeyParameter privateParam;
|
||||
|
||||
/**
|
||||
* basic constructor.
|
||||
*
|
||||
* @param publicParam a public key parameters object.
|
||||
* @param privateParam the corresponding private key parameters.
|
||||
*/
|
||||
public AsymmetricCipherKeyPair(
|
||||
AsymmetricKeyParameter publicParam,
|
||||
AsymmetricKeyParameter privateParam)
|
||||
{
|
||||
this.publicParam = publicParam;
|
||||
this.privateParam = privateParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* basic constructor.
|
||||
*
|
||||
* @param publicParam a public key parameters object.
|
||||
* @param privateParam the corresponding private key parameters.
|
||||
* @deprecated use AsymmetricKeyParameter
|
||||
*/
|
||||
public AsymmetricCipherKeyPair(
|
||||
CipherParameters publicParam,
|
||||
CipherParameters privateParam)
|
||||
{
|
||||
this.publicParam = (AsymmetricKeyParameter)publicParam;
|
||||
this.privateParam = (AsymmetricKeyParameter)privateParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the public key parameters.
|
||||
*
|
||||
* @return the public key parameters.
|
||||
*/
|
||||
public AsymmetricKeyParameter getPublic()
|
||||
{
|
||||
return publicParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the private key parameters.
|
||||
*
|
||||
* @return the private key parameters.
|
||||
*/
|
||||
public AsymmetricKeyParameter getPrivate()
|
||||
{
|
||||
return privateParam;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* interface that a public/private key pair generator should conform to.
|
||||
*/
|
||||
public interface AsymmetricCipherKeyPairGenerator
|
||||
{
|
||||
/**
|
||||
* intialise the key pair generator.
|
||||
*
|
||||
* @param param the parameters the key pair is to be initialised with.
|
||||
*/
|
||||
public void init(KeyGenerationParameters param);
|
||||
|
||||
/**
|
||||
* return an AsymmetricCipherKeyPair containing the generated keys.
|
||||
*
|
||||
* @return an AsymmetricCipherKeyPair containing the generated keys.
|
||||
*/
|
||||
public AsymmetricCipherKeyPair generateKeyPair();
|
||||
}
|
||||
|
@@ -0,0 +1,8 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* all parameter classes implement this.
|
||||
*/
|
||||
public interface CipherParameters
|
||||
{
|
||||
}
|
48
core/java/src/org/bouncycastle/crypto/CryptoException.java
Normal file
48
core/java/src/org/bouncycastle/crypto/CryptoException.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* the foundation class for the hard exceptions thrown by the crypto packages.
|
||||
*/
|
||||
public class CryptoException
|
||||
extends Exception
|
||||
{
|
||||
private Throwable cause;
|
||||
|
||||
/**
|
||||
* base constructor.
|
||||
*/
|
||||
public CryptoException()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* create a CryptoException with the given message.
|
||||
*
|
||||
* @param message the message to be carried with the exception.
|
||||
*/
|
||||
public CryptoException(
|
||||
String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CryptoException with the given message and underlying cause.
|
||||
*
|
||||
* @param message message describing exception.
|
||||
* @param cause the throwable that was the underlying cause.
|
||||
*/
|
||||
public CryptoException(
|
||||
String message,
|
||||
Throwable cause)
|
||||
{
|
||||
super(message);
|
||||
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public Throwable getCause()
|
||||
{
|
||||
return cause;
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
public interface CryptoServiceProperties
|
||||
{
|
||||
int bitsOfSecurity();
|
||||
|
||||
String getServiceName();
|
||||
|
||||
CryptoServicePurpose getPurpose();
|
||||
|
||||
Object getParams();
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
public enum CryptoServicePurpose
|
||||
{
|
||||
AGREEMENT,
|
||||
ENCRYPTION,
|
||||
DECRYPTION,
|
||||
KEYGEN,
|
||||
SIGNING, // for signatures (and digests)
|
||||
VERIFYING,
|
||||
AUTHENTICATION, // for MACs (and digests)
|
||||
VERIFICATION,
|
||||
PRF,
|
||||
ANY
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class CryptoServicesRegistrar {
|
||||
|
||||
public static void checkConstraints(CryptoServiceProperties csp) {}
|
||||
|
||||
private static final SecureRandom sr = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Return the default source of randomness.
|
||||
*
|
||||
* @return the default SecureRandom
|
||||
*/
|
||||
public static SecureRandom getSecureRandom(SecureRandom secureRandom)
|
||||
{
|
||||
return null == secureRandom ? sr : secureRandom;
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* this exception is thrown if a buffer that is meant to have output
|
||||
* copied into it turns out to be too short, or if we've been given
|
||||
* insufficient input. In general this exception will get thrown rather
|
||||
* than an ArrayOutOfBounds exception.
|
||||
*/
|
||||
public class DataLengthException
|
||||
extends RuntimeCryptoException
|
||||
{
|
||||
/**
|
||||
* base constructor.
|
||||
*/
|
||||
public DataLengthException()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* create a DataLengthException with the given message.
|
||||
*
|
||||
* @param message the message to be carried with the exception.
|
||||
*/
|
||||
public DataLengthException(
|
||||
String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
51
core/java/src/org/bouncycastle/crypto/Digest.java
Normal file
51
core/java/src/org/bouncycastle/crypto/Digest.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* interface that a message digest conforms to.
|
||||
*/
|
||||
public interface Digest
|
||||
{
|
||||
/**
|
||||
* return the algorithm name
|
||||
*
|
||||
* @return the algorithm name
|
||||
*/
|
||||
public String getAlgorithmName();
|
||||
|
||||
/**
|
||||
* return the size, in bytes, of the digest produced by this message digest.
|
||||
*
|
||||
* @return the size, in bytes, of the digest produced by this message digest.
|
||||
*/
|
||||
public int getDigestSize();
|
||||
|
||||
/**
|
||||
* update the message digest with a single byte.
|
||||
*
|
||||
* @param in the input byte to be entered.
|
||||
*/
|
||||
public void update(byte in);
|
||||
|
||||
/**
|
||||
* update the message digest with a block of bytes.
|
||||
*
|
||||
* @param in the byte array containing the data.
|
||||
* @param inOff the offset into the byte array where the data starts.
|
||||
* @param len the length of the data.
|
||||
*/
|
||||
public void update(byte[] in, int inOff, int len);
|
||||
|
||||
/**
|
||||
* close the digest, producing the final digest value. The doFinal
|
||||
* call leaves the digest reset.
|
||||
*
|
||||
* @param out the array the digest is to be copied into.
|
||||
* @param outOff the offset into the out array the digest is to start at.
|
||||
*/
|
||||
public int doFinal(byte[] out, int outOff);
|
||||
|
||||
/**
|
||||
* reset the digest back to it's initial state.
|
||||
*/
|
||||
public void reset();
|
||||
}
|
17
core/java/src/org/bouncycastle/crypto/EncodableDigest.java
Normal file
17
core/java/src/org/bouncycastle/crypto/EncodableDigest.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package org.bouncycastle.crypto.digests;
|
||||
|
||||
/**
|
||||
* Encodable digests allow you to download an encoded copy of their internal state. This is useful for the situation where
|
||||
* you need to generate a signature on an external device and it allows for "sign with last round", so a copy of the
|
||||
* internal state of the digest, plus the last few blocks of the message are all that needs to be sent, rather than the
|
||||
* entire message.
|
||||
*/
|
||||
public interface EncodableDigest
|
||||
{
|
||||
/**
|
||||
* Return an encoded byte array for the digest's internal state
|
||||
*
|
||||
* @return an encoding of the digests internal state.
|
||||
*/
|
||||
byte[] getEncodedState();
|
||||
}
|
13
core/java/src/org/bouncycastle/crypto/ExtendedDigest.java
Normal file
13
core/java/src/org/bouncycastle/crypto/ExtendedDigest.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
public interface ExtendedDigest
|
||||
extends Digest
|
||||
{
|
||||
/**
|
||||
* Return the size in bytes of the internal buffer the digest applies it's compression
|
||||
* function to.
|
||||
*
|
||||
* @return byte length of the digests internal buffer.
|
||||
*/
|
||||
public int getByteLength();
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* The base class for parameters to key generators.
|
||||
*/
|
||||
public class KeyGenerationParameters
|
||||
{
|
||||
private SecureRandom random;
|
||||
private int strength;
|
||||
|
||||
/**
|
||||
* initialise the generator with a source of randomness
|
||||
* and a strength (in bits).
|
||||
*
|
||||
* @param random the random byte source.
|
||||
* @param strength the size, in bits, of the keys we want to produce.
|
||||
*/
|
||||
public KeyGenerationParameters(
|
||||
SecureRandom random,
|
||||
int strength)
|
||||
{
|
||||
this.random = CryptoServicesRegistrar.getSecureRandom(random);
|
||||
this.strength = strength;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the random source associated with this
|
||||
* generator.
|
||||
*
|
||||
* @return the generators random source.
|
||||
*/
|
||||
public SecureRandom getRandom()
|
||||
{
|
||||
return random;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the bit strength for keys produced by this generator,
|
||||
*
|
||||
* @return the strength of the keys this generator produces (in bits).
|
||||
*/
|
||||
public int getStrength()
|
||||
{
|
||||
return strength;
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* the foundation class for the exceptions thrown by the crypto packages.
|
||||
*/
|
||||
public class RuntimeCryptoException
|
||||
extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* base constructor.
|
||||
*/
|
||||
public RuntimeCryptoException()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* create a RuntimeCryptoException with the given message.
|
||||
*
|
||||
* @param message the message to be carried with the exception.
|
||||
*/
|
||||
public RuntimeCryptoException(
|
||||
String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
30
core/java/src/org/bouncycastle/crypto/Xof.java
Normal file
30
core/java/src/org/bouncycastle/crypto/Xof.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes.
|
||||
* This interface provides the extra method required to support variable output on an extended digest implementation.
|
||||
*/
|
||||
public interface Xof
|
||||
extends ExtendedDigest
|
||||
{
|
||||
/**
|
||||
* Output the results of the final calculation for this digest to outLen number of bytes.
|
||||
*
|
||||
* @param out output array to write the output bytes to.
|
||||
* @param outOff offset to start writing the bytes at.
|
||||
* @param outLen the number of output bytes requested.
|
||||
* @return the number of bytes written
|
||||
*/
|
||||
int doFinal(byte[] out, int outOff, int outLen);
|
||||
|
||||
/**
|
||||
* Start outputting the results of the final calculation for this digest. Unlike doFinal, this method
|
||||
* will continue producing output until the Xof is explicitly reset, or signals otherwise.
|
||||
*
|
||||
* @param out output array to write the output bytes to.
|
||||
* @param outOff offset to start writing the bytes at.
|
||||
* @param outLen the number of output bytes requested.
|
||||
* @return the number of bytes written
|
||||
*/
|
||||
int doOutput(byte[] out, int outOff, int outLen);
|
||||
}
|
444
core/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java
Normal file
444
core/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java
Normal file
@@ -0,0 +1,444 @@
|
||||
package org.bouncycastle.crypto.digests;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoServiceProperties;
|
||||
import org.bouncycastle.crypto.CryptoServicePurpose;
|
||||
import org.bouncycastle.crypto.CryptoServicesRegistrar;
|
||||
import org.bouncycastle.crypto.ExtendedDigest;
|
||||
import org.bouncycastle.util.Pack;
|
||||
|
||||
/**
|
||||
* implementation of Keccak based on following KeccakNISTInterface.c from https://keccak.noekeon.org/
|
||||
* <p>
|
||||
* Following the naming conventions used in the C source code to enable easy review of the implementation.
|
||||
*/
|
||||
public class KeccakDigest
|
||||
implements ExtendedDigest
|
||||
{
|
||||
private static long[] KeccakRoundConstants = new long[]{ 0x0000000000000001L, 0x0000000000008082L,
|
||||
0x800000000000808aL, 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L,
|
||||
0x8000000000008009L, 0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL,
|
||||
0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L, 0x8000000000008002L,
|
||||
0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, 0x8000000000008080L,
|
||||
0x0000000080000001L, 0x8000000080008008L };
|
||||
protected final CryptoServicePurpose purpose;
|
||||
|
||||
protected long[] state = new long[25];
|
||||
protected byte[] dataQueue = new byte[192];
|
||||
protected int rate;
|
||||
protected int bitsInQueue;
|
||||
protected int fixedOutputLength;
|
||||
protected boolean squeezing;
|
||||
|
||||
public KeccakDigest()
|
||||
{
|
||||
this(288, CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
public KeccakDigest(CryptoServicePurpose purpose)
|
||||
{
|
||||
this(288, purpose);
|
||||
}
|
||||
|
||||
public KeccakDigest(int bitLength)
|
||||
{
|
||||
this(bitLength, CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
public KeccakDigest(int bitLength, CryptoServicePurpose purpose)
|
||||
{
|
||||
this.purpose = purpose;
|
||||
init(bitLength);
|
||||
|
||||
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
|
||||
}
|
||||
|
||||
public KeccakDigest(KeccakDigest source)
|
||||
{
|
||||
this.purpose = source.purpose;
|
||||
System.arraycopy(source.state, 0, this.state, 0, source.state.length);
|
||||
System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length);
|
||||
this.rate = source.rate;
|
||||
this.bitsInQueue = source.bitsInQueue;
|
||||
this.fixedOutputLength = source.fixedOutputLength;
|
||||
this.squeezing = source.squeezing;
|
||||
|
||||
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "Keccak-" + fixedOutputLength;
|
||||
}
|
||||
|
||||
public int getDigestSize()
|
||||
{
|
||||
return fixedOutputLength / 8;
|
||||
}
|
||||
|
||||
public void update(byte in)
|
||||
{
|
||||
absorb(in);
|
||||
}
|
||||
|
||||
public void update(byte[] in, int inOff, int len)
|
||||
{
|
||||
absorb(in, inOff, len);
|
||||
}
|
||||
|
||||
public int doFinal(byte[] out, int outOff)
|
||||
{
|
||||
squeeze(out, outOff, fixedOutputLength);
|
||||
|
||||
reset();
|
||||
|
||||
return getDigestSize();
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Possible API change to support partial-byte suffixes.
|
||||
*/
|
||||
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
|
||||
{
|
||||
if (partialBits > 0)
|
||||
{
|
||||
absorbBits(partialByte, partialBits);
|
||||
}
|
||||
|
||||
squeeze(out, outOff, fixedOutputLength);
|
||||
|
||||
reset();
|
||||
|
||||
return getDigestSize();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
init(fixedOutputLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of block that the compression function is applied to in bytes.
|
||||
*
|
||||
* @return internal byte length of a block.
|
||||
*/
|
||||
public int getByteLength()
|
||||
{
|
||||
return rate / 8;
|
||||
}
|
||||
|
||||
private void init(int bitLength)
|
||||
{
|
||||
switch (bitLength)
|
||||
{
|
||||
case 128:
|
||||
case 224:
|
||||
case 256:
|
||||
case 288:
|
||||
case 384:
|
||||
case 512:
|
||||
initSponge(1600 - (bitLength << 1));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512.");
|
||||
}
|
||||
}
|
||||
|
||||
private void initSponge(int rate)
|
||||
{
|
||||
if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0))
|
||||
{
|
||||
throw new IllegalStateException("invalid rate value");
|
||||
}
|
||||
|
||||
this.rate = rate;
|
||||
for (int i = 0; i < state.length; ++i)
|
||||
{
|
||||
state[i] = 0L;
|
||||
}
|
||||
Arrays.fill(this.dataQueue, (byte)0);
|
||||
this.bitsInQueue = 0;
|
||||
this.squeezing = false;
|
||||
this.fixedOutputLength = (1600 - rate) / 2;
|
||||
}
|
||||
|
||||
protected void absorb(byte data)
|
||||
{
|
||||
if ((bitsInQueue % 8) != 0)
|
||||
{
|
||||
throw new IllegalStateException("attempt to absorb with odd length queue");
|
||||
}
|
||||
if (squeezing)
|
||||
{
|
||||
throw new IllegalStateException("attempt to absorb while squeezing");
|
||||
}
|
||||
|
||||
dataQueue[bitsInQueue >>> 3] = data;
|
||||
if ((bitsInQueue += 8) == rate)
|
||||
{
|
||||
KeccakAbsorb(dataQueue, 0);
|
||||
bitsInQueue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void absorb(byte[] data, int off, int len)
|
||||
{
|
||||
if ((bitsInQueue % 8) != 0)
|
||||
{
|
||||
throw new IllegalStateException("attempt to absorb with odd length queue");
|
||||
}
|
||||
if (squeezing)
|
||||
{
|
||||
throw new IllegalStateException("attempt to absorb while squeezing");
|
||||
}
|
||||
|
||||
int bytesInQueue = bitsInQueue >>> 3;
|
||||
int rateBytes = rate >>> 3;
|
||||
|
||||
int available = rateBytes - bytesInQueue;
|
||||
if (len < available)
|
||||
{
|
||||
System.arraycopy(data, off, dataQueue, bytesInQueue, len);
|
||||
this.bitsInQueue += len << 3;
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
if (bytesInQueue > 0)
|
||||
{
|
||||
System.arraycopy(data, off, dataQueue, bytesInQueue, available);
|
||||
count += available;
|
||||
KeccakAbsorb(dataQueue, 0);
|
||||
}
|
||||
|
||||
int remaining;
|
||||
while ((remaining = (len - count)) >= rateBytes)
|
||||
{
|
||||
KeccakAbsorb(data, off + count);
|
||||
count += rateBytes;
|
||||
}
|
||||
|
||||
System.arraycopy(data, off + count, dataQueue, 0, remaining);
|
||||
this.bitsInQueue = remaining << 3;
|
||||
}
|
||||
|
||||
protected void absorbBits(int data, int bits)
|
||||
{
|
||||
if (bits < 1 || bits > 7)
|
||||
{
|
||||
throw new IllegalArgumentException("'bits' must be in the range 1 to 7");
|
||||
}
|
||||
if ((bitsInQueue % 8) != 0)
|
||||
{
|
||||
throw new IllegalStateException("attempt to absorb with odd length queue");
|
||||
}
|
||||
if (squeezing)
|
||||
{
|
||||
throw new IllegalStateException("attempt to absorb while squeezing");
|
||||
}
|
||||
|
||||
int mask = (1 << bits) - 1;
|
||||
dataQueue[bitsInQueue >>> 3] = (byte)(data & mask);
|
||||
|
||||
// NOTE: After this, bitsInQueue is no longer a multiple of 8, so no more absorbs will work
|
||||
bitsInQueue += bits;
|
||||
}
|
||||
|
||||
private void padAndSwitchToSqueezingPhase()
|
||||
{
|
||||
dataQueue[bitsInQueue >>> 3] |= (byte)(1 << (bitsInQueue & 7));
|
||||
|
||||
if (++bitsInQueue == rate)
|
||||
{
|
||||
KeccakAbsorb(dataQueue, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int full = bitsInQueue >>> 6, partial = bitsInQueue & 63;
|
||||
int off = 0;
|
||||
for (int i = 0; i < full; ++i)
|
||||
{
|
||||
state[i] ^= Pack.littleEndianToLong(dataQueue, off);
|
||||
off += 8;
|
||||
}
|
||||
|
||||
if (partial > 0)
|
||||
{
|
||||
long mask = (1L << partial) - 1L;
|
||||
state[full] ^= Pack.littleEndianToLong(dataQueue, off) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
state[(rate - 1) >>> 6] ^= (1L << 63);
|
||||
|
||||
bitsInQueue = 0;
|
||||
squeezing = true;
|
||||
}
|
||||
|
||||
protected void squeeze(byte[] output, int offset, long outputLength)
|
||||
{
|
||||
if (!squeezing)
|
||||
{
|
||||
padAndSwitchToSqueezingPhase();
|
||||
}
|
||||
|
||||
if ((outputLength % 8) != 0)
|
||||
{
|
||||
throw new IllegalStateException("outputLength not a multiple of 8");
|
||||
}
|
||||
|
||||
long i = 0;
|
||||
while (i < outputLength)
|
||||
{
|
||||
if (bitsInQueue == 0)
|
||||
{
|
||||
KeccakExtract();
|
||||
}
|
||||
int partialBlock = (int)Math.min((long)bitsInQueue, outputLength - i);
|
||||
System.arraycopy(dataQueue, (rate - bitsInQueue) / 8, output, offset + (int)(i / 8), partialBlock / 8);
|
||||
bitsInQueue -= partialBlock;
|
||||
i += partialBlock;
|
||||
}
|
||||
}
|
||||
|
||||
private void KeccakAbsorb(byte[] data, int off)
|
||||
{
|
||||
// assert 0 == bitsInQueue || (dataQueue == data && 0 == off);
|
||||
|
||||
int count = rate >>> 6;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
state[i] ^= Pack.littleEndianToLong(data, off);
|
||||
off += 8;
|
||||
}
|
||||
|
||||
KeccakPermutation();
|
||||
}
|
||||
|
||||
private void KeccakExtract()
|
||||
{
|
||||
// assert 0 == bitsInQueue;
|
||||
|
||||
KeccakPermutation();
|
||||
|
||||
Pack.longToLittleEndian(state, 0, rate >>> 6, dataQueue, 0);
|
||||
|
||||
this.bitsInQueue = rate;
|
||||
}
|
||||
|
||||
private void KeccakPermutation()
|
||||
{
|
||||
long[] A = state;
|
||||
|
||||
long a00 = A[ 0], a01 = A[ 1], a02 = A[ 2], a03 = A[ 3], a04 = A[ 4];
|
||||
long a05 = A[ 5], a06 = A[ 6], a07 = A[ 7], a08 = A[ 8], a09 = A[ 9];
|
||||
long a10 = A[10], a11 = A[11], a12 = A[12], a13 = A[13], a14 = A[14];
|
||||
long a15 = A[15], a16 = A[16], a17 = A[17], a18 = A[18], a19 = A[19];
|
||||
long a20 = A[20], a21 = A[21], a22 = A[22], a23 = A[23], a24 = A[24];
|
||||
|
||||
for (int i = 0; i < 24; i++)
|
||||
{
|
||||
// theta
|
||||
long c0 = a00 ^ a05 ^ a10 ^ a15 ^ a20;
|
||||
long c1 = a01 ^ a06 ^ a11 ^ a16 ^ a21;
|
||||
long c2 = a02 ^ a07 ^ a12 ^ a17 ^ a22;
|
||||
long c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23;
|
||||
long c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24;
|
||||
|
||||
long d1 = (c1 << 1 | c1 >>> -1) ^ c4;
|
||||
long d2 = (c2 << 1 | c2 >>> -1) ^ c0;
|
||||
long d3 = (c3 << 1 | c3 >>> -1) ^ c1;
|
||||
long d4 = (c4 << 1 | c4 >>> -1) ^ c2;
|
||||
long d0 = (c0 << 1 | c0 >>> -1) ^ c3;
|
||||
|
||||
a00 ^= d1; a05 ^= d1; a10 ^= d1; a15 ^= d1; a20 ^= d1;
|
||||
a01 ^= d2; a06 ^= d2; a11 ^= d2; a16 ^= d2; a21 ^= d2;
|
||||
a02 ^= d3; a07 ^= d3; a12 ^= d3; a17 ^= d3; a22 ^= d3;
|
||||
a03 ^= d4; a08 ^= d4; a13 ^= d4; a18 ^= d4; a23 ^= d4;
|
||||
a04 ^= d0; a09 ^= d0; a14 ^= d0; a19 ^= d0; a24 ^= d0;
|
||||
|
||||
// rho/pi
|
||||
c1 = a01 << 1 | a01 >>> 63;
|
||||
a01 = a06 << 44 | a06 >>> 20;
|
||||
a06 = a09 << 20 | a09 >>> 44;
|
||||
a09 = a22 << 61 | a22 >>> 3;
|
||||
a22 = a14 << 39 | a14 >>> 25;
|
||||
a14 = a20 << 18 | a20 >>> 46;
|
||||
a20 = a02 << 62 | a02 >>> 2;
|
||||
a02 = a12 << 43 | a12 >>> 21;
|
||||
a12 = a13 << 25 | a13 >>> 39;
|
||||
a13 = a19 << 8 | a19 >>> 56;
|
||||
a19 = a23 << 56 | a23 >>> 8;
|
||||
a23 = a15 << 41 | a15 >>> 23;
|
||||
a15 = a04 << 27 | a04 >>> 37;
|
||||
a04 = a24 << 14 | a24 >>> 50;
|
||||
a24 = a21 << 2 | a21 >>> 62;
|
||||
a21 = a08 << 55 | a08 >>> 9;
|
||||
a08 = a16 << 45 | a16 >>> 19;
|
||||
a16 = a05 << 36 | a05 >>> 28;
|
||||
a05 = a03 << 28 | a03 >>> 36;
|
||||
a03 = a18 << 21 | a18 >>> 43;
|
||||
a18 = a17 << 15 | a17 >>> 49;
|
||||
a17 = a11 << 10 | a11 >>> 54;
|
||||
a11 = a07 << 6 | a07 >>> 58;
|
||||
a07 = a10 << 3 | a10 >>> 61;
|
||||
a10 = c1;
|
||||
|
||||
// chi
|
||||
c0 = a00 ^ (~a01 & a02);
|
||||
c1 = a01 ^ (~a02 & a03);
|
||||
a02 ^= ~a03 & a04;
|
||||
a03 ^= ~a04 & a00;
|
||||
a04 ^= ~a00 & a01;
|
||||
a00 = c0;
|
||||
a01 = c1;
|
||||
|
||||
c0 = a05 ^ (~a06 & a07);
|
||||
c1 = a06 ^ (~a07 & a08);
|
||||
a07 ^= ~a08 & a09;
|
||||
a08 ^= ~a09 & a05;
|
||||
a09 ^= ~a05 & a06;
|
||||
a05 = c0;
|
||||
a06 = c1;
|
||||
|
||||
c0 = a10 ^ (~a11 & a12);
|
||||
c1 = a11 ^ (~a12 & a13);
|
||||
a12 ^= ~a13 & a14;
|
||||
a13 ^= ~a14 & a10;
|
||||
a14 ^= ~a10 & a11;
|
||||
a10 = c0;
|
||||
a11 = c1;
|
||||
|
||||
c0 = a15 ^ (~a16 & a17);
|
||||
c1 = a16 ^ (~a17 & a18);
|
||||
a17 ^= ~a18 & a19;
|
||||
a18 ^= ~a19 & a15;
|
||||
a19 ^= ~a15 & a16;
|
||||
a15 = c0;
|
||||
a16 = c1;
|
||||
|
||||
c0 = a20 ^ (~a21 & a22);
|
||||
c1 = a21 ^ (~a22 & a23);
|
||||
a22 ^= ~a23 & a24;
|
||||
a23 ^= ~a24 & a20;
|
||||
a24 ^= ~a20 & a21;
|
||||
a20 = c0;
|
||||
a21 = c1;
|
||||
|
||||
// iota
|
||||
a00 ^= KeccakRoundConstants[i];
|
||||
}
|
||||
|
||||
A[ 0] = a00; A[ 1] = a01; A[ 2] = a02; A[ 3] = a03; A[ 4] = a04;
|
||||
A[ 5] = a05; A[ 6] = a06; A[ 7] = a07; A[ 8] = a08; A[ 9] = a09;
|
||||
A[10] = a10; A[11] = a11; A[12] = a12; A[13] = a13; A[14] = a14;
|
||||
A[15] = a15; A[16] = a16; A[17] = a17; A[18] = a18; A[19] = a19;
|
||||
A[20] = a20; A[21] = a21; A[22] = a22; A[23] = a23; A[24] = a24;
|
||||
}
|
||||
|
||||
protected CryptoServiceProperties cryptoServiceProperties()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
426
core/java/src/org/bouncycastle/crypto/digests/LongDigest.java
Normal file
426
core/java/src/org/bouncycastle/crypto/digests/LongDigest.java
Normal file
@@ -0,0 +1,426 @@
|
||||
package org.bouncycastle.crypto.digests;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoServiceProperties;
|
||||
import org.bouncycastle.crypto.CryptoServicePurpose;
|
||||
import org.bouncycastle.crypto.ExtendedDigest;
|
||||
import org.bouncycastle.util.Memoable;
|
||||
import org.bouncycastle.util.Pack;
|
||||
|
||||
/**
|
||||
* Base class for SHA-384 and SHA-512.
|
||||
*/
|
||||
public abstract class LongDigest
|
||||
implements ExtendedDigest, Memoable, EncodableDigest
|
||||
{
|
||||
private static final int BYTE_LENGTH = 128;
|
||||
|
||||
protected final CryptoServicePurpose purpose;
|
||||
|
||||
private byte[] xBuf = new byte[8];
|
||||
private int xBufOff;
|
||||
|
||||
private long byteCount1;
|
||||
private long byteCount2;
|
||||
|
||||
protected long H1, H2, H3, H4, H5, H6, H7, H8;
|
||||
|
||||
private long[] W = new long[80];
|
||||
private int wOff;
|
||||
|
||||
/**
|
||||
* Constructor for variable length word
|
||||
*/
|
||||
protected LongDigest()
|
||||
{
|
||||
this(CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for variable length word
|
||||
*/
|
||||
protected LongDigest(CryptoServicePurpose purpose)
|
||||
{
|
||||
this.purpose = purpose;
|
||||
|
||||
xBufOff = 0;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor. We are using copy constructors in place
|
||||
* of the Object.clone() interface as this interface is not
|
||||
* supported by J2ME.
|
||||
*/
|
||||
protected LongDigest(LongDigest t)
|
||||
{
|
||||
this.purpose = t.purpose;
|
||||
|
||||
copyIn(t);
|
||||
}
|
||||
|
||||
protected void copyIn(LongDigest t)
|
||||
{
|
||||
System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
|
||||
|
||||
xBufOff = t.xBufOff;
|
||||
byteCount1 = t.byteCount1;
|
||||
byteCount2 = t.byteCount2;
|
||||
|
||||
H1 = t.H1;
|
||||
H2 = t.H2;
|
||||
H3 = t.H3;
|
||||
H4 = t.H4;
|
||||
H5 = t.H5;
|
||||
H6 = t.H6;
|
||||
H7 = t.H7;
|
||||
H8 = t.H8;
|
||||
|
||||
System.arraycopy(t.W, 0, W, 0, t.W.length);
|
||||
wOff = t.wOff;
|
||||
}
|
||||
|
||||
protected void populateState(byte[] state)
|
||||
{
|
||||
System.arraycopy(xBuf, 0, state, 0, xBufOff);
|
||||
Pack.intToBigEndian(xBufOff, state, 8);
|
||||
Pack.longToBigEndian(byteCount1, state, 12);
|
||||
Pack.longToBigEndian(byteCount2, state, 20);
|
||||
Pack.longToBigEndian(H1, state, 28);
|
||||
Pack.longToBigEndian(H2, state, 36);
|
||||
Pack.longToBigEndian(H3, state, 44);
|
||||
Pack.longToBigEndian(H4, state, 52);
|
||||
Pack.longToBigEndian(H5, state, 60);
|
||||
Pack.longToBigEndian(H6, state, 68);
|
||||
Pack.longToBigEndian(H7, state, 76);
|
||||
Pack.longToBigEndian(H8, state, 84);
|
||||
|
||||
Pack.intToBigEndian(wOff, state, 92);
|
||||
for (int i = 0; i < wOff; i++)
|
||||
{
|
||||
Pack.longToBigEndian(W[i], state, 96 + (i * 8));
|
||||
}
|
||||
}
|
||||
|
||||
protected void restoreState(byte[] encodedState)
|
||||
{
|
||||
xBufOff = Pack.bigEndianToInt(encodedState, 8);
|
||||
System.arraycopy(encodedState, 0, xBuf, 0, xBufOff);
|
||||
byteCount1 = Pack.bigEndianToLong(encodedState, 12);
|
||||
byteCount2 = Pack.bigEndianToLong(encodedState, 20);
|
||||
|
||||
H1 = Pack.bigEndianToLong(encodedState, 28);
|
||||
H2 = Pack.bigEndianToLong(encodedState, 36);
|
||||
H3 = Pack.bigEndianToLong(encodedState, 44);
|
||||
H4 = Pack.bigEndianToLong(encodedState, 52);
|
||||
H5 = Pack.bigEndianToLong(encodedState, 60);
|
||||
H6 = Pack.bigEndianToLong(encodedState, 68);
|
||||
H7 = Pack.bigEndianToLong(encodedState, 76);
|
||||
H8 = Pack.bigEndianToLong(encodedState, 84);
|
||||
|
||||
wOff = Pack.bigEndianToInt(encodedState, 92);
|
||||
for (int i = 0; i < wOff; i++)
|
||||
{
|
||||
W[i] = Pack.bigEndianToLong(encodedState, 96 + (i * 8));
|
||||
}
|
||||
}
|
||||
|
||||
protected int getEncodedStateSize()
|
||||
{
|
||||
return 96 + (wOff * 8);
|
||||
}
|
||||
|
||||
public void update(
|
||||
byte in)
|
||||
{
|
||||
xBuf[xBufOff++] = in;
|
||||
|
||||
if (xBufOff == xBuf.length)
|
||||
{
|
||||
processWord(xBuf, 0);
|
||||
xBufOff = 0;
|
||||
}
|
||||
|
||||
byteCount1++;
|
||||
}
|
||||
|
||||
public void update(
|
||||
byte[] in,
|
||||
int inOff,
|
||||
int len)
|
||||
{
|
||||
//
|
||||
// fill the current word
|
||||
//
|
||||
while ((xBufOff != 0) && (len > 0))
|
||||
{
|
||||
update(in[inOff]);
|
||||
|
||||
inOff++;
|
||||
len--;
|
||||
}
|
||||
|
||||
//
|
||||
// process whole words.
|
||||
//
|
||||
while (len >= xBuf.length)
|
||||
{
|
||||
processWord(in, inOff);
|
||||
|
||||
inOff += xBuf.length;
|
||||
len -= xBuf.length;
|
||||
byteCount1 += xBuf.length;
|
||||
}
|
||||
|
||||
//
|
||||
// load in the remainder.
|
||||
//
|
||||
while (len > 0)
|
||||
{
|
||||
update(in[inOff]);
|
||||
|
||||
inOff++;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish()
|
||||
{
|
||||
adjustByteCounts();
|
||||
|
||||
long lowBitLength = byteCount1 << 3;
|
||||
long hiBitLength = byteCount2;
|
||||
|
||||
//
|
||||
// add the pad bytes.
|
||||
//
|
||||
update((byte)128);
|
||||
|
||||
while (xBufOff != 0)
|
||||
{
|
||||
update((byte)0);
|
||||
}
|
||||
|
||||
processLength(lowBitLength, hiBitLength);
|
||||
|
||||
processBlock();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
byteCount1 = 0;
|
||||
byteCount2 = 0;
|
||||
|
||||
xBufOff = 0;
|
||||
for (int i = 0; i < xBuf.length; i++)
|
||||
{
|
||||
xBuf[i] = 0;
|
||||
}
|
||||
|
||||
wOff = 0;
|
||||
for (int i = 0; i != W.length; i++)
|
||||
{
|
||||
W[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int getByteLength()
|
||||
{
|
||||
return BYTE_LENGTH;
|
||||
}
|
||||
|
||||
protected void processWord(
|
||||
byte[] in,
|
||||
int inOff)
|
||||
{
|
||||
W[wOff] = Pack.bigEndianToLong(in, inOff);
|
||||
|
||||
if (++wOff == 16)
|
||||
{
|
||||
processBlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* adjust the byte counts so that byteCount2 represents the
|
||||
* upper long (less 3 bits) word of the byte count.
|
||||
*/
|
||||
private void adjustByteCounts()
|
||||
{
|
||||
if (byteCount1 > 0x1fffffffffffffffL)
|
||||
{
|
||||
byteCount2 += (byteCount1 >>> 61);
|
||||
byteCount1 &= 0x1fffffffffffffffL;
|
||||
}
|
||||
}
|
||||
|
||||
protected void processLength(
|
||||
long lowW,
|
||||
long hiW)
|
||||
{
|
||||
if (wOff > 14)
|
||||
{
|
||||
processBlock();
|
||||
}
|
||||
|
||||
W[14] = hiW;
|
||||
W[15] = lowW;
|
||||
}
|
||||
|
||||
protected void processBlock()
|
||||
{
|
||||
adjustByteCounts();
|
||||
|
||||
//
|
||||
// expand 16 word block into 80 word blocks.
|
||||
//
|
||||
for (int t = 16; t <= 79; t++)
|
||||
{
|
||||
W[t] = Sigma1(W[t - 2]) + W[t - 7] + Sigma0(W[t - 15]) + W[t - 16];
|
||||
}
|
||||
|
||||
//
|
||||
// set up working variables.
|
||||
//
|
||||
long a = H1;
|
||||
long b = H2;
|
||||
long c = H3;
|
||||
long d = H4;
|
||||
long e = H5;
|
||||
long f = H6;
|
||||
long g = H7;
|
||||
long h = H8;
|
||||
|
||||
int t = 0;
|
||||
for(int i = 0; i < 10; i ++)
|
||||
{
|
||||
// t = 8 * i
|
||||
h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++];
|
||||
d += h;
|
||||
h += Sum0(a) + Maj(a, b, c);
|
||||
|
||||
// t = 8 * i + 1
|
||||
g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++];
|
||||
c += g;
|
||||
g += Sum0(h) + Maj(h, a, b);
|
||||
|
||||
// t = 8 * i + 2
|
||||
f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++];
|
||||
b += f;
|
||||
f += Sum0(g) + Maj(g, h, a);
|
||||
|
||||
// t = 8 * i + 3
|
||||
e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++];
|
||||
a += e;
|
||||
e += Sum0(f) + Maj(f, g, h);
|
||||
|
||||
// t = 8 * i + 4
|
||||
d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++];
|
||||
h += d;
|
||||
d += Sum0(e) + Maj(e, f, g);
|
||||
|
||||
// t = 8 * i + 5
|
||||
c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++];
|
||||
g += c;
|
||||
c += Sum0(d) + Maj(d, e, f);
|
||||
|
||||
// t = 8 * i + 6
|
||||
b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++];
|
||||
f += b;
|
||||
b += Sum0(c) + Maj(c, d, e);
|
||||
|
||||
// t = 8 * i + 7
|
||||
a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++];
|
||||
e += a;
|
||||
a += Sum0(b) + Maj(b, c, d);
|
||||
}
|
||||
|
||||
H1 += a;
|
||||
H2 += b;
|
||||
H3 += c;
|
||||
H4 += d;
|
||||
H5 += e;
|
||||
H6 += f;
|
||||
H7 += g;
|
||||
H8 += h;
|
||||
|
||||
//
|
||||
// reset the offset and clean out the word buffer.
|
||||
//
|
||||
wOff = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
W[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */
|
||||
private long Ch(
|
||||
long x,
|
||||
long y,
|
||||
long z)
|
||||
{
|
||||
return ((x & y) ^ ((~x) & z));
|
||||
}
|
||||
|
||||
private long Maj(
|
||||
long x,
|
||||
long y,
|
||||
long z)
|
||||
{
|
||||
return ((x & y) ^ (x & z) ^ (y & z));
|
||||
}
|
||||
|
||||
private long Sum0(
|
||||
long x)
|
||||
{
|
||||
return ((x << 36)|(x >>> 28)) ^ ((x << 30)|(x >>> 34)) ^ ((x << 25)|(x >>> 39));
|
||||
}
|
||||
|
||||
private long Sum1(
|
||||
long x)
|
||||
{
|
||||
return ((x << 50)|(x >>> 14)) ^ ((x << 46)|(x >>> 18)) ^ ((x << 23)|(x >>> 41));
|
||||
}
|
||||
|
||||
private long Sigma0(
|
||||
long x)
|
||||
{
|
||||
return ((x << 63)|(x >>> 1)) ^ ((x << 56)|(x >>> 8)) ^ (x >>> 7);
|
||||
}
|
||||
|
||||
private long Sigma1(
|
||||
long x)
|
||||
{
|
||||
return ((x << 45)|(x >>> 19)) ^ ((x << 3)|(x >>> 61)) ^ (x >>> 6);
|
||||
}
|
||||
|
||||
/* SHA-384 and SHA-512 Constants
|
||||
* (represent the first 64 bits of the fractional parts of the
|
||||
* cube roots of the first sixty-four prime numbers)
|
||||
*/
|
||||
static final long K[] = {
|
||||
0x428a2f98d728ae22L, 0x7137449123ef65cdL, 0xb5c0fbcfec4d3b2fL, 0xe9b5dba58189dbbcL,
|
||||
0x3956c25bf348b538L, 0x59f111f1b605d019L, 0x923f82a4af194f9bL, 0xab1c5ed5da6d8118L,
|
||||
0xd807aa98a3030242L, 0x12835b0145706fbeL, 0x243185be4ee4b28cL, 0x550c7dc3d5ffb4e2L,
|
||||
0x72be5d74f27b896fL, 0x80deb1fe3b1696b1L, 0x9bdc06a725c71235L, 0xc19bf174cf692694L,
|
||||
0xe49b69c19ef14ad2L, 0xefbe4786384f25e3L, 0x0fc19dc68b8cd5b5L, 0x240ca1cc77ac9c65L,
|
||||
0x2de92c6f592b0275L, 0x4a7484aa6ea6e483L, 0x5cb0a9dcbd41fbd4L, 0x76f988da831153b5L,
|
||||
0x983e5152ee66dfabL, 0xa831c66d2db43210L, 0xb00327c898fb213fL, 0xbf597fc7beef0ee4L,
|
||||
0xc6e00bf33da88fc2L, 0xd5a79147930aa725L, 0x06ca6351e003826fL, 0x142929670a0e6e70L,
|
||||
0x27b70a8546d22ffcL, 0x2e1b21385c26c926L, 0x4d2c6dfc5ac42aedL, 0x53380d139d95b3dfL,
|
||||
0x650a73548baf63deL, 0x766a0abb3c77b2a8L, 0x81c2c92e47edaee6L, 0x92722c851482353bL,
|
||||
0xa2bfe8a14cf10364L, 0xa81a664bbc423001L, 0xc24b8b70d0f89791L, 0xc76c51a30654be30L,
|
||||
0xd192e819d6ef5218L, 0xd69906245565a910L, 0xf40e35855771202aL, 0x106aa07032bbd1b8L,
|
||||
0x19a4c116b8d2d0c8L, 0x1e376c085141ab53L, 0x2748774cdf8eeb99L, 0x34b0bcb5e19b48a8L,
|
||||
0x391c0cb3c5c95a63L, 0x4ed8aa4ae3418acbL, 0x5b9cca4f7763e373L, 0x682e6ff3d6b2b8a3L,
|
||||
0x748f82ee5defb2fcL, 0x78a5636f43172f60L, 0x84c87814a1f0ab72L, 0x8cc702081a6439ecL,
|
||||
0x90befffa23631e28L, 0xa4506cebde82bde9L, 0xbef9a3f7b2c67915L, 0xc67178f2e372532bL,
|
||||
0xca273eceea26619cL, 0xd186b8c721c0c207L, 0xeada7dd6cde0eb1eL, 0xf57d4f7fee6ed178L,
|
||||
0x06f067aa72176fbaL, 0x0a637dc5a2c898a6L, 0x113f9804bef90daeL, 0x1b710b35131c471bL,
|
||||
0x28db77f523047d84L, 0x32caab7b40c72493L, 0x3c9ebe0a15c9bebcL, 0x431d67c49c100d4cL,
|
||||
0x4cc5d4becb3e42b6L, 0x597f299cfc657e2aL, 0x5fcb6fab3ad6faecL, 0x6c44198c4a475817L
|
||||
};
|
||||
|
||||
protected abstract CryptoServiceProperties cryptoServiceProperties();
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
package org.bouncycastle.crypto.digests;
|
||||
|
||||
|
||||
import org.bouncycastle.crypto.CryptoServicePurpose;
|
||||
|
||||
/**
|
||||
* implementation of SHA-3 based on following KeccakNISTInterface.c from https://keccak.noekeon.org/
|
||||
* <p>
|
||||
* Following the naming conventions used in the C source code to enable easy review of the implementation.
|
||||
*/
|
||||
public class SHA3Digest
|
||||
extends KeccakDigest
|
||||
{
|
||||
private static int checkBitLength(int bitLength)
|
||||
{
|
||||
switch (bitLength)
|
||||
{
|
||||
case 224:
|
||||
case 256:
|
||||
case 384:
|
||||
case 512:
|
||||
return bitLength;
|
||||
default:
|
||||
throw new IllegalArgumentException("'bitLength' " + bitLength + " not supported for SHA-3");
|
||||
}
|
||||
}
|
||||
|
||||
public SHA3Digest()
|
||||
{
|
||||
this(256, CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
public SHA3Digest(CryptoServicePurpose purpose)
|
||||
{
|
||||
this(256, purpose);
|
||||
}
|
||||
|
||||
public SHA3Digest(int bitLength)
|
||||
{
|
||||
super(checkBitLength(bitLength), CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
public SHA3Digest(int bitLength, CryptoServicePurpose purpose)
|
||||
{
|
||||
super(checkBitLength(bitLength), purpose);
|
||||
}
|
||||
|
||||
public SHA3Digest(SHA3Digest source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "SHA3-" + fixedOutputLength;
|
||||
}
|
||||
|
||||
public int doFinal(byte[] out, int outOff)
|
||||
{
|
||||
absorbBits(0x02, 2);
|
||||
|
||||
return super.doFinal(out, outOff);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Possible API change to support partial-byte suffixes.
|
||||
*/
|
||||
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
|
||||
{
|
||||
if (partialBits < 0 || partialBits > 7)
|
||||
{
|
||||
throw new IllegalArgumentException("'partialBits' must be in the range [0,7]");
|
||||
}
|
||||
|
||||
int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x02 << partialBits);
|
||||
int finalBits = partialBits + 2;
|
||||
|
||||
if (finalBits >= 8)
|
||||
{
|
||||
absorb((byte)finalInput);
|
||||
finalBits -= 8;
|
||||
finalInput >>>= 8;
|
||||
}
|
||||
|
||||
return super.doFinal(out, outOff, (byte)finalInput, finalBits);
|
||||
}
|
||||
}
|
150
core/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java
Normal file
150
core/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java
Normal file
@@ -0,0 +1,150 @@
|
||||
package org.bouncycastle.crypto.digests;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoServiceProperties;
|
||||
import org.bouncycastle.crypto.CryptoServicePurpose;
|
||||
import org.bouncycastle.crypto.CryptoServicesRegistrar;
|
||||
import org.bouncycastle.util.Memoable;
|
||||
import org.bouncycastle.util.Pack;
|
||||
|
||||
|
||||
/**
|
||||
* FIPS 180-2 implementation of SHA-512.
|
||||
*
|
||||
* <pre>
|
||||
* block word digest
|
||||
* SHA-1 512 32 160
|
||||
* SHA-256 512 32 256
|
||||
* SHA-384 1024 64 384
|
||||
* SHA-512 1024 64 512
|
||||
* </pre>
|
||||
*/
|
||||
public class SHA512Digest
|
||||
extends LongDigest
|
||||
{
|
||||
private static final int DIGEST_LENGTH = 64;
|
||||
|
||||
/**
|
||||
* Standard constructor
|
||||
*/
|
||||
public SHA512Digest()
|
||||
{
|
||||
this(CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard constructor, with purpose
|
||||
*/
|
||||
public SHA512Digest(CryptoServicePurpose purpose)
|
||||
{
|
||||
super(purpose);
|
||||
|
||||
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor. This will copy the state of the provided
|
||||
* message digest.
|
||||
*/
|
||||
public SHA512Digest(SHA512Digest t)
|
||||
{
|
||||
super(t);
|
||||
|
||||
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* State constructor - create a digest initialised with the state of a previous one.
|
||||
*
|
||||
* @param encodedState the encoded state from the originating digest.
|
||||
*/
|
||||
public SHA512Digest(byte[] encodedState)
|
||||
{
|
||||
super(CryptoServicePurpose.values()[encodedState[encodedState.length - 1]]);
|
||||
|
||||
restoreState(encodedState);
|
||||
|
||||
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "SHA-512";
|
||||
}
|
||||
|
||||
public int getDigestSize()
|
||||
{
|
||||
return DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
public int doFinal(
|
||||
byte[] out,
|
||||
int outOff)
|
||||
{
|
||||
finish();
|
||||
|
||||
Pack.longToBigEndian(H1, out, outOff);
|
||||
Pack.longToBigEndian(H2, out, outOff + 8);
|
||||
Pack.longToBigEndian(H3, out, outOff + 16);
|
||||
Pack.longToBigEndian(H4, out, outOff + 24);
|
||||
Pack.longToBigEndian(H5, out, outOff + 32);
|
||||
Pack.longToBigEndian(H6, out, outOff + 40);
|
||||
Pack.longToBigEndian(H7, out, outOff + 48);
|
||||
Pack.longToBigEndian(H8, out, outOff + 56);
|
||||
|
||||
reset();
|
||||
|
||||
return DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the chaining variables
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
super.reset();
|
||||
|
||||
/* SHA-512 initial hash value
|
||||
* The first 64 bits of the fractional parts of the square roots
|
||||
* of the first eight prime numbers
|
||||
*/
|
||||
H1 = 0x6a09e667f3bcc908L;
|
||||
H2 = 0xbb67ae8584caa73bL;
|
||||
H3 = 0x3c6ef372fe94f82bL;
|
||||
H4 = 0xa54ff53a5f1d36f1L;
|
||||
H5 = 0x510e527fade682d1L;
|
||||
H6 = 0x9b05688c2b3e6c1fL;
|
||||
H7 = 0x1f83d9abfb41bd6bL;
|
||||
H8 = 0x5be0cd19137e2179L;
|
||||
}
|
||||
|
||||
public Memoable copy()
|
||||
{
|
||||
return new SHA512Digest(this);
|
||||
}
|
||||
|
||||
public void reset(Memoable other)
|
||||
{
|
||||
SHA512Digest d = (SHA512Digest)other;
|
||||
|
||||
copyIn(d);
|
||||
}
|
||||
|
||||
public byte[] getEncodedState()
|
||||
{
|
||||
byte[] encoded = new byte[getEncodedStateSize() + 1];
|
||||
super.populateState(encoded);
|
||||
|
||||
encoded[encoded.length - 1] = (byte)purpose.ordinal();
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
protected CryptoServiceProperties cryptoServiceProperties()
|
||||
{
|
||||
return null;
|
||||
//return Utils.getDefaultProperties(this, 256, purpose);
|
||||
}
|
||||
}
|
||||
|
150
core/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java
Normal file
150
core/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java
Normal file
@@ -0,0 +1,150 @@
|
||||
package org.bouncycastle.crypto.digests;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoServiceProperties;
|
||||
import org.bouncycastle.crypto.CryptoServicePurpose;
|
||||
import org.bouncycastle.crypto.Xof;
|
||||
|
||||
|
||||
/**
|
||||
* implementation of SHAKE based on following KeccakNISTInterface.c from https://keccak.noekeon.org/
|
||||
* <p>
|
||||
* Following the naming conventions used in the C source code to enable easy review of the implementation.
|
||||
*/
|
||||
public class SHAKEDigest
|
||||
extends KeccakDigest
|
||||
implements Xof
|
||||
{
|
||||
private static int checkBitLength(int bitStrength)
|
||||
{
|
||||
switch (bitStrength)
|
||||
{
|
||||
case 128:
|
||||
case 256:
|
||||
return bitStrength;
|
||||
default:
|
||||
throw new IllegalArgumentException("'bitStrength' " + bitStrength + " not supported for SHAKE");
|
||||
}
|
||||
}
|
||||
|
||||
public SHAKEDigest()
|
||||
{
|
||||
this(128);
|
||||
}
|
||||
|
||||
public SHAKEDigest(CryptoServicePurpose purpose)
|
||||
{
|
||||
this(128, purpose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base constructor.
|
||||
*
|
||||
* @param bitStrength the security strength in bits of the XOF.
|
||||
*/
|
||||
public SHAKEDigest(int bitStrength)
|
||||
{
|
||||
super(checkBitLength(bitStrength), CryptoServicePurpose.ANY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base constructor.
|
||||
*
|
||||
* @param bitStrength the security strength in bits of the XOF.
|
||||
* @param purpose the purpose of the digest will be used for.
|
||||
*/
|
||||
public SHAKEDigest(int bitStrength, CryptoServicePurpose purpose)
|
||||
{
|
||||
super(checkBitLength(bitStrength), purpose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone constructor
|
||||
*
|
||||
* @param source the other digest to be copied.
|
||||
*/
|
||||
public SHAKEDigest(SHAKEDigest source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "SHAKE" + fixedOutputLength;
|
||||
}
|
||||
|
||||
public int getDigestSize()
|
||||
{
|
||||
return fixedOutputLength / 4;
|
||||
}
|
||||
|
||||
public int doFinal(byte[] out, int outOff)
|
||||
{
|
||||
return doFinal(out, outOff, getDigestSize());
|
||||
}
|
||||
|
||||
public int doFinal(byte[] out, int outOff, int outLen)
|
||||
{
|
||||
int length = doOutput(out, outOff, outLen);
|
||||
|
||||
reset();
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public int doOutput(byte[] out, int outOff, int outLen)
|
||||
{
|
||||
if (!squeezing)
|
||||
{
|
||||
absorbBits(0x0F, 4);
|
||||
}
|
||||
|
||||
squeeze(out, outOff, ((long)outLen) * 8);
|
||||
|
||||
return outLen;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Possible API change to support partial-byte suffixes.
|
||||
*/
|
||||
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
|
||||
{
|
||||
return doFinal(out, outOff, getDigestSize(), partialByte, partialBits);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Possible API change to support partial-byte suffixes.
|
||||
*/
|
||||
protected int doFinal(byte[] out, int outOff, int outLen, byte partialByte, int partialBits)
|
||||
{
|
||||
if (partialBits < 0 || partialBits > 7)
|
||||
{
|
||||
throw new IllegalArgumentException("'partialBits' must be in the range [0,7]");
|
||||
}
|
||||
|
||||
int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits);
|
||||
int finalBits = partialBits + 4;
|
||||
|
||||
if (finalBits >= 8)
|
||||
{
|
||||
absorb((byte)finalInput);
|
||||
finalBits -= 8;
|
||||
finalInput >>>= 8;
|
||||
}
|
||||
|
||||
if (finalBits > 0)
|
||||
{
|
||||
absorbBits(finalInput, finalBits);
|
||||
}
|
||||
|
||||
squeeze(out, outOff, ((long)outLen) * 8);
|
||||
|
||||
reset();
|
||||
|
||||
return outLen;
|
||||
}
|
||||
|
||||
protected CryptoServiceProperties cryptoServiceProperties()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Message digest classes.
|
||||
*/
|
||||
package org.bouncycastle.crypto.digests;
|
4
core/java/src/org/bouncycastle/crypto/package-info.java
Normal file
4
core/java/src/org/bouncycastle/crypto/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Base classes for the lightweight API.
|
||||
*/
|
||||
package org.bouncycastle.crypto;
|
@@ -0,0 +1,20 @@
|
||||
package org.bouncycastle.crypto.params;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
|
||||
public class AsymmetricKeyParameter
|
||||
implements CipherParameters
|
||||
{
|
||||
boolean privateKey;
|
||||
|
||||
public AsymmetricKeyParameter(
|
||||
boolean privateKey)
|
||||
{
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public boolean isPrivate()
|
||||
{
|
||||
return privateKey;
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package org.bouncycastle.crypto.params;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.util.Util;
|
||||
|
||||
public class ParametersWithContext
|
||||
implements CipherParameters
|
||||
{
|
||||
private CipherParameters parameters;
|
||||
private byte[] context;
|
||||
|
||||
public ParametersWithContext(
|
||||
CipherParameters parameters,
|
||||
byte[] context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new NullPointerException("'context' cannot be null");
|
||||
}
|
||||
|
||||
this.parameters = parameters;
|
||||
this.context = Util.clone(context);
|
||||
}
|
||||
|
||||
public void copyContextTo(byte[] buf, int off, int len)
|
||||
{
|
||||
if (context.length != len)
|
||||
{
|
||||
throw new IllegalArgumentException("len");
|
||||
}
|
||||
|
||||
System.arraycopy(context, 0, buf, off, len);
|
||||
}
|
||||
|
||||
public byte[] getContext()
|
||||
{
|
||||
return Util.clone(context);
|
||||
}
|
||||
|
||||
public int getContextLength()
|
||||
{
|
||||
return context.length;
|
||||
}
|
||||
|
||||
public CipherParameters getParameters()
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package org.bouncycastle.crypto.params;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.CryptoServicesRegistrar;
|
||||
|
||||
public class ParametersWithRandom
|
||||
implements CipherParameters
|
||||
{
|
||||
private SecureRandom random;
|
||||
private CipherParameters parameters;
|
||||
|
||||
public ParametersWithRandom(
|
||||
CipherParameters parameters,
|
||||
SecureRandom random)
|
||||
{
|
||||
this.random = CryptoServicesRegistrar.getSecureRandom(random);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public ParametersWithRandom(
|
||||
CipherParameters parameters)
|
||||
{
|
||||
this(parameters, null);
|
||||
}
|
||||
|
||||
public SecureRandom getRandom()
|
||||
{
|
||||
return random;
|
||||
}
|
||||
|
||||
public CipherParameters getParameters()
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Classes for parameter objects for ciphers and generators.
|
||||
*/
|
||||
package org.bouncycastle.crypto.params;
|
6
core/java/src/org/bouncycastle/package-info.java
Normal file
6
core/java/src/org/bouncycastle/package-info.java
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* This is a small portion of bouncycastle 1.80 for MLDSA, modified to reduce dependencies.
|
||||
* MLKEM is in router.jar.
|
||||
* @since 0.9.80
|
||||
*/
|
||||
package org.bouncycastle;
|
55
core/java/src/org/bouncycastle/pqc/crypto/DigestUtils.java
Normal file
55
core/java/src/org/bouncycastle/pqc/crypto/DigestUtils.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package org.bouncycastle.pqc.crypto;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
/*
|
||||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
|
||||
*/
|
||||
|
||||
public class DigestUtils
|
||||
{
|
||||
|
||||
/**
|
||||
* Retrieve oid of hash/XOF function used to calculate pre-hash signatures
|
||||
* for pre-hash versions of slh-dsa and ml-dsa
|
||||
*/
|
||||
static final Map digestOids = new HashMap<String, ASN1ObjectIdentifier>();
|
||||
|
||||
static
|
||||
{
|
||||
/*
|
||||
FIXME only for HashMLDSA
|
||||
|
||||
|
||||
digestOids.put("SHA-1", X509ObjectIdentifiers.id_SHA1);
|
||||
digestOids.put("SHA-224", NISTObjectIdentifiers.id_sha224);
|
||||
digestOids.put("SHA-256", NISTObjectIdentifiers.id_sha256);
|
||||
digestOids.put("SHA-384", NISTObjectIdentifiers.id_sha384);
|
||||
digestOids.put("SHA-512", NISTObjectIdentifiers.id_sha512);
|
||||
digestOids.put("SHA-512/224", NISTObjectIdentifiers.id_sha512_224);
|
||||
digestOids.put("SHA-512/256", NISTObjectIdentifiers.id_sha512_256);
|
||||
|
||||
digestOids.put("SHA3-224", NISTObjectIdentifiers.id_sha3_224);
|
||||
digestOids.put("SHA3-256", NISTObjectIdentifiers.id_sha3_256);
|
||||
digestOids.put("SHA3-384", NISTObjectIdentifiers.id_sha3_384);
|
||||
digestOids.put("SHA3-512", NISTObjectIdentifiers.id_sha3_512);
|
||||
|
||||
digestOids.put("SHAKE128", NISTObjectIdentifiers.id_shake128);
|
||||
digestOids.put("SHAKE256", NISTObjectIdentifiers.id_shake256);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
public static ASN1ObjectIdentifier getDigestOid(String digestName)
|
||||
{
|
||||
if (digestOids.containsKey(digestName))
|
||||
{
|
||||
return (ASN1ObjectIdentifier)digestOids.get(digestName);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("unrecognised digest algorithm: " + digestName);
|
||||
}
|
||||
}
|
715
core/java/src/org/bouncycastle/pqc/crypto/mldsa/MLDSAEngine.java
Normal file
715
core/java/src/org/bouncycastle/pqc/crypto/mldsa/MLDSAEngine.java
Normal file
@@ -0,0 +1,715 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SHAKEDigest;
|
||||
import org.bouncycastle.util.Util;
|
||||
|
||||
class MLDSAEngine
|
||||
{
|
||||
private final SecureRandom random;
|
||||
|
||||
private final SHAKEDigest shake256Digest = new SHAKEDigest(256);
|
||||
|
||||
public final static int DilithiumN = 256;
|
||||
public final static int DilithiumQ = 8380417;
|
||||
public final static int DilithiumQinv = 58728449; // q^(-1) mod 2^32
|
||||
public final static int DilithiumD = 13;
|
||||
public final static int DilithiumRootOfUnity = 1753;
|
||||
public final static int SeedBytes = 32;
|
||||
public final static int CrhBytes = 64;
|
||||
public final static int RndBytes = 32;
|
||||
public final static int TrBytes = 64;
|
||||
|
||||
public final static int DilithiumPolyT1PackedBytes = 320;
|
||||
public final static int DilithiumPolyT0PackedBytes = 416;
|
||||
|
||||
private final int DilithiumPolyVecHPackedBytes;
|
||||
|
||||
private final int DilithiumPolyZPackedBytes;
|
||||
private final int DilithiumPolyW1PackedBytes;
|
||||
private final int DilithiumPolyEtaPackedBytes;
|
||||
|
||||
private final int DilithiumMode;
|
||||
|
||||
private final int DilithiumK;
|
||||
private final int DilithiumL;
|
||||
private final int DilithiumEta;
|
||||
private final int DilithiumTau;
|
||||
private final int DilithiumBeta;
|
||||
private final int DilithiumGamma1;
|
||||
private final int DilithiumGamma2;
|
||||
private final int DilithiumOmega;
|
||||
private final int DilithiumCTilde;
|
||||
|
||||
private final int CryptoPublicKeyBytes;
|
||||
private final int CryptoSecretKeyBytes;
|
||||
private final int CryptoBytes;
|
||||
|
||||
private final int PolyUniformGamma1NBlocks;
|
||||
|
||||
private final Symmetric symmetric;
|
||||
|
||||
protected Symmetric GetSymmetric()
|
||||
{
|
||||
return symmetric;
|
||||
}
|
||||
|
||||
int getDilithiumPolyVecHPackedBytes()
|
||||
{
|
||||
return DilithiumPolyVecHPackedBytes;
|
||||
}
|
||||
|
||||
int getDilithiumPolyZPackedBytes()
|
||||
{
|
||||
return DilithiumPolyZPackedBytes;
|
||||
}
|
||||
|
||||
int getDilithiumPolyW1PackedBytes()
|
||||
{
|
||||
return DilithiumPolyW1PackedBytes;
|
||||
}
|
||||
|
||||
int getDilithiumPolyEtaPackedBytes()
|
||||
{
|
||||
return DilithiumPolyEtaPackedBytes;
|
||||
}
|
||||
|
||||
int getDilithiumMode()
|
||||
{
|
||||
return DilithiumMode;
|
||||
}
|
||||
|
||||
int getDilithiumK()
|
||||
{
|
||||
return DilithiumK;
|
||||
}
|
||||
|
||||
int getDilithiumL()
|
||||
{
|
||||
return DilithiumL;
|
||||
}
|
||||
|
||||
int getDilithiumEta()
|
||||
{
|
||||
return DilithiumEta;
|
||||
}
|
||||
|
||||
int getDilithiumTau()
|
||||
{
|
||||
return DilithiumTau;
|
||||
}
|
||||
|
||||
int getDilithiumBeta()
|
||||
{
|
||||
return DilithiumBeta;
|
||||
}
|
||||
|
||||
int getDilithiumGamma1()
|
||||
{
|
||||
return DilithiumGamma1;
|
||||
}
|
||||
|
||||
int getDilithiumGamma2()
|
||||
{
|
||||
return DilithiumGamma2;
|
||||
}
|
||||
|
||||
int getDilithiumOmega()
|
||||
{
|
||||
return DilithiumOmega;
|
||||
}
|
||||
|
||||
int getDilithiumCTilde()
|
||||
{
|
||||
return DilithiumCTilde;
|
||||
}
|
||||
|
||||
int getCryptoPublicKeyBytes()
|
||||
{
|
||||
return CryptoPublicKeyBytes;
|
||||
}
|
||||
|
||||
int getCryptoSecretKeyBytes()
|
||||
{
|
||||
return CryptoSecretKeyBytes;
|
||||
}
|
||||
|
||||
int getCryptoBytes()
|
||||
{
|
||||
return CryptoBytes;
|
||||
}
|
||||
|
||||
int getPolyUniformGamma1NBlocks()
|
||||
{
|
||||
return this.PolyUniformGamma1NBlocks;
|
||||
}
|
||||
|
||||
MLDSAEngine(int mode, SecureRandom random)
|
||||
{
|
||||
this.DilithiumMode = mode;
|
||||
switch (mode)
|
||||
{
|
||||
case 2:
|
||||
this.DilithiumK = 4;
|
||||
this.DilithiumL = 4;
|
||||
this.DilithiumEta = 2;
|
||||
this.DilithiumTau = 39;
|
||||
this.DilithiumBeta = 78;
|
||||
this.DilithiumGamma1 = (1 << 17);
|
||||
this.DilithiumGamma2 = ((DilithiumQ - 1) / 88);
|
||||
this.DilithiumOmega = 80;
|
||||
this.DilithiumPolyZPackedBytes = 576;
|
||||
this.DilithiumPolyW1PackedBytes = 192;
|
||||
this.DilithiumPolyEtaPackedBytes = 96;
|
||||
this.DilithiumCTilde = 32;
|
||||
break;
|
||||
case 3:
|
||||
this.DilithiumK = 6;
|
||||
this.DilithiumL = 5;
|
||||
this.DilithiumEta = 4;
|
||||
this.DilithiumTau = 49;
|
||||
this.DilithiumBeta = 196;
|
||||
this.DilithiumGamma1 = (1 << 19);
|
||||
this.DilithiumGamma2 = ((DilithiumQ - 1) / 32);
|
||||
this.DilithiumOmega = 55;
|
||||
this.DilithiumPolyZPackedBytes = 640;
|
||||
this.DilithiumPolyW1PackedBytes = 128;
|
||||
this.DilithiumPolyEtaPackedBytes = 128;
|
||||
this.DilithiumCTilde = 48;
|
||||
break;
|
||||
case 5:
|
||||
this.DilithiumK = 8;
|
||||
this.DilithiumL = 7;
|
||||
this.DilithiumEta = 2;
|
||||
this.DilithiumTau = 60;
|
||||
this.DilithiumBeta = 120;
|
||||
this.DilithiumGamma1 = (1 << 19);
|
||||
this.DilithiumGamma2 = ((DilithiumQ - 1) / 32);
|
||||
this.DilithiumOmega = 75;
|
||||
this.DilithiumPolyZPackedBytes = 640;
|
||||
this.DilithiumPolyW1PackedBytes = 128;
|
||||
this.DilithiumPolyEtaPackedBytes = 96;
|
||||
this.DilithiumCTilde = 64;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("The mode " + mode + "is not supported by Crystals Dilithium!");
|
||||
}
|
||||
|
||||
this.symmetric = new Symmetric.ShakeSymmetric();
|
||||
|
||||
this.random = random;
|
||||
this.DilithiumPolyVecHPackedBytes = this.DilithiumOmega + this.DilithiumK;
|
||||
this.CryptoPublicKeyBytes = SeedBytes + this.DilithiumK * DilithiumPolyT1PackedBytes;
|
||||
this.CryptoSecretKeyBytes =
|
||||
(
|
||||
2 * SeedBytes
|
||||
+ TrBytes
|
||||
+ DilithiumL * this.DilithiumPolyEtaPackedBytes
|
||||
+ DilithiumK * this.DilithiumPolyEtaPackedBytes
|
||||
+ DilithiumK * DilithiumPolyT0PackedBytes
|
||||
);
|
||||
this.CryptoBytes = DilithiumCTilde + DilithiumL * this.DilithiumPolyZPackedBytes + this.DilithiumPolyVecHPackedBytes;
|
||||
|
||||
if (this.DilithiumGamma1 == (1 << 17))
|
||||
{
|
||||
this.PolyUniformGamma1NBlocks = ((576 + symmetric.stream256BlockBytes - 1) / symmetric.stream256BlockBytes);
|
||||
}
|
||||
else if (this.DilithiumGamma1 == (1 << 19))
|
||||
{
|
||||
this.PolyUniformGamma1NBlocks = ((640 + symmetric.stream256BlockBytes - 1) / symmetric.stream256BlockBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Dilithium Gamma1!");
|
||||
}
|
||||
}
|
||||
|
||||
//Internal functions are deterministic. No randomness is sampled inside them
|
||||
byte[][] generateKeyPairInternal(byte[] seed)
|
||||
{
|
||||
byte[] buf = new byte[2 * SeedBytes + CrhBytes];
|
||||
byte[] tr = new byte[TrBytes];
|
||||
|
||||
byte[] rho = new byte[SeedBytes],
|
||||
rhoPrime = new byte[CrhBytes],
|
||||
key = new byte[SeedBytes];
|
||||
|
||||
PolyVecMatrix aMatrix = new PolyVecMatrix(this);
|
||||
|
||||
PolyVecL s1 = new PolyVecL(this), s1hat;
|
||||
PolyVecK s2 = new PolyVecK(this), t1 = new PolyVecK(this), t0 = new PolyVecK(this);
|
||||
|
||||
|
||||
shake256Digest.update(seed, 0, SeedBytes);
|
||||
|
||||
//Domain separation
|
||||
shake256Digest.update((byte)DilithiumK);
|
||||
shake256Digest.update((byte)DilithiumL);
|
||||
|
||||
shake256Digest.doFinal(buf, 0, 2 * SeedBytes + CrhBytes);
|
||||
// System.out.print("buf = ");
|
||||
// Helper.printByteArray(buf);
|
||||
|
||||
System.arraycopy(buf, 0, rho, 0, SeedBytes);
|
||||
System.arraycopy(buf, SeedBytes, rhoPrime, 0, CrhBytes);
|
||||
System.arraycopy(buf, SeedBytes + CrhBytes, key, 0, SeedBytes);
|
||||
// System.out.println("key = ");
|
||||
// Helper.printByteArray(key);
|
||||
|
||||
aMatrix.expandMatrix(rho);
|
||||
// System.out.print(aMatrix.toString("aMatrix"));
|
||||
|
||||
// System.out.println("rhoPrime = ");
|
||||
// Helper.printByteArray(rhoPrime);
|
||||
s1.uniformEta(rhoPrime, (short)0);
|
||||
// System.out.println(s1.toString("s1"));
|
||||
|
||||
s2.uniformEta(rhoPrime, (short)DilithiumL);
|
||||
|
||||
s1hat = new PolyVecL(this);
|
||||
|
||||
s1.copyPolyVecL(s1hat);
|
||||
s1hat.polyVecNtt();
|
||||
|
||||
// System.out.println(s1hat.toString("s1hat"));
|
||||
|
||||
aMatrix.pointwiseMontgomery(t1, s1hat);
|
||||
// System.out.println(t1.toString("t1"));
|
||||
|
||||
t1.reduce();
|
||||
t1.invNttToMont();
|
||||
|
||||
t1.addPolyVecK(s2);
|
||||
// System.out.println(s2.toString("s2"));
|
||||
// System.out.println(t1.toString("t1"));
|
||||
t1.conditionalAddQ();
|
||||
t1.power2Round(t0);
|
||||
|
||||
// System.out.println(t1.toString("t1"));
|
||||
// System.out.println(t0.toString("t0"));
|
||||
|
||||
|
||||
byte[] encT1 = Packing.packPublicKey(t1, this);
|
||||
// System.out.println("pk engine = ");
|
||||
// Helper.printByteArray(pk);
|
||||
|
||||
shake256Digest.update(rho, 0, rho.length);
|
||||
shake256Digest.update(encT1, 0, encT1.length);
|
||||
shake256Digest.doFinal(tr, 0, TrBytes);
|
||||
|
||||
byte[][] sk = Packing.packSecretKey(rho, tr, key, t0, s1, s2, this);
|
||||
|
||||
return new byte[][]{sk[0], sk[1], sk[2], sk[3], sk[4], sk[5], encT1, seed};
|
||||
}
|
||||
|
||||
byte[] deriveT1(byte[] rho, byte[] key, byte[] tr, byte[] s1Enc, byte[] s2Enc, byte[] t0Enc)
|
||||
{
|
||||
PolyVecMatrix aMatrix = new PolyVecMatrix(this);
|
||||
|
||||
PolyVecL s1 = new PolyVecL(this), s1hat;
|
||||
PolyVecK s2 = new PolyVecK(this), t1 = new PolyVecK(this), t0 = new PolyVecK(this);
|
||||
|
||||
Packing.unpackSecretKey(t0, s1, s2, t0Enc, s1Enc, s2Enc, this);
|
||||
|
||||
// System.out.print("rho = ");
|
||||
// Helper.printByteArray(rho);
|
||||
|
||||
// System.out.println("key = ");
|
||||
// Helper.printByteArray(key);
|
||||
|
||||
aMatrix.expandMatrix(rho);
|
||||
// System.out.print(aMatrix.toString("aMatrix"));
|
||||
|
||||
s1hat = new PolyVecL(this);
|
||||
|
||||
s1.copyPolyVecL(s1hat);
|
||||
s1hat.polyVecNtt();
|
||||
|
||||
// System.out.println(s1hat.toString("s1hat"));
|
||||
|
||||
aMatrix.pointwiseMontgomery(t1, s1hat);
|
||||
// System.out.println(t1.toString("t1"));
|
||||
|
||||
t1.reduce();
|
||||
t1.invNttToMont();
|
||||
|
||||
t1.addPolyVecK(s2);
|
||||
// System.out.println(s2.toString("s2"));
|
||||
// System.out.println(t1.toString("t1"));
|
||||
t1.conditionalAddQ();
|
||||
t1.power2Round(t0);
|
||||
|
||||
// System.out.println(t1.toString("t1"));
|
||||
// System.out.println(t0.toString("t0"));
|
||||
|
||||
byte[] encT1 = Packing.packPublicKey(t1, this);
|
||||
// System.out.println("enc t1 = ");
|
||||
// Helper.printByteArray(encT1);
|
||||
return encT1;
|
||||
}
|
||||
|
||||
SHAKEDigest getShake256Digest()
|
||||
{
|
||||
return new SHAKEDigest(shake256Digest);
|
||||
}
|
||||
|
||||
void initSign(byte[] tr, boolean isPreHash, byte[] ctx)
|
||||
{
|
||||
shake256Digest.update(tr, 0, TrBytes);
|
||||
if (ctx != null)
|
||||
{
|
||||
shake256Digest.update(isPreHash ? (byte)1 : (byte)0);
|
||||
shake256Digest.update((byte)ctx.length);
|
||||
shake256Digest.update(ctx, 0, ctx.length);
|
||||
}
|
||||
}
|
||||
|
||||
void initVerify(byte[] rho, byte[] encT1, boolean isPreHash, byte[] ctx)
|
||||
{
|
||||
byte[] mu = new byte[TrBytes];
|
||||
|
||||
shake256Digest.update(rho, 0, rho.length);
|
||||
shake256Digest.update(encT1, 0, encT1.length);
|
||||
shake256Digest.doFinal(mu, 0, TrBytes);
|
||||
|
||||
shake256Digest.update(mu, 0, TrBytes);
|
||||
if (ctx != null)
|
||||
{
|
||||
shake256Digest.update(isPreHash ? (byte)1 : (byte)0);
|
||||
shake256Digest.update((byte)ctx.length);
|
||||
shake256Digest.update(ctx, 0, ctx.length);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] signInternal(byte[] msg, int msglen, byte[] rho, byte[] key, byte[] t0Enc, byte[] s1Enc, byte[] s2Enc, byte[] rnd)
|
||||
{
|
||||
SHAKEDigest shake256 = new SHAKEDigest(shake256Digest);
|
||||
|
||||
shake256.update(msg, 0, msglen);
|
||||
|
||||
return generateSignature(shake256, rho, key, t0Enc, s1Enc, s2Enc, rnd);
|
||||
}
|
||||
|
||||
byte[] generateSignature(SHAKEDigest shake256Digest, byte[] rho, byte[] key, byte[] t0Enc, byte[] s1Enc, byte[] s2Enc, byte[] rnd)
|
||||
{
|
||||
byte[] mu = new byte[CrhBytes];
|
||||
|
||||
shake256Digest.doFinal(mu, 0, CrhBytes);
|
||||
|
||||
int n;
|
||||
byte[] outSig = new byte[CryptoBytes];
|
||||
byte[] rhoPrime = new byte[CrhBytes];
|
||||
short nonce = 0;
|
||||
PolyVecL s1 = new PolyVecL(this), y = new PolyVecL(this), z = new PolyVecL(this);
|
||||
PolyVecK t0 = new PolyVecK(this), s2 = new PolyVecK(this), w1 = new PolyVecK(this), w0 = new PolyVecK(this), h = new PolyVecK(this);
|
||||
Poly cp = new Poly(this);
|
||||
PolyVecMatrix aMatrix = new PolyVecMatrix(this);
|
||||
|
||||
Packing.unpackSecretKey(t0, s1, s2, t0Enc, s1Enc, s2Enc, this);
|
||||
|
||||
byte[] keyMu = Arrays.copyOf(key, SeedBytes + RndBytes + CrhBytes);
|
||||
System.arraycopy(rnd, 0, keyMu, SeedBytes, RndBytes);
|
||||
System.arraycopy(mu, 0, keyMu, SeedBytes + RndBytes, CrhBytes);
|
||||
shake256Digest.update(keyMu, 0, SeedBytes + RndBytes + CrhBytes);
|
||||
shake256Digest.doFinal(rhoPrime, 0, CrhBytes);
|
||||
|
||||
aMatrix.expandMatrix(rho);
|
||||
|
||||
s1.polyVecNtt();
|
||||
s2.polyVecNtt();
|
||||
|
||||
t0.polyVecNtt();
|
||||
|
||||
int count = 0;
|
||||
while (count < 1000)
|
||||
{
|
||||
count++;
|
||||
// Sample intermediate vector
|
||||
y.uniformGamma1(rhoPrime, nonce++);
|
||||
|
||||
y.copyPolyVecL(z);
|
||||
z.polyVecNtt();
|
||||
|
||||
// Matrix-vector multiplication
|
||||
aMatrix.pointwiseMontgomery(w1, z);
|
||||
w1.reduce();
|
||||
w1.invNttToMont();
|
||||
|
||||
// Decompose w and call the random oracle
|
||||
w1.conditionalAddQ();
|
||||
w1.decompose(w0);
|
||||
|
||||
System.arraycopy(w1.packW1(), 0, outSig, 0, DilithiumK * DilithiumPolyW1PackedBytes);
|
||||
|
||||
shake256Digest.update(mu, 0, CrhBytes);
|
||||
shake256Digest.update(outSig, 0, DilithiumK * DilithiumPolyW1PackedBytes);
|
||||
shake256Digest.doFinal(outSig, 0, DilithiumCTilde);
|
||||
|
||||
cp.challenge(Arrays.copyOfRange(outSig, 0, DilithiumCTilde)); // uses only the first DilithiumCTilde bytes of sig
|
||||
cp.polyNtt();
|
||||
|
||||
// Compute z, reject if it reveals secret
|
||||
z.pointwisePolyMontgomery(cp, s1);
|
||||
z.invNttToMont();
|
||||
z.addPolyVecL(y);
|
||||
z.reduce();
|
||||
if (z.checkNorm(DilithiumGamma1 - DilithiumBeta))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
h.pointwisePolyMontgomery(cp, s2);
|
||||
h.invNttToMont();
|
||||
w0.subtract(h);
|
||||
w0.reduce();
|
||||
if (w0.checkNorm(DilithiumGamma2 - DilithiumBeta))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
h.pointwisePolyMontgomery(cp, t0);
|
||||
h.invNttToMont();
|
||||
h.reduce();
|
||||
if (h.checkNorm(DilithiumGamma2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
w0.addPolyVecK(h);
|
||||
w0.conditionalAddQ();
|
||||
n = h.makeHint(w0, w1);
|
||||
if (n > DilithiumOmega)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return Packing.packSignature(outSig, z, h, this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean verifyInternal(byte[] sig, int siglen, SHAKEDigest shake256Digest, byte[] rho, byte[] encT1)
|
||||
{
|
||||
if (siglen != CryptoBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// System.out.println("publickey = ");
|
||||
// Helper.printByteArray(publicKey);
|
||||
byte[] buf,
|
||||
mu = new byte[CrhBytes],
|
||||
c,
|
||||
c2 = new byte[DilithiumCTilde];
|
||||
Poly cp = new Poly(this);
|
||||
PolyVecMatrix aMatrix = new PolyVecMatrix(this);
|
||||
PolyVecL z = new PolyVecL(this);
|
||||
PolyVecK t1 = new PolyVecK(this), w1 = new PolyVecK(this), h = new PolyVecK(this);
|
||||
|
||||
t1 = Packing.unpackPublicKey(t1, encT1, this);
|
||||
|
||||
// System.out.println(t1.toString("t1"));
|
||||
|
||||
// System.out.println("rho = ");
|
||||
// Helper.printByteArray(rho);
|
||||
|
||||
if (!Packing.unpackSignature(z, h, sig, this))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
c = Arrays.copyOfRange(sig, 0, DilithiumCTilde);
|
||||
|
||||
// System.out.println(z.toString("z"));
|
||||
// System.out.println(h.toString("h"));
|
||||
|
||||
if (z.checkNorm(getDilithiumGamma1() - getDilithiumBeta()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shake256Digest.doFinal(mu, 0);
|
||||
|
||||
// System.out.println("mu after = ");
|
||||
// Helper.printByteArray(mu);
|
||||
|
||||
// Matrix-vector multiplication; compute Az - c2^dt1
|
||||
cp.challenge(Arrays.copyOfRange(c, 0, DilithiumCTilde)); // use only first DilithiumCTilde of c.
|
||||
// System.out.println("cp = ");
|
||||
// System.out.println(cp.toString());
|
||||
|
||||
aMatrix.expandMatrix(rho);
|
||||
// System.out.println(aMatrix.toString("aMatrix = "));
|
||||
|
||||
|
||||
z.polyVecNtt();
|
||||
aMatrix.pointwiseMontgomery(w1, z);
|
||||
|
||||
cp.polyNtt();
|
||||
// System.out.println("cp = ");
|
||||
// System.out.println(cp.toString());
|
||||
|
||||
t1.shiftLeft();
|
||||
t1.polyVecNtt();
|
||||
t1.pointwisePolyMontgomery(cp, t1);
|
||||
|
||||
// System.out.println(t1.toString("t1"));
|
||||
|
||||
w1.subtract(t1);
|
||||
w1.reduce();
|
||||
w1.invNttToMont();
|
||||
|
||||
// System.out.println(w1.toString("w1 before caddq"));
|
||||
|
||||
// Reconstruct w1
|
||||
w1.conditionalAddQ();
|
||||
// System.out.println(w1.toString("w1 before hint"));
|
||||
w1.useHint(w1, h);
|
||||
// System.out.println(w1.toString("w1"));
|
||||
|
||||
buf = w1.packW1();
|
||||
|
||||
// System.out.println("buf = ");
|
||||
// Helper.printByteArray(buf);
|
||||
|
||||
// System.out.println("mu = ");
|
||||
// Helper.printByteArray(mu);
|
||||
|
||||
SHAKEDigest shakeDigest256 = new SHAKEDigest(256);
|
||||
shakeDigest256.update(mu, 0, CrhBytes);
|
||||
shakeDigest256.update(buf, 0, DilithiumK * DilithiumPolyW1PackedBytes);
|
||||
shakeDigest256.doFinal(c2, 0, DilithiumCTilde);
|
||||
|
||||
// System.out.println("c = ");
|
||||
// Helper.printByteArray(c);
|
||||
|
||||
// System.out.println("c2 = ");
|
||||
// Helper.printByteArray(c2);
|
||||
|
||||
|
||||
return Util.constantTimeAreEqual(c, c2);
|
||||
}
|
||||
|
||||
public boolean verifyInternal(byte[] sig, int siglen, byte[] msg, int msglen, byte[] rho, byte[] encT1)
|
||||
{
|
||||
if (siglen != CryptoBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// System.out.println("publickey = ");
|
||||
// Helper.printByteArray(publicKey);
|
||||
byte[] buf,
|
||||
mu = new byte[CrhBytes],
|
||||
c,
|
||||
c2 = new byte[DilithiumCTilde];
|
||||
Poly cp = new Poly(this);
|
||||
PolyVecMatrix aMatrix = new PolyVecMatrix(this);
|
||||
PolyVecL z = new PolyVecL(this);
|
||||
PolyVecK t1 = new PolyVecK(this), w1 = new PolyVecK(this), h = new PolyVecK(this);
|
||||
|
||||
t1 = Packing.unpackPublicKey(t1, encT1, this);
|
||||
|
||||
// System.out.println(t1.toString("t1"));
|
||||
|
||||
// System.out.println("rho = ");
|
||||
// Helper.printByteArray(rho);
|
||||
|
||||
if (!Packing.unpackSignature(z, h, sig, this))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
c = Arrays.copyOfRange(sig, 0, DilithiumCTilde);
|
||||
|
||||
// System.out.println(z.toString("z"));
|
||||
// System.out.println(h.toString("h"));
|
||||
|
||||
if (z.checkNorm(getDilithiumGamma1() - getDilithiumBeta()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute crh(crh(rho, t1), msg)
|
||||
// shake256Digest.update(rho, 0, rho.length);
|
||||
// shake256Digest.update(encT1, 0, encT1.length);
|
||||
// shake256Digest.doFinal(mu, 0, TrBytes);
|
||||
// System.out.println("mu before = ");
|
||||
// Helper.printByteArray(mu);
|
||||
|
||||
//shake256Digest.update(mu, 0, TrBytes);
|
||||
shake256Digest.update(msg, 0, msglen);
|
||||
shake256Digest.doFinal(mu, 0);
|
||||
|
||||
// System.out.println("mu after = ");
|
||||
// Helper.printByteArray(mu);
|
||||
|
||||
// Matrix-vector multiplication; compute Az - c2^dt1
|
||||
cp.challenge(Arrays.copyOfRange(c, 0, DilithiumCTilde)); // use only first DilithiumCTilde of c.
|
||||
// System.out.println("cp = ");
|
||||
// System.out.println(cp.toString());
|
||||
|
||||
aMatrix.expandMatrix(rho);
|
||||
// System.out.println(aMatrix.toString("aMatrix = "));
|
||||
|
||||
|
||||
z.polyVecNtt();
|
||||
aMatrix.pointwiseMontgomery(w1, z);
|
||||
|
||||
cp.polyNtt();
|
||||
// System.out.println("cp = ");
|
||||
// System.out.println(cp.toString());
|
||||
|
||||
t1.shiftLeft();
|
||||
t1.polyVecNtt();
|
||||
t1.pointwisePolyMontgomery(cp, t1);
|
||||
|
||||
// System.out.println(t1.toString("t1"));
|
||||
|
||||
w1.subtract(t1);
|
||||
w1.reduce();
|
||||
w1.invNttToMont();
|
||||
|
||||
// System.out.println(w1.toString("w1 before caddq"));
|
||||
|
||||
// Reconstruct w1
|
||||
w1.conditionalAddQ();
|
||||
// System.out.println(w1.toString("w1 before hint"));
|
||||
w1.useHint(w1, h);
|
||||
// System.out.println(w1.toString("w1"));
|
||||
|
||||
buf = w1.packW1();
|
||||
|
||||
// System.out.println("buf = ");
|
||||
// Helper.printByteArray(buf);
|
||||
|
||||
// System.out.println("mu = ");
|
||||
// Helper.printByteArray(mu);
|
||||
|
||||
SHAKEDigest shakeDigest256 = new SHAKEDigest(256);
|
||||
shakeDigest256.update(mu, 0, CrhBytes);
|
||||
shakeDigest256.update(buf, 0, DilithiumK * DilithiumPolyW1PackedBytes);
|
||||
shakeDigest256.doFinal(c2, 0, DilithiumCTilde);
|
||||
|
||||
// System.out.println("c = ");
|
||||
// Helper.printByteArray(c);
|
||||
|
||||
// System.out.println("c2 = ");
|
||||
// Helper.printByteArray(c2);
|
||||
|
||||
|
||||
return Util.constantTimeAreEqual(c, c2);
|
||||
}
|
||||
|
||||
public byte[][] generateKeyPair()
|
||||
{
|
||||
byte[] seedBuf = new byte[SeedBytes];
|
||||
random.nextBytes(seedBuf);
|
||||
return generateKeyPairInternal(seedBuf);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bouncycastle.crypto.KeyGenerationParameters;
|
||||
|
||||
public class MLDSAKeyGenerationParameters
|
||||
extends KeyGenerationParameters
|
||||
{
|
||||
private final MLDSAParameters params;
|
||||
|
||||
public MLDSAKeyGenerationParameters(
|
||||
SecureRandom random,
|
||||
MLDSAParameters mldsaParameters)
|
||||
{
|
||||
super(random, 256);
|
||||
this.params = mldsaParameters;
|
||||
}
|
||||
|
||||
public MLDSAParameters getParameters()
|
||||
{
|
||||
return params;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.KeyGenerationParameters;
|
||||
|
||||
public class MLDSAKeyPairGenerator
|
||||
{
|
||||
private MLDSAParameters parameters;
|
||||
private SecureRandom random;
|
||||
|
||||
public void init(KeyGenerationParameters param)
|
||||
{
|
||||
this.parameters = ((MLDSAKeyGenerationParameters)param).getParameters();
|
||||
this.random = param.getRandom();
|
||||
}
|
||||
|
||||
public AsymmetricCipherKeyPair generateKeyPair()
|
||||
{
|
||||
MLDSAEngine engine = parameters.getEngine(random);
|
||||
|
||||
byte[][] keyPair = engine.generateKeyPair();
|
||||
MLDSAPublicKeyParameters pubKey = new MLDSAPublicKeyParameters(parameters, keyPair[0], keyPair[6]);
|
||||
MLDSAPrivateKeyParameters privKey = new MLDSAPrivateKeyParameters(parameters, keyPair[0], keyPair[1], keyPair[2], keyPair[3], keyPair[4], keyPair[5], keyPair[6], keyPair[7]);
|
||||
|
||||
return new AsymmetricCipherKeyPair(pubKey, privKey);
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
public class MLDSAKeyParameters
|
||||
extends AsymmetricKeyParameter
|
||||
{
|
||||
private final MLDSAParameters params;
|
||||
|
||||
public MLDSAKeyParameters(
|
||||
boolean isPrivate,
|
||||
MLDSAParameters params)
|
||||
{
|
||||
super(isPrivate);
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public MLDSAParameters getParameters()
|
||||
{
|
||||
return params;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MLDSAParameters
|
||||
{
|
||||
public static final int TYPE_PURE = 0;
|
||||
public static final int TYPE_SHA2_512 = 1;
|
||||
|
||||
public static final MLDSAParameters ml_dsa_44 = new MLDSAParameters("ml-dsa-44", 2, TYPE_PURE);
|
||||
public static final MLDSAParameters ml_dsa_65 = new MLDSAParameters("ml-dsa-65", 3, TYPE_PURE);
|
||||
public static final MLDSAParameters ml_dsa_87 = new MLDSAParameters("ml-dsa-87", 5, TYPE_PURE);
|
||||
|
||||
public static final MLDSAParameters ml_dsa_44_with_sha512 = new MLDSAParameters("ml-dsa-44-with-sha512", 2, TYPE_SHA2_512);
|
||||
public static final MLDSAParameters ml_dsa_65_with_sha512 = new MLDSAParameters("ml-dsa-65-with-sha512", 3, TYPE_SHA2_512);
|
||||
public static final MLDSAParameters ml_dsa_87_with_sha512 = new MLDSAParameters("ml-dsa-87-with-sha512", 5, TYPE_SHA2_512);
|
||||
|
||||
private final int k;
|
||||
private final String name;
|
||||
private final int preHashDigest;
|
||||
|
||||
private MLDSAParameters(String name, int k, int preHashDigest)
|
||||
{
|
||||
this.name = name;
|
||||
this.k = k;
|
||||
this.preHashDigest = preHashDigest;
|
||||
}
|
||||
|
||||
public boolean isPreHash()
|
||||
{
|
||||
return preHashDigest != TYPE_PURE;
|
||||
}
|
||||
|
||||
public int getType()
|
||||
{
|
||||
return preHashDigest;
|
||||
}
|
||||
|
||||
MLDSAEngine getEngine(SecureRandom random)
|
||||
{
|
||||
return new MLDSAEngine(k, random);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
@@ -0,0 +1,162 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
import org.bouncycastle.util.Util;
|
||||
|
||||
public class MLDSAPrivateKeyParameters
|
||||
extends MLDSAKeyParameters
|
||||
{
|
||||
final byte[] rho;
|
||||
final byte[] k;
|
||||
final byte[] tr;
|
||||
final byte[] s1;
|
||||
final byte[] s2;
|
||||
final byte[] t0;
|
||||
|
||||
private final byte[] t1;
|
||||
private final byte[] seed;
|
||||
|
||||
public MLDSAPrivateKeyParameters(MLDSAParameters params, byte[] encoding)
|
||||
{
|
||||
this(params, encoding, null);
|
||||
}
|
||||
|
||||
public MLDSAPrivateKeyParameters(MLDSAParameters params, byte[] rho, byte[] K, byte[] tr, byte[] s1, byte[] s2, byte[] t0, byte[] t1)
|
||||
{
|
||||
this(params, rho, K, tr, s1, s2, t0, t1, null);
|
||||
}
|
||||
|
||||
public MLDSAPrivateKeyParameters(MLDSAParameters params, byte[] rho, byte[] K, byte[] tr, byte[] s1, byte[] s2, byte[] t0, byte[] t1, byte[] seed)
|
||||
{
|
||||
super(true, params);
|
||||
this.rho = Util.clone(rho);
|
||||
this.k = Util.clone(K);
|
||||
this.tr = Util.clone(tr);
|
||||
this.s1 = Util.clone(s1);
|
||||
this.s2 = Util.clone(s2);
|
||||
this.t0 = Util.clone(t0);
|
||||
this.t1 = Util.clone(t1);
|
||||
this.seed = Util.clone(seed);
|
||||
}
|
||||
|
||||
public MLDSAPrivateKeyParameters(MLDSAParameters params, byte[] encoding, MLDSAPublicKeyParameters pubKey)
|
||||
{
|
||||
super(true, params);
|
||||
|
||||
MLDSAEngine eng = params.getEngine(null);
|
||||
if (encoding.length == MLDSAEngine.SeedBytes)
|
||||
{
|
||||
byte[][] keyDetails = eng.generateKeyPairInternal(encoding);
|
||||
|
||||
this.rho = keyDetails[0];
|
||||
this.k = keyDetails[1];
|
||||
this.tr = keyDetails[2];
|
||||
this.s1 = keyDetails[3];
|
||||
this.s2 = keyDetails[4];
|
||||
this.t0 = keyDetails[5];
|
||||
this.t1 = keyDetails[6];
|
||||
this.seed = keyDetails[7];
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = 0;
|
||||
this.rho = Arrays.copyOfRange(encoding, 0, MLDSAEngine.SeedBytes);
|
||||
index += MLDSAEngine.SeedBytes;
|
||||
this.k = Arrays.copyOfRange(encoding, index, index + MLDSAEngine.SeedBytes);
|
||||
index += MLDSAEngine.SeedBytes;
|
||||
this.tr = Arrays.copyOfRange(encoding, index, index + MLDSAEngine.TrBytes);
|
||||
index += MLDSAEngine.TrBytes;
|
||||
int delta = eng.getDilithiumL() * eng.getDilithiumPolyEtaPackedBytes();
|
||||
this.s1 = Arrays.copyOfRange(encoding, index, index + delta);
|
||||
index += delta;
|
||||
delta = eng.getDilithiumK() * eng.getDilithiumPolyEtaPackedBytes();
|
||||
this.s2 = Arrays.copyOfRange(encoding, index, index + delta);
|
||||
index += delta;
|
||||
delta = eng.getDilithiumK() * MLDSAEngine.DilithiumPolyT0PackedBytes;
|
||||
this.t0 = Arrays.copyOfRange(encoding, index, index + delta);
|
||||
index += delta;
|
||||
this.t1 = eng.deriveT1(rho, k, tr, s1, s2, t0);
|
||||
|
||||
if (pubKey != null)
|
||||
{
|
||||
if (!Util.constantTimeAreEqual(this.t1, pubKey.getT1()))
|
||||
{
|
||||
throw new IllegalArgumentException("passed in public key does not match private values");
|
||||
}
|
||||
}
|
||||
|
||||
this.seed = null;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getEncoded()
|
||||
{
|
||||
return Util.concatenate(new byte[][]{rho, k, tr, s1, s2, t0});
|
||||
}
|
||||
|
||||
public byte[] getK()
|
||||
{
|
||||
return Util.clone(k);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getEncoded()} instead.
|
||||
*/
|
||||
public byte[] getPrivateKey()
|
||||
{
|
||||
return getEncoded();
|
||||
}
|
||||
|
||||
public byte[] getPublicKey()
|
||||
{
|
||||
return MLDSAPublicKeyParameters.getEncoded(rho, t1);
|
||||
}
|
||||
|
||||
public byte[] getSeed()
|
||||
{
|
||||
return Util.clone(seed);
|
||||
}
|
||||
|
||||
public MLDSAPublicKeyParameters getPublicKeyParameters()
|
||||
{
|
||||
if (this.t1 == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MLDSAPublicKeyParameters(getParameters(), rho, t1);
|
||||
}
|
||||
|
||||
public byte[] getRho()
|
||||
{
|
||||
return Util.clone(rho);
|
||||
}
|
||||
|
||||
public byte[] getS1()
|
||||
{
|
||||
return Util.clone(s1);
|
||||
}
|
||||
|
||||
public byte[] getS2()
|
||||
{
|
||||
return Util.clone(s2);
|
||||
}
|
||||
|
||||
public byte[] getT0()
|
||||
{
|
||||
return Util.clone(t0);
|
||||
}
|
||||
|
||||
public byte[] getT1()
|
||||
{
|
||||
return Util.clone(t1);
|
||||
}
|
||||
|
||||
public byte[] getTr()
|
||||
{
|
||||
return Util.clone(tr);
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.util.Util;
|
||||
|
||||
public class MLDSAPublicKeyParameters
|
||||
extends MLDSAKeyParameters
|
||||
{
|
||||
static byte[] getEncoded(byte[] rho, byte[] t1)
|
||||
{
|
||||
return Util.concatenate(rho, t1);
|
||||
}
|
||||
|
||||
final byte[] rho;
|
||||
final byte[] t1;
|
||||
|
||||
public MLDSAPublicKeyParameters(MLDSAParameters params, byte[] encoding)
|
||||
{
|
||||
super(false, params);
|
||||
this.rho = Arrays.copyOfRange(encoding, 0, MLDSAEngine.SeedBytes);
|
||||
this.t1 = Arrays.copyOfRange(encoding, MLDSAEngine.SeedBytes, encoding.length);
|
||||
if (t1.length == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("encoding too short");
|
||||
}
|
||||
}
|
||||
|
||||
public MLDSAPublicKeyParameters(MLDSAParameters params, byte[] rho, byte[] t1)
|
||||
{
|
||||
super(false, params);
|
||||
if (rho == null)
|
||||
{
|
||||
throw new NullPointerException("rho cannot be null");
|
||||
}
|
||||
if (t1 == null)
|
||||
{
|
||||
throw new NullPointerException("t1 cannot be null");
|
||||
}
|
||||
this.rho = Util.clone(rho);
|
||||
this.t1 = Util.clone(t1);
|
||||
}
|
||||
|
||||
public byte[] getEncoded()
|
||||
{
|
||||
return getEncoded(rho, t1);
|
||||
}
|
||||
|
||||
public byte[] getRho()
|
||||
{
|
||||
return Util.clone(rho);
|
||||
}
|
||||
|
||||
public byte[] getT1()
|
||||
{
|
||||
return Util.clone(t1);
|
||||
}
|
||||
}
|
145
core/java/src/org/bouncycastle/pqc/crypto/mldsa/MLDSASigner.java
Normal file
145
core/java/src/org/bouncycastle/pqc/crypto/mldsa/MLDSASigner.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.digests.SHAKEDigest;
|
||||
import org.bouncycastle.crypto.params.ParametersWithContext;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
|
||||
public class MLDSASigner
|
||||
{
|
||||
private static final byte[] EMPTY_CONTEXT = new byte[0];
|
||||
|
||||
private MLDSAPublicKeyParameters pubKey;
|
||||
private MLDSAPrivateKeyParameters privKey;
|
||||
private SecureRandom random;
|
||||
|
||||
private MLDSAEngine engine;
|
||||
private SHAKEDigest msgDigest;
|
||||
|
||||
public MLDSASigner()
|
||||
{
|
||||
}
|
||||
|
||||
public void init(boolean forSigning, CipherParameters param)
|
||||
{
|
||||
byte[] ctx = EMPTY_CONTEXT;
|
||||
if (param instanceof ParametersWithContext)
|
||||
{
|
||||
ParametersWithContext withContext = (ParametersWithContext)param;
|
||||
ctx = withContext.getContext();
|
||||
param = withContext.getParameters();
|
||||
|
||||
if (ctx.length > 255)
|
||||
{
|
||||
throw new IllegalArgumentException("context too long");
|
||||
}
|
||||
}
|
||||
|
||||
MLDSAParameters parameters;
|
||||
if (forSigning)
|
||||
{
|
||||
pubKey = null;
|
||||
|
||||
if (param instanceof ParametersWithRandom)
|
||||
{
|
||||
ParametersWithRandom withRandom = (ParametersWithRandom)param;
|
||||
privKey = (MLDSAPrivateKeyParameters)withRandom.getParameters();
|
||||
random = withRandom.getRandom();
|
||||
}
|
||||
else
|
||||
{
|
||||
privKey = (MLDSAPrivateKeyParameters)param;
|
||||
random = null;
|
||||
}
|
||||
|
||||
parameters = privKey.getParameters();
|
||||
engine = parameters.getEngine(random);
|
||||
|
||||
engine.initSign(privKey.tr, false, ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
pubKey = (MLDSAPublicKeyParameters)param;
|
||||
privKey = null;
|
||||
random = null;
|
||||
|
||||
parameters = pubKey.getParameters();
|
||||
engine = parameters.getEngine(null);
|
||||
|
||||
engine.initVerify(pubKey.rho, pubKey.t1, false, ctx);
|
||||
}
|
||||
|
||||
if (parameters.isPreHash())
|
||||
{
|
||||
throw new IllegalArgumentException("\"pure\" ml-dsa must use non pre-hash parameters");
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public void update(byte b)
|
||||
{
|
||||
msgDigest.update(b);
|
||||
}
|
||||
|
||||
public void update(byte[] in, int off, int len)
|
||||
{
|
||||
msgDigest.update(in, off, len);
|
||||
}
|
||||
|
||||
public byte[] generateSignature()
|
||||
throws CryptoException, DataLengthException
|
||||
{
|
||||
byte[] rnd = new byte[MLDSAEngine.RndBytes];
|
||||
if (random != null)
|
||||
{
|
||||
random.nextBytes(rnd);
|
||||
}
|
||||
|
||||
byte[] sig = engine.generateSignature(msgDigest, privKey.rho, privKey.k, privKey.t0, privKey.s1, privKey.s2, rnd);
|
||||
|
||||
reset();
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
public boolean verifySignature(byte[] signature)
|
||||
{
|
||||
boolean isTrue = engine.verifyInternal(signature, signature.length, msgDigest, pubKey.rho, pubKey.t1);
|
||||
|
||||
reset();
|
||||
|
||||
return isTrue;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
msgDigest = engine.getShake256Digest();
|
||||
}
|
||||
|
||||
protected byte[] internalGenerateSignature(byte[] message, byte[] random)
|
||||
{
|
||||
MLDSAEngine engine = privKey.getParameters().getEngine(this.random);
|
||||
|
||||
engine.initSign(privKey.tr, false, null);
|
||||
|
||||
return engine.signInternal(message, message.length, privKey.rho, privKey.k, privKey.t0, privKey.s1, privKey.s2, random);
|
||||
}
|
||||
|
||||
protected boolean internalVerifySignature(byte[] message, byte[] signature)
|
||||
{
|
||||
MLDSAEngine engine = pubKey.getParameters().getEngine(random);
|
||||
|
||||
engine.initVerify(pubKey.rho, pubKey.t1, false, null);
|
||||
|
||||
SHAKEDigest msgDigest = engine.getShake256Digest();
|
||||
|
||||
msgDigest.update(message, 0, message.length);
|
||||
|
||||
return engine.verifyInternal(signature, signature.length, msgDigest, pubKey.rho, pubKey.t1);
|
||||
}
|
||||
}
|
98
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Ntt.java
Normal file
98
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Ntt.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class Ntt
|
||||
{
|
||||
static final int[] nttZetas = {
|
||||
0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468,
|
||||
1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103,
|
||||
2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549,
|
||||
-2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005,
|
||||
2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439,
|
||||
-3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299,
|
||||
-1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596,
|
||||
811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779,
|
||||
-3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221,
|
||||
-1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922,
|
||||
3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047,
|
||||
-671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430,
|
||||
-3343383, 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618,
|
||||
-3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856,
|
||||
189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330,
|
||||
1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961,
|
||||
2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462,
|
||||
266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, -3193378,
|
||||
900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500,
|
||||
-655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838,
|
||||
342297, 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044,
|
||||
2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974,
|
||||
-3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970,
|
||||
-1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, -1962642,
|
||||
-1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031,
|
||||
-542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993,
|
||||
-2013608, 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385,
|
||||
-3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107,
|
||||
-3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078,
|
||||
-426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893,
|
||||
-2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687,
|
||||
-554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782
|
||||
};
|
||||
|
||||
static int[] ntt(int[] a)
|
||||
{
|
||||
int[] r = Arrays.copyOfRange(a, 0, a.length);
|
||||
|
||||
int len, start, j, k;
|
||||
int zeta, t;
|
||||
|
||||
k = 0;
|
||||
for (len = 128; len > 0; len >>>= 1)
|
||||
{
|
||||
for (start = 0; start < MLDSAEngine.DilithiumN; start = j + len)
|
||||
{
|
||||
zeta = nttZetas[++k];
|
||||
for (j = start; j < start + len; ++j)
|
||||
{
|
||||
t = Reduce.montgomeryReduce(((long)zeta * (long)r[j + len]));
|
||||
r[j + len] = r[j] - t;
|
||||
r[j] = r[j] + t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static int[] invNttToMont(int[] a)
|
||||
{
|
||||
int start, len, j, k;
|
||||
int t, zeta;
|
||||
final int f = 41978; // (mont^2)/256
|
||||
|
||||
int[] out = Arrays.copyOfRange(a, 0, a.length);
|
||||
|
||||
k = 256;
|
||||
for (len = 1; len < MLDSAEngine.DilithiumN; len <<= 1)
|
||||
{
|
||||
for (start = 0; start < MLDSAEngine.DilithiumN; start = j + len)
|
||||
{
|
||||
zeta = (-1) * nttZetas[--k];
|
||||
for (j = start; j < start + len; ++j)
|
||||
{
|
||||
t = out[j];
|
||||
out[j] = t + out[j + len];
|
||||
out[j + len] = t - out[j + len];
|
||||
out[j + len] = Reduce.montgomeryReduce((long)((long)zeta * (long)out[j + len]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < MLDSAEngine.DilithiumN; ++j)
|
||||
{
|
||||
out[j] = Reduce.montgomeryReduce((long)((long)f * (long)out[j]));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
165
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Packing.java
Normal file
165
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Packing.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class Packing
|
||||
{
|
||||
|
||||
static byte[] packPublicKey(PolyVecK t1, MLDSAEngine engine)
|
||||
{
|
||||
byte[] out = new byte[engine.getCryptoPublicKeyBytes() - MLDSAEngine.SeedBytes];
|
||||
|
||||
for (int i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
System.arraycopy(t1.getVectorIndex(i).polyt1Pack(), 0, out, i * MLDSAEngine.DilithiumPolyT1PackedBytes, MLDSAEngine.DilithiumPolyT1PackedBytes);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static PolyVecK unpackPublicKey(PolyVecK t1, byte[] publicKey, MLDSAEngine engine)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
t1.getVectorIndex(i).polyt1Unpack(Arrays.copyOfRange(publicKey, i * MLDSAEngine.DilithiumPolyT1PackedBytes, (i + 1) * MLDSAEngine.DilithiumPolyT1PackedBytes));
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
static byte[][] packSecretKey(byte[] rho, byte[] tr, byte[] key, PolyVecK t0, PolyVecL s1, PolyVecK s2, MLDSAEngine engine)
|
||||
{
|
||||
byte[][] out = new byte[6][];
|
||||
|
||||
out[0] = rho;
|
||||
out[1] = key;
|
||||
out[2] = tr;
|
||||
|
||||
out[3] = new byte[engine.getDilithiumL() * engine.getDilithiumPolyEtaPackedBytes()];
|
||||
for (int i = 0; i < engine.getDilithiumL(); ++i)
|
||||
{
|
||||
s1.getVectorIndex(i).polyEtaPack(out[3], i * engine.getDilithiumPolyEtaPackedBytes());
|
||||
}
|
||||
|
||||
out[4] = new byte[engine.getDilithiumK() * engine.getDilithiumPolyEtaPackedBytes()];
|
||||
for (int i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
s2.getVectorIndex(i).polyEtaPack(out[4], i * engine.getDilithiumPolyEtaPackedBytes());
|
||||
}
|
||||
|
||||
out[5] = new byte[engine.getDilithiumK() * MLDSAEngine.DilithiumPolyT0PackedBytes];
|
||||
for (int i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
t0.getVectorIndex(i).polyt0Pack(out[5], i * MLDSAEngine.DilithiumPolyT0PackedBytes);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param t0
|
||||
* @param s1
|
||||
* @param s2
|
||||
* @param engine
|
||||
* return Byte matrix where byte[0] = rho, byte[1] = tr, byte[2] = key
|
||||
*/
|
||||
|
||||
static void unpackSecretKey(PolyVecK t0, PolyVecL s1, PolyVecK s2, byte[] t0Enc, byte[] s1Enc, byte[] s2Enc, MLDSAEngine engine)
|
||||
{
|
||||
for (int i = 0; i < engine.getDilithiumL(); ++i)
|
||||
{
|
||||
s1.getVectorIndex(i).polyEtaUnpack(s1Enc, i * engine.getDilithiumPolyEtaPackedBytes());
|
||||
}
|
||||
|
||||
for (int i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
s2.getVectorIndex(i).polyEtaUnpack(s2Enc, i * engine.getDilithiumPolyEtaPackedBytes());
|
||||
}
|
||||
|
||||
for (int i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
t0.getVectorIndex(i).polyt0Unpack(t0Enc, i * MLDSAEngine.DilithiumPolyT0PackedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] packSignature(byte[] c, PolyVecL z, PolyVecK h, MLDSAEngine engine)
|
||||
{
|
||||
int i, j, k, end = 0;
|
||||
byte[] outBytes = new byte[engine.getCryptoBytes()];
|
||||
|
||||
System.arraycopy(c, 0, outBytes, 0, engine.getDilithiumCTilde());
|
||||
end += engine.getDilithiumCTilde();
|
||||
|
||||
for (i = 0; i < engine.getDilithiumL(); ++i)
|
||||
{
|
||||
System.arraycopy(z.getVectorIndex(i).zPack(), 0, outBytes, end + i * engine.getDilithiumPolyZPackedBytes(), engine.getDilithiumPolyZPackedBytes());
|
||||
}
|
||||
end += engine.getDilithiumL() * engine.getDilithiumPolyZPackedBytes();
|
||||
|
||||
for (i = 0; i < engine.getDilithiumOmega() + engine.getDilithiumK(); ++i)
|
||||
{
|
||||
outBytes[end + i] = 0;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for (i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
for (j = 0; j < MLDSAEngine.DilithiumN; ++j)
|
||||
{
|
||||
if (h.getVectorIndex(i).getCoeffIndex(j) != 0)
|
||||
{
|
||||
outBytes[end + k++] = (byte)j;
|
||||
}
|
||||
}
|
||||
outBytes[end + engine.getDilithiumOmega() + i] = (byte)k;
|
||||
}
|
||||
|
||||
return outBytes;
|
||||
|
||||
}
|
||||
|
||||
static boolean unpackSignature(PolyVecL z, PolyVecK h, byte[] sig, MLDSAEngine engine)
|
||||
{
|
||||
int i, j, k;
|
||||
|
||||
int end = engine.getDilithiumCTilde();
|
||||
for (i = 0; i < engine.getDilithiumL(); ++i)
|
||||
{
|
||||
z.getVectorIndex(i).zUnpack(Arrays.copyOfRange(sig, end + i * engine.getDilithiumPolyZPackedBytes(), end + (i + 1) * engine.getDilithiumPolyZPackedBytes()));
|
||||
}
|
||||
end += engine.getDilithiumL() * engine.getDilithiumPolyZPackedBytes();
|
||||
|
||||
k = 0;
|
||||
for (i = 0; i < engine.getDilithiumK(); ++i)
|
||||
{
|
||||
for (j = 0; j < MLDSAEngine.DilithiumN; ++j)
|
||||
{
|
||||
h.getVectorIndex(i).setCoeffIndex(j, 0);
|
||||
}
|
||||
|
||||
if ((sig[end + engine.getDilithiumOmega() + i] & 0xFF) < k || (sig[end + engine.getDilithiumOmega() + i] & 0xFF) > engine.getDilithiumOmega())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (j = k; j < (sig[end + engine.getDilithiumOmega() + i] & 0xFF); ++j)
|
||||
{
|
||||
if (j > k && (sig[end + j] & 0xFF) <= (sig[end + j - 1] & 0xFF))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
h.getVectorIndex(i).setCoeffIndex((sig[end + j] & 0xFF), 1);
|
||||
}
|
||||
|
||||
k = (int)(sig[end + engine.getDilithiumOmega() + i]);
|
||||
}
|
||||
for (j = k; j < engine.getDilithiumOmega(); ++j)
|
||||
{
|
||||
if ((sig[end + j] & 0xFF) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
802
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Poly.java
Normal file
802
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Poly.java
Normal file
@@ -0,0 +1,802 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SHAKEDigest;
|
||||
|
||||
class Poly
|
||||
{
|
||||
private final int polyUniformNBlocks;
|
||||
private int[] coeffs;
|
||||
private final MLDSAEngine engine;
|
||||
private final int dilithiumN;
|
||||
|
||||
private final Symmetric symmetric;
|
||||
|
||||
|
||||
public Poly(MLDSAEngine engine)
|
||||
{
|
||||
this.dilithiumN = MLDSAEngine.DilithiumN;
|
||||
this.coeffs = new int[dilithiumN];
|
||||
this.engine = engine;
|
||||
this.symmetric = engine.GetSymmetric();
|
||||
this.polyUniformNBlocks = (768 + symmetric.stream128BlockBytes - 1) / symmetric.stream128BlockBytes;
|
||||
}
|
||||
|
||||
public int getCoeffIndex(int i)
|
||||
{
|
||||
return this.coeffs[i];
|
||||
}
|
||||
|
||||
public int[] getCoeffs()
|
||||
{
|
||||
return this.coeffs;
|
||||
}
|
||||
|
||||
public void setCoeffIndex(int i, int val)
|
||||
{
|
||||
this.coeffs[i] = val;
|
||||
}
|
||||
|
||||
public void setCoeffs(int[] coeffs)
|
||||
{
|
||||
this.coeffs = coeffs;
|
||||
}
|
||||
|
||||
public void uniformBlocks(byte[] seed, short nonce)
|
||||
{
|
||||
int i, ctr, off,
|
||||
buflen = polyUniformNBlocks * symmetric.stream128BlockBytes;
|
||||
byte[] buf = new byte[buflen + 2];
|
||||
|
||||
symmetric.stream128init(seed, nonce);
|
||||
|
||||
symmetric.stream128squeezeBlocks(buf, 0, buflen);
|
||||
|
||||
ctr = rejectUniform(this, 0, dilithiumN, buf, buflen);
|
||||
|
||||
// ctr can be less than N
|
||||
|
||||
while (ctr < dilithiumN)
|
||||
{
|
||||
off = buflen % 3;
|
||||
for (i = 0; i < off; ++i)
|
||||
{
|
||||
buf[i] = buf[buflen - off + i];
|
||||
}
|
||||
symmetric.stream128squeezeBlocks(buf, off, symmetric.stream128BlockBytes);
|
||||
buflen = symmetric.stream128BlockBytes + off;
|
||||
ctr += rejectUniform(this, ctr, dilithiumN - ctr, buf, buflen);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int rejectUniform(Poly outputPoly, int coeffOff, int len, byte[] inpBuf, int buflen)
|
||||
{
|
||||
int ctr, pos;
|
||||
int t;
|
||||
|
||||
ctr = pos = 0;
|
||||
while (ctr < len && pos + 3 <= buflen)
|
||||
{
|
||||
t = (inpBuf[pos++] & 0xFF);
|
||||
t |= (inpBuf[pos++] & 0xFF) << 8;
|
||||
t |= (inpBuf[pos++] & 0xFF) << 16;
|
||||
t &= 0x7FFFFF;
|
||||
|
||||
if (t < MLDSAEngine.DilithiumQ)
|
||||
{
|
||||
outputPoly.setCoeffIndex(coeffOff + ctr, t);
|
||||
ctr++;
|
||||
}
|
||||
}
|
||||
|
||||
return ctr;
|
||||
|
||||
}
|
||||
|
||||
public void uniformEta(byte[] seed, short nonce)
|
||||
{
|
||||
int ctr, polyUniformEtaNBlocks, eta = engine.getDilithiumEta();
|
||||
|
||||
if (engine.getDilithiumEta() == 2)
|
||||
{
|
||||
polyUniformEtaNBlocks = ((136 + symmetric.stream256BlockBytes - 1) / symmetric.stream256BlockBytes); // TODO: change with class
|
||||
}
|
||||
else if (engine.getDilithiumEta() == 4)
|
||||
{
|
||||
polyUniformEtaNBlocks = ((227 + symmetric.stream256BlockBytes - 1) / symmetric.stream256BlockBytes); // TODO: change with class
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Dilithium Eta!");
|
||||
}
|
||||
|
||||
int buflen = polyUniformEtaNBlocks * symmetric.stream256BlockBytes;
|
||||
|
||||
byte[] buf = new byte[buflen];
|
||||
|
||||
symmetric.stream256init(seed, nonce);
|
||||
symmetric.stream256squeezeBlocks(buf, 0, buflen);
|
||||
|
||||
ctr = rejectEta(this, 0, dilithiumN, buf, buflen, eta);
|
||||
|
||||
while (ctr < MLDSAEngine.DilithiumN)
|
||||
{
|
||||
symmetric.stream256squeezeBlocks(buf, 0, symmetric.stream256BlockBytes);
|
||||
ctr += rejectEta(this, ctr, dilithiumN - ctr, buf, symmetric.stream256BlockBytes, eta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int rejectEta(Poly outputPoly, int coeffOff, int len, byte[] buf, int buflen, int eta)
|
||||
{
|
||||
int ctr, pos;
|
||||
int t0, t1;
|
||||
|
||||
ctr = pos = 0;
|
||||
|
||||
while (ctr < len && pos < buflen)
|
||||
{
|
||||
t0 = (buf[pos] & 0xFF) & 0x0F;
|
||||
t1 = (buf[pos++] & 0xFF) >> 4;
|
||||
if (eta == 2)
|
||||
{
|
||||
if (t0 < 15)
|
||||
{
|
||||
t0 = t0 - (205 * t0 >> 10) * 5;
|
||||
outputPoly.setCoeffIndex(coeffOff + ctr, 2 - t0);
|
||||
ctr++;
|
||||
}
|
||||
if (t1 < 15 && ctr < len)
|
||||
{
|
||||
t1 = t1 - (205 * t1 >> 10) * 5;
|
||||
outputPoly.setCoeffIndex(coeffOff + ctr, 2 - t1);
|
||||
ctr++;
|
||||
}
|
||||
}
|
||||
else if (eta == 4)
|
||||
{
|
||||
if (t0 < 9)
|
||||
{
|
||||
outputPoly.setCoeffIndex(coeffOff + ctr, 4 - t0);
|
||||
ctr++;
|
||||
}
|
||||
if (t1 < 9 && ctr < len)
|
||||
{
|
||||
outputPoly.setCoeffIndex(coeffOff + ctr, 4 - t1);
|
||||
ctr++;
|
||||
}
|
||||
// System.out.printf("ctr %d coeff %d\n", ctr, outputPoly.getCoeffIndex(ctr - 1));
|
||||
}
|
||||
}
|
||||
return ctr;
|
||||
}
|
||||
|
||||
public void polyNtt()
|
||||
{
|
||||
this.setCoeffs(Ntt.ntt(this.coeffs));
|
||||
}
|
||||
|
||||
public void pointwiseMontgomery(Poly v, Poly w)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, Reduce.montgomeryReduce((long)((long)v.getCoeffIndex(i) * (long)w.getCoeffIndex(i))));
|
||||
}
|
||||
}
|
||||
|
||||
public void pointwiseAccountMontgomery(PolyVecL u, PolyVecL v)
|
||||
{
|
||||
int i;
|
||||
Poly t = new Poly(engine);
|
||||
|
||||
this.pointwiseMontgomery(u.getVectorIndex(0), v.getVectorIndex(0));
|
||||
|
||||
for (i = 1; i < engine.getDilithiumL(); ++i)
|
||||
{
|
||||
t.pointwiseMontgomery(u.getVectorIndex(i), v.getVectorIndex(i));
|
||||
this.addPoly(t);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void addPoly(Poly a)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumN; i++)
|
||||
{
|
||||
this.setCoeffIndex(i, this.getCoeffIndex(i) + a.getCoeffIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void reduce()
|
||||
{
|
||||
for (int i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, Reduce.reduce32(this.getCoeffIndex(i)));
|
||||
}
|
||||
}
|
||||
|
||||
public void invNttToMont()
|
||||
{
|
||||
this.setCoeffs(Ntt.invNttToMont(this.getCoeffs()));
|
||||
}
|
||||
|
||||
public void conditionalAddQ()
|
||||
{
|
||||
for (int i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, Reduce.conditionalAddQ(this.getCoeffIndex(i)));
|
||||
}
|
||||
}
|
||||
|
||||
public void power2Round(Poly a)
|
||||
{
|
||||
for (int i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
int[] p2r = Rounding.power2Round(this.getCoeffIndex(i));
|
||||
this.setCoeffIndex(i, p2r[0]);
|
||||
a.setCoeffIndex(i, p2r[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] polyt1Pack()
|
||||
{
|
||||
byte[] out = new byte[MLDSAEngine.DilithiumPolyT1PackedBytes];
|
||||
|
||||
for (int i = 0; i < dilithiumN / 4; ++i)
|
||||
{
|
||||
out[5 * i + 0] = (byte)(this.coeffs[4 * i + 0] >> 0);
|
||||
out[5 * i + 1] = (byte)((this.coeffs[4 * i + 0] >> 8) | (this.coeffs[4 * i + 1] << 2));
|
||||
out[5 * i + 2] = (byte)((this.coeffs[4 * i + 1] >> 6) | (this.coeffs[4 * i + 2] << 4));
|
||||
out[5 * i + 3] = (byte)((this.coeffs[4 * i + 2] >> 4) | (this.coeffs[4 * i + 3] << 6));
|
||||
out[5 * i + 4] = (byte)(this.coeffs[4 * i + 3] >> 2);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void polyt1Unpack(byte[] a)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dilithiumN / 4; ++i)
|
||||
{
|
||||
this.setCoeffIndex(4 * i + 0, (((a[5 * i + 0] & 0xFF) >> 0) | ((int)(a[5 * i + 1] & 0xFF) << 8)) & 0x3FF);
|
||||
this.setCoeffIndex(4 * i + 1, (((a[5 * i + 1] & 0xFF) >> 2) | ((int)(a[5 * i + 2] & 0xFF) << 6)) & 0x3FF);
|
||||
this.setCoeffIndex(4 * i + 2, (((a[5 * i + 2] & 0xFF) >> 4) | ((int)(a[5 * i + 3] & 0xFF) << 4)) & 0x3FF);
|
||||
this.setCoeffIndex(4 * i + 3, (((a[5 * i + 3] & 0xFF) >> 6) | ((int)(a[5 * i + 4] & 0xFF) << 2)) & 0x3FF);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] polyEtaPack(byte[] out, int outOff)
|
||||
{
|
||||
int i;
|
||||
byte[] t = new byte[8];
|
||||
|
||||
if (engine.getDilithiumEta() == 2)
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 8; ++i)
|
||||
{
|
||||
t[0] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 0));
|
||||
t[1] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 1));
|
||||
t[2] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 2));
|
||||
t[3] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 3));
|
||||
t[4] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 4));
|
||||
t[5] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 5));
|
||||
t[6] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 6));
|
||||
t[7] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(8 * i + 7));
|
||||
|
||||
out[outOff + 3 * i + 0] = (byte)((t[0] >> 0) | (t[1] << 3) | (t[2] << 6));
|
||||
out[outOff + 3 * i + 1] = (byte)((t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7));
|
||||
out[outOff + 3 * i + 2] = (byte)((t[5] >> 1) | (t[6] << 2) | (t[7] << 5));
|
||||
}
|
||||
}
|
||||
else if (engine.getDilithiumEta() == 4)
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 2; ++i)
|
||||
{
|
||||
t[0] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(2 * i + 0));
|
||||
t[1] = (byte)(engine.getDilithiumEta() - this.getCoeffIndex(2 * i + 1));
|
||||
out[outOff + i] = (byte)(t[0] | t[1] << 4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Eta needs to be 2 or 4!");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void polyEtaUnpack(byte[] a, int aOff)
|
||||
{
|
||||
int i, eta = engine.getDilithiumEta();
|
||||
|
||||
if (engine.getDilithiumEta() == 2)
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 8; ++i)
|
||||
{
|
||||
int base = aOff + 3 * i;
|
||||
this.setCoeffIndex(8 * i + 0, (((a[base + 0] & 0xFF) >> 0)) & 7);
|
||||
this.setCoeffIndex(8 * i + 1, (((a[base + 0] & 0xFF) >> 3)) & 7);
|
||||
this.setCoeffIndex(8 * i + 2, ((a[base + 0] & 0xFF) >> 6) | ((a[base + 1] & 0xFF) << 2) & 7);
|
||||
this.setCoeffIndex(8 * i + 3, (((a[base + 1] & 0xFF) >> 1)) & 7);
|
||||
this.setCoeffIndex(8 * i + 4, (((a[base + 1] & 0xFF) >> 4)) & 7);
|
||||
this.setCoeffIndex(8 * i + 5, ((a[base + 1] & 0xFF) >> 7) | ((a[base + 2] & 0xFF) << 1) & 7);
|
||||
this.setCoeffIndex(8 * i + 6, (((a[base + 2] & 0xFF) >> 2)) & 7);
|
||||
this.setCoeffIndex(8 * i + 7, (((a[base + 2] & 0xFF) >> 5)) & 7);
|
||||
|
||||
this.setCoeffIndex(8 * i + 0, eta - this.getCoeffIndex(8 * i + 0));
|
||||
this.setCoeffIndex(8 * i + 1, eta - this.getCoeffIndex(8 * i + 1));
|
||||
this.setCoeffIndex(8 * i + 2, eta - this.getCoeffIndex(8 * i + 2));
|
||||
this.setCoeffIndex(8 * i + 3, eta - this.getCoeffIndex(8 * i + 3));
|
||||
this.setCoeffIndex(8 * i + 4, eta - this.getCoeffIndex(8 * i + 4));
|
||||
this.setCoeffIndex(8 * i + 5, eta - this.getCoeffIndex(8 * i + 5));
|
||||
this.setCoeffIndex(8 * i + 6, eta - this.getCoeffIndex(8 * i + 6));
|
||||
this.setCoeffIndex(8 * i + 7, eta - this.getCoeffIndex(8 * i + 7));
|
||||
}
|
||||
}
|
||||
else if (engine.getDilithiumEta() == 4)
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 2; ++i)
|
||||
{
|
||||
this.setCoeffIndex(2 * i + 0, a[aOff + i] & 0x0F);
|
||||
this.setCoeffIndex(2 * i + 1, (a[aOff + i] & 0xFF) >> 4);
|
||||
this.setCoeffIndex(2 * i + 0, eta - this.getCoeffIndex(2 * i + 0));
|
||||
this.setCoeffIndex(2 * i + 1, eta - this.getCoeffIndex(2 * i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] polyt0Pack(byte[] out, int outOff)
|
||||
{
|
||||
int i;
|
||||
int[] t = new int[8];
|
||||
|
||||
for (i = 0; i < dilithiumN / 8; ++i)
|
||||
{
|
||||
t[0] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 0);
|
||||
t[1] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 1);
|
||||
t[2] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 2);
|
||||
t[3] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 3);
|
||||
t[4] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 4);
|
||||
t[5] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 5);
|
||||
t[6] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 6);
|
||||
t[7] = (1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 7);
|
||||
|
||||
int base = outOff + 13 * i;
|
||||
out[base + 0] = (byte)(t[0]);
|
||||
out[base + 1] = (byte)(t[0] >> 8);
|
||||
out[base + 1] = (byte)(out[base + 1] | (byte)(t[1] << 5));
|
||||
out[base + 2] = (byte)(t[1] >> 3);
|
||||
out[base + 3] = (byte)(t[1] >> 11);
|
||||
out[base + 3] = (byte)(out[base + 3] | (byte)(t[2] << 2));
|
||||
out[base + 4] = (byte)(t[2] >> 6);
|
||||
out[base + 4] = (byte)(out[base + 4] | (byte)(t[3] << 7));
|
||||
out[base + 5] = (byte)(t[3] >> 1);
|
||||
out[base + 6] = (byte)(t[3] >> 9);
|
||||
out[base + 6] = (byte)(out[base + 6] | (byte)(t[4] << 4));
|
||||
out[base + 7] = (byte)(t[4] >> 4);
|
||||
out[base + 8] = (byte)(t[4] >> 12);
|
||||
out[base + 8] = (byte)(out[base + 8] | (byte)(t[5] << 1));
|
||||
out[base + 9] = (byte)(t[5] >> 7);
|
||||
out[base + 9] = (byte)(out[base + 9] | (byte)(t[6] << 6));
|
||||
out[base + 10] = (byte)(t[6] >> 2);
|
||||
out[base + 11] = (byte)(t[6] >> 10);
|
||||
out[base + 11] = (byte)(out[base + 11] | (byte)(t[7] << 3));
|
||||
out[base + 12] = (byte)(t[7] >> 5);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void polyt0Unpack(byte[] a, int aOff)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumN / 8; ++i)
|
||||
{
|
||||
int base = aOff + 13 * i;
|
||||
this.setCoeffIndex(8 * i + 0,
|
||||
(
|
||||
(a[base + 0] & 0xFF) |
|
||||
((a[base + 1] & 0xFF) << 8)
|
||||
) & 0x1FFF);
|
||||
this.setCoeffIndex(8 * i + 1,
|
||||
(
|
||||
(((a[base + 1] & 0xFF) >> 5) |
|
||||
((a[base + 2] & 0xFF) << 3)) |
|
||||
((a[base + 3] & 0xFF) << 11)
|
||||
) & 0x1FFF);
|
||||
|
||||
this.setCoeffIndex(8 * i + 2,
|
||||
(
|
||||
(((a[base + 3] & 0xFF) >> 2) |
|
||||
((a[base + 4] & 0xFF) << 6))
|
||||
) & 0x1FFF);
|
||||
|
||||
this.setCoeffIndex(8 * i + 3,
|
||||
(
|
||||
(((a[base + 4] & 0xFF) >> 7) |
|
||||
((a[base + 5] & 0xFF) << 1)) |
|
||||
((a[base + 6] & 0xFF) << 9)
|
||||
) & 0x1FFF);
|
||||
|
||||
this.setCoeffIndex(8 * i + 4,
|
||||
(
|
||||
(((a[base + 6] & 0xFF) >> 4) |
|
||||
((a[base + 7] & 0xFF) << 4)) |
|
||||
((a[base + 8] & 0xFF) << 12)
|
||||
) & 0x1FFF);
|
||||
|
||||
this.setCoeffIndex(8 * i + 5,
|
||||
(
|
||||
(((a[base + 8] & 0xFF) >> 1) |
|
||||
((a[base + 9] & 0xFF) << 7))
|
||||
) & 0x1FFF);
|
||||
|
||||
this.setCoeffIndex(8 * i + 6,
|
||||
(
|
||||
(((a[base + 9] & 0xFF) >> 6) |
|
||||
((a[base + 10] & 0xFF) << 2)) |
|
||||
((a[base + 11] & 0xFF) << 10)
|
||||
) & 0x1FFF);
|
||||
|
||||
this.setCoeffIndex(8 * i + 7,
|
||||
(
|
||||
((a[base + 11] & 0xFF) >> 3 |
|
||||
((a[base + 12] & 0xFF) << 5))
|
||||
) & 0x1FFF);
|
||||
|
||||
|
||||
this.setCoeffIndex(8 * i + 0, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 0)));
|
||||
this.setCoeffIndex(8 * i + 1, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 1)));
|
||||
this.setCoeffIndex(8 * i + 2, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 2)));
|
||||
this.setCoeffIndex(8 * i + 3, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 3)));
|
||||
this.setCoeffIndex(8 * i + 4, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 4)));
|
||||
this.setCoeffIndex(8 * i + 5, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 5)));
|
||||
this.setCoeffIndex(8 * i + 6, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 6)));
|
||||
this.setCoeffIndex(8 * i + 7, ((1 << (MLDSAEngine.DilithiumD - 1)) - this.getCoeffIndex(8 * i + 7)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void uniformGamma1(byte[] seed, short nonce)
|
||||
{
|
||||
byte[] buf = new byte[engine.getPolyUniformGamma1NBlocks() * symmetric.stream256BlockBytes];
|
||||
|
||||
symmetric.stream256init(seed, nonce);
|
||||
symmetric.stream256squeezeBlocks(buf, 0, engine.getPolyUniformGamma1NBlocks() * symmetric.stream256BlockBytes);// todo this is final
|
||||
|
||||
this.unpackZ(buf);
|
||||
}
|
||||
|
||||
private void unpackZ(byte[] a)
|
||||
{
|
||||
int i;
|
||||
if (engine.getDilithiumGamma1() == (1 << 17))
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 4; ++i)
|
||||
{
|
||||
this.setCoeffIndex(4 * i + 0,
|
||||
(
|
||||
(((a[9 * i + 0] & 0xFF)) |
|
||||
((a[9 * i + 1] & 0xFF) << 8)) |
|
||||
((a[9 * i + 2] & 0xFF) << 16)
|
||||
) & 0x3FFFF);
|
||||
this.setCoeffIndex(4 * i + 1,
|
||||
(
|
||||
(((a[9 * i + 2] & 0xFF) >> 2) |
|
||||
((a[9 * i + 3] & 0xFF) << 6)) |
|
||||
((a[9 * i + 4] & 0xFF) << 14)
|
||||
) & 0x3FFFF);
|
||||
this.setCoeffIndex(4 * i + 2,
|
||||
(
|
||||
(((a[9 * i + 4] & 0xFF) >> 4) |
|
||||
((a[9 * i + 5] & 0xFF) << 4)) |
|
||||
((a[9 * i + 6] & 0xFF) << 12)
|
||||
) & 0x3FFFF);
|
||||
this.setCoeffIndex(4 * i + 3,
|
||||
(
|
||||
(((a[9 * i + 6] & 0xFF) >> 6) |
|
||||
((a[9 * i + 7] & 0xFF) << 2)) |
|
||||
((a[9 * i + 8] & 0xFF) << 10)
|
||||
) & 0x3FFFF);
|
||||
|
||||
|
||||
this.setCoeffIndex(4 * i + 0, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 0));
|
||||
this.setCoeffIndex(4 * i + 1, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 1));
|
||||
this.setCoeffIndex(4 * i + 2, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 2));
|
||||
this.setCoeffIndex(4 * i + 3, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 3));
|
||||
}
|
||||
}
|
||||
else if (engine.getDilithiumGamma1() == (1 << 19))
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 2; ++i)
|
||||
{
|
||||
this.setCoeffIndex(2 * i + 0,
|
||||
(
|
||||
(((a[5 * i + 0] & 0xFF)) |
|
||||
((a[5 * i + 1] & 0xFF) << 8)) |
|
||||
((a[5 * i + 2] & 0xFF) << 16)
|
||||
) & 0xFFFFF);
|
||||
this.setCoeffIndex(2 * i + 1,
|
||||
(
|
||||
(((a[5 * i + 2] & 0xFF) >> 4) |
|
||||
((a[5 * i + 3] & 0xFF) << 4)) |
|
||||
((a[5 * i + 4] & 0xFF) << 12)
|
||||
) & 0xFFFFF);
|
||||
|
||||
this.setCoeffIndex(2 * i + 0, engine.getDilithiumGamma1() - this.getCoeffIndex(2 * i + 0));
|
||||
this.setCoeffIndex(2 * i + 1, engine.getDilithiumGamma1() - this.getCoeffIndex(2 * i + 1));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Dilithiumn Gamma1!");
|
||||
}
|
||||
}
|
||||
|
||||
public void decompose(Poly a)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
int[] decomp = Rounding.decompose(this.getCoeffIndex(i), engine.getDilithiumGamma2());
|
||||
this.setCoeffIndex(i, decomp[1]);
|
||||
a.setCoeffIndex(i, decomp[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] w1Pack()
|
||||
{
|
||||
int i;
|
||||
|
||||
byte[] out = new byte[engine.getDilithiumPolyW1PackedBytes()];
|
||||
|
||||
if (engine.getDilithiumGamma2() == (MLDSAEngine.DilithiumQ - 1) / 88)
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 4; ++i)
|
||||
{
|
||||
out[3 * i + 0] = (byte)(((byte)this.getCoeffIndex(4 * i + 0)) | (this.getCoeffIndex(4 * i + 1) << 6));
|
||||
out[3 * i + 1] = (byte)((byte)(this.getCoeffIndex(4 * i + 1) >> 2) | (this.getCoeffIndex(4 * i + 2) << 4));
|
||||
out[3 * i + 2] = (byte)((byte)(this.getCoeffIndex(4 * i + 2) >> 4) | (this.getCoeffIndex(4 * i + 3) << 2));
|
||||
}
|
||||
}
|
||||
else if (engine.getDilithiumGamma2() == (MLDSAEngine.DilithiumQ - 1) / 32)
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 2; ++i)
|
||||
{
|
||||
out[i] = (byte)(this.getCoeffIndex(2 * i + 0) | (this.getCoeffIndex(2 * i + 1) << 4));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public void challenge(byte[] seed)
|
||||
{
|
||||
int i, b = 0, pos;
|
||||
long signs;
|
||||
byte[] buf = new byte[symmetric.stream256BlockBytes];
|
||||
|
||||
SHAKEDigest shake256Digest = new SHAKEDigest(256);
|
||||
shake256Digest.update(seed, 0, engine.getDilithiumCTilde());
|
||||
shake256Digest.doOutput(buf, 0, symmetric.stream256BlockBytes);
|
||||
|
||||
signs = (long)0;
|
||||
for (i = 0; i < 8; ++i)
|
||||
{
|
||||
signs |= (long)(buf[i] & 0xFF) << 8 * i;
|
||||
}
|
||||
|
||||
pos = 8;
|
||||
|
||||
for (i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, 0);
|
||||
}
|
||||
for (i = dilithiumN - engine.getDilithiumTau(); i < dilithiumN; ++i)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (pos >= symmetric.stream256BlockBytes)
|
||||
{
|
||||
shake256Digest.doOutput(buf, 0, symmetric.stream256BlockBytes);
|
||||
pos = 0;
|
||||
}
|
||||
b = (buf[pos++] & 0xFF);
|
||||
}
|
||||
while (b > i);
|
||||
|
||||
this.setCoeffIndex(i, this.getCoeffIndex(b));
|
||||
this.setCoeffIndex(b, (int)(1 - 2 * (signs & 1)));
|
||||
signs = (long)(signs >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkNorm(int B)
|
||||
{
|
||||
int i, t;
|
||||
|
||||
if (B > (MLDSAEngine.DilithiumQ - 1) / 8)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
t = this.getCoeffIndex(i) >> 31;
|
||||
t = this.getCoeffIndex(i) - (t & 2 * this.getCoeffIndex(i));
|
||||
|
||||
if (t >= B)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void subtract(Poly inpPoly)
|
||||
{
|
||||
for (int i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, this.getCoeffIndex(i) - inpPoly.getCoeffIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public int polyMakeHint(Poly a0, Poly a1)
|
||||
{
|
||||
int i, s = 0;
|
||||
|
||||
for (i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, Rounding.makeHint(a0.getCoeffIndex(i), a1.getCoeffIndex(i), engine));
|
||||
s += this.getCoeffIndex(i);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public void polyUseHint(Poly a, Poly h)
|
||||
{
|
||||
for (int i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, Rounding.useHint(a.getCoeffIndex(i), h.getCoeffIndex(i), engine.getDilithiumGamma2()));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] zPack()
|
||||
{
|
||||
byte[] outBytes = new byte[engine.getDilithiumPolyZPackedBytes()];
|
||||
int i;
|
||||
int[] t = new int[4];
|
||||
if (engine.getDilithiumGamma1() == (1 << 17))
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 4; ++i)
|
||||
{
|
||||
t[0] = engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 0);
|
||||
t[1] = engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 1);
|
||||
t[2] = engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 2);
|
||||
t[3] = engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 3);
|
||||
|
||||
outBytes[9 * i + 0] = (byte)t[0];
|
||||
outBytes[9 * i + 1] = (byte)(t[0] >> 8);
|
||||
outBytes[9 * i + 2] = (byte)((byte)(t[0] >> 16) | (t[1] << 2));
|
||||
outBytes[9 * i + 3] = (byte)(t[1] >> 6);
|
||||
outBytes[9 * i + 4] = (byte)((byte)(t[1] >> 14) | (t[2] << 4));
|
||||
outBytes[9 * i + 5] = (byte)(t[2] >> 4);
|
||||
outBytes[9 * i + 6] = (byte)((byte)(t[2] >> 12) | (t[3] << 6));
|
||||
outBytes[9 * i + 7] = (byte)(t[3] >> 2);
|
||||
outBytes[9 * i + 8] = (byte)(t[3] >> 10);
|
||||
}
|
||||
}
|
||||
else if (engine.getDilithiumGamma1() == (1 << 19))
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 2; ++i)
|
||||
{
|
||||
t[0] = engine.getDilithiumGamma1() - this.getCoeffIndex(2 * i + 0);
|
||||
t[1] = engine.getDilithiumGamma1() - this.getCoeffIndex(2 * i + 1);
|
||||
|
||||
outBytes[5 * i + 0] = (byte)t[0];
|
||||
outBytes[5 * i + 1] = (byte)(t[0] >> 8);
|
||||
outBytes[5 * i + 2] = (byte)((byte)(t[0] >> 16) | (t[1] << 4));
|
||||
outBytes[5 * i + 3] = (byte)(t[1] >> 4);
|
||||
outBytes[5 * i + 4] = (byte)(t[1] >> 12);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Dilithium Gamma1!");
|
||||
}
|
||||
return outBytes;
|
||||
}
|
||||
|
||||
void zUnpack(byte[] a)
|
||||
{
|
||||
int i;
|
||||
if (engine.getDilithiumGamma1() == (1 << 17))
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 4; ++i)
|
||||
{
|
||||
this.setCoeffIndex(4 * i + 0,
|
||||
(((int)(a[9 * i + 0] & 0xFF)
|
||||
| (int)((a[9 * i + 1] & 0xFF) << 8))
|
||||
| (int)((a[9 * i + 2] & 0xFF) << 16))
|
||||
& 0x3FFFF);
|
||||
|
||||
this.setCoeffIndex(4 * i + 1,
|
||||
(((int)((a[9 * i + 2] & 0xFF) >>> 2)
|
||||
| (int)((a[9 * i + 3] & 0xFF) << 6))
|
||||
| (int)((a[9 * i + 4] & 0xFF) << 14))
|
||||
& 0x3FFFF);
|
||||
|
||||
this.setCoeffIndex(4 * i + 2,
|
||||
(((int)((a[9 * i + 4] & 0xFF) >>> 4)
|
||||
| (int)((a[9 * i + 5] & 0xFF) << 4))
|
||||
| (int)((a[9 * i + 6] & 0xFF) << 12))
|
||||
& 0x3FFFF);
|
||||
|
||||
this.setCoeffIndex(4 * i + 3,
|
||||
(((int)((a[9 * i + 6] & 0xFF) >>> 6)
|
||||
| (int)((a[9 * i + 7] & 0xFF) << 2))
|
||||
| (int)((a[9 * i + 8] & 0xFF) << 10))
|
||||
& 0x3FFFF);
|
||||
|
||||
this.setCoeffIndex(4 * i + 0, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 0));
|
||||
this.setCoeffIndex(4 * i + 1, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 1));
|
||||
this.setCoeffIndex(4 * i + 2, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 2));
|
||||
this.setCoeffIndex(4 * i + 3, engine.getDilithiumGamma1() - this.getCoeffIndex(4 * i + 3));
|
||||
}
|
||||
}
|
||||
else if (engine.getDilithiumGamma1() == (1 << 19))
|
||||
{
|
||||
for (i = 0; i < dilithiumN / 2; ++i)
|
||||
{
|
||||
this.setCoeffIndex(2 * i + 0,
|
||||
(int)(((((int)(a[5 * i + 0] & 0xFF))
|
||||
| (int)((a[5 * i + 1] & 0xFF) << 8))
|
||||
| (int)((a[5 * i + 2] & 0xFF) << 16))
|
||||
& 0xFFFFF)
|
||||
);
|
||||
|
||||
this.setCoeffIndex(2 * i + 1,
|
||||
(int)(((((int)((a[5 * i + 2] & 0xFF) >>> 4))
|
||||
| (int)((a[5 * i + 3] & 0xFF) << 4))
|
||||
| (int)((a[5 * i + 4] & 0xFF) << 12))
|
||||
& 0xFFFFF)
|
||||
);
|
||||
|
||||
this.setCoeffIndex(2 * i + 0, engine.getDilithiumGamma1() - this.getCoeffIndex(2 * i + 0));
|
||||
this.setCoeffIndex(2 * i + 1, engine.getDilithiumGamma1() - this.getCoeffIndex(2 * i + 1));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Dilithium Gamma1!");
|
||||
}
|
||||
}
|
||||
|
||||
public void shiftLeft()
|
||||
{
|
||||
for (int i = 0; i < dilithiumN; ++i)
|
||||
{
|
||||
this.setCoeffIndex(i, this.getCoeffIndex(i) << MLDSAEngine.DilithiumD);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuffer out = new StringBuffer();
|
||||
out.append("[");
|
||||
for (int i = 0; i < coeffs.length; i++)
|
||||
{
|
||||
out.append(coeffs[i]);
|
||||
if (i != coeffs.length - 1)
|
||||
{
|
||||
out.append(", ");
|
||||
}
|
||||
}
|
||||
out.append("]");
|
||||
return out.toString();
|
||||
}
|
||||
}
|
198
core/java/src/org/bouncycastle/pqc/crypto/mldsa/PolyVecK.java
Normal file
198
core/java/src/org/bouncycastle/pqc/crypto/mldsa/PolyVecK.java
Normal file
@@ -0,0 +1,198 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
class PolyVecK
|
||||
{
|
||||
Poly[] vec;
|
||||
private MLDSAEngine engine;
|
||||
private int mode;
|
||||
private int polyVecBytes;
|
||||
private int dilithiumK;
|
||||
private int dilithiumL;
|
||||
|
||||
public PolyVecK(MLDSAEngine engine)
|
||||
{
|
||||
this.engine = engine;
|
||||
this.mode = engine.getDilithiumMode();
|
||||
this.dilithiumK = engine.getDilithiumK();
|
||||
this.dilithiumL = engine.getDilithiumL();
|
||||
|
||||
this.vec = new Poly[dilithiumK];
|
||||
for (int i = 0; i < dilithiumK; i++)
|
||||
{
|
||||
vec[i] = new Poly(engine);
|
||||
}
|
||||
}
|
||||
|
||||
public PolyVecK()
|
||||
throws Exception
|
||||
{
|
||||
throw new Exception("Requires Parameter");
|
||||
}
|
||||
|
||||
public Poly getVectorIndex(int i)
|
||||
{
|
||||
return vec[i];
|
||||
}
|
||||
|
||||
public void setVectorIndex(int i, Poly p)
|
||||
{
|
||||
this.vec[i] = p;
|
||||
}
|
||||
|
||||
public void uniformEta(byte[] seed, short nonce)
|
||||
{
|
||||
int i;
|
||||
short n = nonce;
|
||||
for (i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
getVectorIndex(i).uniformEta(seed, n++);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void reduce()
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).reduce();
|
||||
}
|
||||
}
|
||||
|
||||
public void invNttToMont()
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).invNttToMont();
|
||||
}
|
||||
}
|
||||
|
||||
public void addPolyVecK(PolyVecK b)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).addPoly(b.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void conditionalAddQ()
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).conditionalAddQ();
|
||||
}
|
||||
}
|
||||
|
||||
public void power2Round(PolyVecK pvk)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).power2Round(pvk.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void polyVecNtt()
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.vec[i].polyNtt();
|
||||
}
|
||||
}
|
||||
|
||||
public void decompose(PolyVecK v)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).decompose(v.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] packW1()
|
||||
{
|
||||
byte[] out = new byte[dilithiumK * engine.getDilithiumPolyW1PackedBytes()];
|
||||
int i;
|
||||
for (i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
System.arraycopy(this.getVectorIndex(i).w1Pack(), 0, out, i * engine.getDilithiumPolyW1PackedBytes(), engine.getDilithiumPolyW1PackedBytes());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void pointwisePolyMontgomery(Poly a, PolyVecK v)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).pointwiseMontgomery(a, v.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void subtract(PolyVecK inpVec)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).subtract(inpVec.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkNorm(int bound)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
if (this.getVectorIndex(i).checkNorm(bound))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int makeHint(PolyVecK v0, PolyVecK v1)
|
||||
{
|
||||
int i, s = 0;
|
||||
for (i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
s += this.getVectorIndex(i).polyMakeHint(v0.getVectorIndex(i), v1.getVectorIndex(i));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public void useHint(PolyVecK u, PolyVecK h)
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).polyUseHint(u.getVectorIndex(i), h.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void shiftLeft()
|
||||
{
|
||||
for (int i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).shiftLeft();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String out = "[";
|
||||
for (int i = 0; i < dilithiumK; i++)
|
||||
{
|
||||
out += i + " " + this.getVectorIndex(i).toString();
|
||||
if (i == dilithiumK - 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out += ",\n";
|
||||
}
|
||||
out += "]";
|
||||
return out;
|
||||
}
|
||||
|
||||
public String toString(String name)
|
||||
{
|
||||
return name + ": " + this.toString();
|
||||
}
|
||||
}
|
152
core/java/src/org/bouncycastle/pqc/crypto/mldsa/PolyVecL.java
Normal file
152
core/java/src/org/bouncycastle/pqc/crypto/mldsa/PolyVecL.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
class PolyVecL
|
||||
{
|
||||
Poly[] vec;
|
||||
private MLDSAEngine engine;
|
||||
private int mode;
|
||||
private int polyVecBytes;
|
||||
private int dilithiumL;
|
||||
private int dilithiumK;
|
||||
|
||||
public PolyVecL(MLDSAEngine engine)
|
||||
{
|
||||
this.engine = engine;
|
||||
this.mode = engine.getDilithiumMode();
|
||||
this.dilithiumL = engine.getDilithiumL();
|
||||
this.dilithiumK = engine.getDilithiumK();
|
||||
|
||||
this.vec = new Poly[dilithiumL];
|
||||
for (int i = 0; i < dilithiumL; i++)
|
||||
{
|
||||
vec[i] = new Poly(engine);
|
||||
}
|
||||
}
|
||||
|
||||
public PolyVecL()
|
||||
throws Exception
|
||||
{
|
||||
throw new Exception("Requires Parameter");
|
||||
}
|
||||
|
||||
public Poly getVectorIndex(int i)
|
||||
{
|
||||
return vec[i];
|
||||
}
|
||||
|
||||
public void expandMatrix(byte[] rho, int i)
|
||||
{
|
||||
int j;
|
||||
for (j = 0; j < dilithiumL; j++)
|
||||
{
|
||||
vec[j].uniformBlocks(rho, (short)((i << 8) + j));
|
||||
}
|
||||
}
|
||||
|
||||
public void uniformEta(byte[] seed, short nonce)
|
||||
{
|
||||
int i;
|
||||
short n = nonce;
|
||||
for (i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
getVectorIndex(i).uniformEta(seed, n++);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void copyPolyVecL(PolyVecL outPoly)
|
||||
{
|
||||
for (int i = 0; i < dilithiumL; i++)
|
||||
{
|
||||
for (int j = 0; j < MLDSAEngine.DilithiumN; j++)
|
||||
{
|
||||
outPoly.getVectorIndex(i).setCoeffIndex(j, this.getVectorIndex(i).getCoeffIndex(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void polyVecNtt()
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
this.vec[i].polyNtt();
|
||||
}
|
||||
}
|
||||
|
||||
public void uniformGamma1(byte[] seed, short nonce)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).uniformGamma1(seed, (short)(dilithiumL * nonce + i));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void pointwisePolyMontgomery(Poly a, PolyVecL v)
|
||||
{
|
||||
for (int i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).pointwiseMontgomery(a, v.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void invNttToMont()
|
||||
{
|
||||
for (int i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).invNttToMont();
|
||||
}
|
||||
}
|
||||
|
||||
public void addPolyVecL(PolyVecL v)
|
||||
{
|
||||
for (int i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).addPoly(v.getVectorIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void reduce()
|
||||
{
|
||||
for (int i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
this.getVectorIndex(i).reduce();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkNorm(int bound)
|
||||
{
|
||||
for (int i = 0; i < dilithiumL; ++i)
|
||||
{
|
||||
if (this.getVectorIndex(i).checkNorm(bound))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String out = "\n[";
|
||||
for (int i = 0; i < dilithiumL; i++)
|
||||
{
|
||||
out += "Inner Matrix " + i + " " + this.getVectorIndex(i).toString();
|
||||
if (i == dilithiumL - 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out += ",\n";
|
||||
}
|
||||
out += "]";
|
||||
return out;
|
||||
}
|
||||
|
||||
public String toString(String name)
|
||||
{
|
||||
return name + ": " + this.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
class PolyVecMatrix
|
||||
{
|
||||
private final int dilithiumK;
|
||||
private final int dilithiumL;
|
||||
|
||||
private final PolyVecL[] mat;
|
||||
|
||||
/**
|
||||
* PolyVecL Matrix of size K
|
||||
*
|
||||
* @param engine source engine for the matrix to be used by.
|
||||
*/
|
||||
public PolyVecMatrix(MLDSAEngine engine)
|
||||
{
|
||||
this.dilithiumK = engine.getDilithiumK();
|
||||
this.dilithiumL = engine.getDilithiumL();
|
||||
this.mat = new PolyVecL[dilithiumK];
|
||||
|
||||
for (int i = 0; i < dilithiumK; i++)
|
||||
{
|
||||
mat[i] = new PolyVecL(engine);
|
||||
}
|
||||
}
|
||||
|
||||
public void pointwiseMontgomery(PolyVecK t, PolyVecL v)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
t.getVectorIndex(i).pointwiseAccountMontgomery(mat[i], v);
|
||||
}
|
||||
}
|
||||
|
||||
public void expandMatrix(byte[] rho)
|
||||
{
|
||||
int i, j;
|
||||
for (i = 0; i < dilithiumK; ++i)
|
||||
{
|
||||
for (j = 0; j < dilithiumL; ++j)
|
||||
{
|
||||
this.mat[i].getVectorIndex(j).uniformBlocks(rho, (short)((i << 8) + j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String addString()
|
||||
{
|
||||
String out = "[";
|
||||
int i;
|
||||
for (i = 0; i < dilithiumK; i++)
|
||||
{
|
||||
out += "Outer Matrix " + i + " [";
|
||||
out += this.mat[i].toString();
|
||||
if (i == dilithiumK - 1)
|
||||
{
|
||||
out += "]\n";
|
||||
continue;
|
||||
}
|
||||
out += "],\n";
|
||||
}
|
||||
out += "]\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
public String toString(String name)
|
||||
{
|
||||
return name.concat(": \n" + this.addString());
|
||||
}
|
||||
}
|
28
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Reduce.java
Normal file
28
core/java/src/org/bouncycastle/pqc/crypto/mldsa/Reduce.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
class Reduce
|
||||
{
|
||||
static int montgomeryReduce(long a)
|
||||
{
|
||||
int t;
|
||||
t = (int)(a * MLDSAEngine.DilithiumQinv);
|
||||
t = (int)((a - ((long)t) * MLDSAEngine.DilithiumQ) >>> 32);
|
||||
// System.out.printf("%d, ", t);
|
||||
return t;
|
||||
|
||||
}
|
||||
|
||||
static int reduce32(int a)
|
||||
{
|
||||
int t;
|
||||
t = (a + (1 << 22)) >> 23;
|
||||
t = a - t * MLDSAEngine.DilithiumQ;
|
||||
return t;
|
||||
}
|
||||
|
||||
static int conditionalAddQ(int a)
|
||||
{
|
||||
a += (a >> 31) & MLDSAEngine.DilithiumQ;
|
||||
return a;
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
class Rounding
|
||||
{
|
||||
public static int[] power2Round(int a)
|
||||
{
|
||||
int[] out = new int[2];
|
||||
|
||||
out[0] = (a + (1 << (MLDSAEngine.DilithiumD - 1)) - 1) >> MLDSAEngine.DilithiumD;
|
||||
out[1] = a - (out[0] << MLDSAEngine.DilithiumD);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static int[] decompose(int a, int gamma2)
|
||||
{
|
||||
int a1, a0;
|
||||
|
||||
a1 = (a + 127) >> 7;
|
||||
if (gamma2 == (MLDSAEngine.DilithiumQ - 1) / 32)
|
||||
{
|
||||
a1 = (a1 * 1025 + (1 << 21)) >> 22;
|
||||
a1 &= 15;
|
||||
}
|
||||
else if (gamma2 == (MLDSAEngine.DilithiumQ - 1) / 88)
|
||||
{
|
||||
a1 = (a1 * 11275 + (1 << 23)) >> 24;
|
||||
a1 ^= ((43 - a1) >> 31) & a1;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Gamma2!");
|
||||
}
|
||||
|
||||
a0 = a - a1 * 2 * gamma2;
|
||||
a0 -= (((MLDSAEngine.DilithiumQ - 1) / 2 - a0) >> 31) & MLDSAEngine.DilithiumQ;
|
||||
return new int[]{a0, a1};
|
||||
}
|
||||
|
||||
public static int makeHint(int a0, int a1, MLDSAEngine engine)
|
||||
{
|
||||
int g2 = engine.getDilithiumGamma2(), q = MLDSAEngine.DilithiumQ;
|
||||
if (a0 <= g2 || a0 > q - g2 || (a0 == q - g2 && a1 == 0))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static int useHint(int a, int hint, int gamma2)
|
||||
{
|
||||
int a0, a1;
|
||||
|
||||
int[] intArray = decompose(a, gamma2);
|
||||
a0 = intArray[0];
|
||||
a1 = intArray[1];
|
||||
// System.out.printf("a0: %d, a1: %d\n", a0, a1);
|
||||
|
||||
if (hint == 0)
|
||||
{
|
||||
return a1;
|
||||
}
|
||||
|
||||
if (gamma2 == (MLDSAEngine.DilithiumQ - 1) / 32)
|
||||
{
|
||||
if (a0 > 0)
|
||||
{
|
||||
return (a1 + 1) & 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (a1 - 1) & 15;
|
||||
}
|
||||
}
|
||||
else if (gamma2 == (MLDSAEngine.DilithiumQ - 1) / 88)
|
||||
{
|
||||
if (a0 > 0)
|
||||
{
|
||||
return (a1 == 43) ? 0 : a1 + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (a1 == 0) ? 43 : a1 - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Wrong Gamma2!");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package org.bouncycastle.pqc.crypto.mldsa;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SHAKEDigest;
|
||||
|
||||
abstract class Symmetric
|
||||
{
|
||||
|
||||
final int stream128BlockBytes;
|
||||
final int stream256BlockBytes;
|
||||
|
||||
Symmetric(int stream128, int stream256)
|
||||
{
|
||||
this.stream128BlockBytes = stream128;
|
||||
this.stream256BlockBytes = stream256;
|
||||
}
|
||||
|
||||
abstract void stream128init(byte[] seed, short nonce);
|
||||
|
||||
abstract void stream256init(byte[] seed, short nonce);
|
||||
|
||||
abstract void stream128squeezeBlocks(byte[] output, int offset, int size);
|
||||
|
||||
abstract void stream256squeezeBlocks(byte[] output, int offset, int size);
|
||||
|
||||
static class ShakeSymmetric
|
||||
extends Symmetric
|
||||
{
|
||||
private final SHAKEDigest digest128;
|
||||
private final SHAKEDigest digest256;
|
||||
|
||||
ShakeSymmetric()
|
||||
{
|
||||
super(168, 136);
|
||||
digest128 = new SHAKEDigest(128);
|
||||
digest256 = new SHAKEDigest(256);
|
||||
}
|
||||
|
||||
private void streamInit(SHAKEDigest digest, byte[] seed, short nonce)
|
||||
{
|
||||
digest.reset();
|
||||
// byte[] temp = new byte[seed.length + 2];
|
||||
// System.arraycopy(seed, 0, temp, 0, seed.length);
|
||||
|
||||
// temp[seed.length] = (byte) nonce;
|
||||
// temp[seed.length] = (byte) (nonce >> 8);
|
||||
byte[] temp = new byte[2];
|
||||
// System.arraycopy(seed, 0, temp, 0, seed.length);
|
||||
temp[0] = (byte)nonce;
|
||||
temp[1] = (byte)(nonce >> 8);
|
||||
|
||||
digest.update(seed, 0, seed.length);
|
||||
digest.update(temp, 0, temp.length);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void stream128init(byte[] seed, short nonce)
|
||||
{
|
||||
streamInit(digest128, seed, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
void stream256init(byte[] seed, short nonce)
|
||||
{
|
||||
streamInit(digest256, seed, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
void stream128squeezeBlocks(byte[] output, int offset, int size)
|
||||
{
|
||||
digest128.doOutput(output, offset, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
void stream256squeezeBlocks(byte[] output, int offset, int size)
|
||||
{
|
||||
digest256.doOutput(output, offset, size);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package org.bouncycastle.pqc.jcajce.provider;
|
||||
|
||||
import java.security.Provider;
|
||||
|
||||
public class BouncyCastlePQCProvider
|
||||
extends Provider
|
||||
{
|
||||
private static String info = "BouncyCastle Post-Quantum Security Provider v1.80";
|
||||
|
||||
public static String PROVIDER_NAME = "BCPQC";
|
||||
|
||||
/**
|
||||
* Construct a new provider. This should only be required when
|
||||
* using runtime registration of the provider using the
|
||||
* <code>Security.addProvider()</code> mechanism.
|
||||
*/
|
||||
public BouncyCastlePQCProvider()
|
||||
{
|
||||
super(PROVIDER_NAME, 1.80, info);
|
||||
}
|
||||
}
|
26
core/java/src/org/bouncycastle/util/Memoable.java
Normal file
26
core/java/src/org/bouncycastle/util/Memoable.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.bouncycastle.util;
|
||||
|
||||
/**
|
||||
* Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state
|
||||
* via the copy() method and then resetting the object back to that state later using the reset() method.
|
||||
*/
|
||||
public interface Memoable
|
||||
{
|
||||
/**
|
||||
* Produce a copy of this object with its configuration and in its current state.
|
||||
* <p>
|
||||
* The returned object may be used simply to store the state, or may be used as a similar object
|
||||
* starting from the copied state.
|
||||
*/
|
||||
Memoable copy();
|
||||
|
||||
/**
|
||||
* Restore a copied object state into this object.
|
||||
* <p>
|
||||
* Implementations of this method <em>should</em> try to avoid or minimise memory allocation to perform the reset.
|
||||
*
|
||||
* @param other an object originally {@link #copy() copied} from an object of the same type as this instance.
|
||||
* @throws ClassCastException if the provided object is not of the correct type.
|
||||
*/
|
||||
void reset(Memoable other);
|
||||
}
|
419
core/java/src/org/bouncycastle/util/Pack.java
Normal file
419
core/java/src/org/bouncycastle/util/Pack.java
Normal file
@@ -0,0 +1,419 @@
|
||||
package org.bouncycastle.util;
|
||||
|
||||
/**
|
||||
* Utility methods for converting byte arrays into ints and longs, and back again.
|
||||
*/
|
||||
public abstract class Pack
|
||||
{
|
||||
public static short bigEndianToShort(byte[] bs, int off)
|
||||
{
|
||||
int n = (bs[off] & 0xff) << 8;
|
||||
n |= (bs[++off] & 0xff);
|
||||
return (short)n;
|
||||
}
|
||||
|
||||
public static int bigEndianToInt(byte[] bs, int off)
|
||||
{
|
||||
int n = bs[off] << 24;
|
||||
n |= (bs[++off] & 0xff) << 16;
|
||||
n |= (bs[++off] & 0xff) << 8;
|
||||
n |= (bs[++off] & 0xff);
|
||||
return n;
|
||||
}
|
||||
|
||||
public static void bigEndianToInt(byte[] bs, int off, int[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = bigEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static void bigEndianToInt(byte[] bs, int off, int[] ns, int nsOff, int nsLen)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
ns[nsOff + i] = bigEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] intToBigEndian(int n)
|
||||
{
|
||||
byte[] bs = new byte[4];
|
||||
intToBigEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToBigEndian(int n, byte[] bs, int off)
|
||||
{
|
||||
bs[off] = (byte)(n >>> 24);
|
||||
bs[++off] = (byte)(n >>> 16);
|
||||
bs[++off] = (byte)(n >>> 8);
|
||||
bs[++off] = (byte)(n);
|
||||
}
|
||||
|
||||
public static byte[] intToBigEndian(int[] ns)
|
||||
{
|
||||
byte[] bs = new byte[4 * ns.length];
|
||||
intToBigEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToBigEndian(int[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
intToBigEndian(ns[i], bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static void intToBigEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
intToBigEndian(ns[nsOff + i], bs, bsOff);
|
||||
bsOff += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static long bigEndianToLong(byte[] bs, int off)
|
||||
{
|
||||
int hi = bigEndianToInt(bs, off);
|
||||
int lo = bigEndianToInt(bs, off + 4);
|
||||
return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static void bigEndianToLong(byte[] bs, int off, long[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = bigEndianToLong(bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static void bigEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
ns[nsOff + i] = bigEndianToLong(bs, bsOff);
|
||||
bsOff += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static long bigEndianToLong(byte[] bs, int off, int len)
|
||||
{
|
||||
long x = 0;
|
||||
for (int i = 0; i < len; ++i)
|
||||
{
|
||||
x |= (bs[i + off] & 0xFFL) << ((7 - i) << 3);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
public static byte[] longToBigEndian(long n)
|
||||
{
|
||||
byte[] bs = new byte[8];
|
||||
longToBigEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToBigEndian(long n, byte[] bs, int off)
|
||||
{
|
||||
intToBigEndian((int)(n >>> 32), bs, off);
|
||||
intToBigEndian((int)(n & 0xffffffffL), bs, off + 4);
|
||||
}
|
||||
|
||||
public static byte[] longToBigEndian(long[] ns)
|
||||
{
|
||||
byte[] bs = new byte[8 * ns.length];
|
||||
longToBigEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToBigEndian(long[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
longToBigEndian(ns[i], bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static void longToBigEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
longToBigEndian(ns[nsOff + i], bs, bsOff);
|
||||
bsOff += 8;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value The number
|
||||
* @param bs The target.
|
||||
* @param off Position in target to start.
|
||||
* @param bytes number of bytes to write.
|
||||
* @deprecated Will be removed
|
||||
*/
|
||||
public static void longToBigEndian(long value, byte[] bs, int off, int bytes)
|
||||
{
|
||||
for (int i = bytes - 1; i >= 0; i--)
|
||||
{
|
||||
bs[i + off] = (byte)(value & 0xff);
|
||||
value >>>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static short littleEndianToShort(byte[] bs, int off)
|
||||
{
|
||||
int n = bs[off] & 0xff;
|
||||
n |= (bs[++off] & 0xff) << 8;
|
||||
return (short)n;
|
||||
}
|
||||
|
||||
public static int littleEndianToInt(byte[] bs, int off)
|
||||
{
|
||||
int n = bs[off] & 0xff;
|
||||
n |= (bs[++off] & 0xff) << 8;
|
||||
n |= (bs[++off] & 0xff) << 16;
|
||||
n |= bs[++off] << 24;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int littleEndianToInt_High(byte[] bs, int off, int len)
|
||||
{
|
||||
return littleEndianToInt_Low(bs, off, len) << ((4 - len) << 3);
|
||||
}
|
||||
|
||||
public static int littleEndianToInt_Low(byte[] bs, int off, int len)
|
||||
{
|
||||
// assert 1 <= len && len <= 4;
|
||||
|
||||
int result = bs[off] & 0xff;
|
||||
int pos = 0;
|
||||
for (int i = 1; i < len; ++i)
|
||||
{
|
||||
pos += 8;
|
||||
result |= (bs[off + i] & 0xff) << pos;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void littleEndianToInt(byte[] bs, int off, int[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = littleEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static void littleEndianToInt(byte[] bs, int bOff, int[] ns, int nOff, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
ns[nOff + i] = littleEndianToInt(bs, bOff);
|
||||
bOff += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] littleEndianToInt(byte[] bs, int off, int count)
|
||||
{
|
||||
int[] ns = new int[count];
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = littleEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
public static byte[] shortToLittleEndian(short n)
|
||||
{
|
||||
byte[] bs = new byte[2];
|
||||
shortToLittleEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void shortToLittleEndian(short n, byte[] bs, int off)
|
||||
{
|
||||
bs[off] = (byte)(n);
|
||||
bs[++off] = (byte)(n >>> 8);
|
||||
}
|
||||
|
||||
|
||||
public static byte[] shortToBigEndian(short n)
|
||||
{
|
||||
byte[] r = new byte[2];
|
||||
shortToBigEndian(n, r, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static void shortToBigEndian(short n, byte[] bs, int off)
|
||||
{
|
||||
bs[off] = (byte)(n >>> 8);
|
||||
bs[++off] = (byte)(n);
|
||||
}
|
||||
|
||||
|
||||
public static byte[] intToLittleEndian(int n)
|
||||
{
|
||||
byte[] bs = new byte[4];
|
||||
intToLittleEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToLittleEndian(int n, byte[] bs, int off)
|
||||
{
|
||||
bs[off] = (byte)(n);
|
||||
bs[++off] = (byte)(n >>> 8);
|
||||
bs[++off] = (byte)(n >>> 16);
|
||||
bs[++off] = (byte)(n >>> 24);
|
||||
}
|
||||
|
||||
public static byte[] intToLittleEndian(int[] ns)
|
||||
{
|
||||
byte[] bs = new byte[4 * ns.length];
|
||||
intToLittleEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToLittleEndian(int[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
intToLittleEndian(ns[i], bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static void intToLittleEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
intToLittleEndian(ns[nsOff + i], bs, bsOff);
|
||||
bsOff += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static long littleEndianToLong(byte[] bs, int off)
|
||||
{
|
||||
int lo = littleEndianToInt(bs, off);
|
||||
int hi = littleEndianToInt(bs, off + 4);
|
||||
return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static void littleEndianToLong(byte[] bs, int off, long[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = littleEndianToLong(bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static long littleEndianToLong(byte[] input, int off, int len)
|
||||
{
|
||||
long result = 0;
|
||||
for (int i = 0; i < len; ++i)
|
||||
{
|
||||
result |= (input[off + i] & 0xFFL) << (i << 3);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void littleEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
ns[nsOff + i] = littleEndianToLong(bs, bsOff);
|
||||
bsOff += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static void longToLittleEndian_High(long n, byte[] bs, int off, int len)
|
||||
{
|
||||
//Debug.Assert(1 <= len && len <= 8);
|
||||
int pos = 56;
|
||||
bs[off] = (byte)(n >>> pos);
|
||||
for (int i = 1; i < len; ++i)
|
||||
{
|
||||
pos -= 8;
|
||||
bs[off + i] = (byte)(n >>> pos);
|
||||
}
|
||||
}
|
||||
|
||||
public static void longToLittleEndian(long n, byte[] bs, int off, int len)
|
||||
{
|
||||
for (int i = 0; i < len; ++i)
|
||||
{
|
||||
bs[off + i] = (byte)(n >>> (i << 3));
|
||||
}
|
||||
}
|
||||
|
||||
// public static void longToLittleEndian_Low(long n, byte[] bs, int off, int len)
|
||||
// {
|
||||
// longToLittleEndian_High(n << ((8 - len) << 3), bs, off, len);
|
||||
// }
|
||||
|
||||
public static long littleEndianToLong_High(byte[] bs, int off, int len)
|
||||
{
|
||||
return littleEndianToLong_Low(bs, off, len) << ((8 - len) << 3);
|
||||
}
|
||||
|
||||
public static long littleEndianToLong_Low(byte[] bs, int off, int len)
|
||||
{
|
||||
//Debug.Assert(1 <= len && len <= 8);
|
||||
long result = bs[off] & 0xFF;
|
||||
for (int i = 1; i < len; ++i)
|
||||
{
|
||||
result <<= 8;
|
||||
result |= bs[off + i] & 0xFF;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] longToLittleEndian(long n)
|
||||
{
|
||||
byte[] bs = new byte[8];
|
||||
longToLittleEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToLittleEndian(long n, byte[] bs, int off)
|
||||
{
|
||||
intToLittleEndian((int)(n & 0xffffffffL), bs, off);
|
||||
intToLittleEndian((int)(n >>> 32), bs, off + 4);
|
||||
}
|
||||
|
||||
public static byte[] longToLittleEndian(long[] ns)
|
||||
{
|
||||
byte[] bs = new byte[8 * ns.length];
|
||||
longToLittleEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToLittleEndian(long[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
longToLittleEndian(ns[i], bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static void longToLittleEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
|
||||
{
|
||||
for (int i = 0; i < nsLen; ++i)
|
||||
{
|
||||
longToLittleEndian(ns[nsOff + i], bs, bsOff);
|
||||
bsOff += 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
52
core/java/src/org/bouncycastle/util/Util.java
Normal file
52
core/java/src/org/bouncycastle/util/Util.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package org.bouncycastle.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
public class Util
|
||||
{
|
||||
public static byte[] clone(byte[] a)
|
||||
{
|
||||
return Arrays.copyOf(a, a.length);
|
||||
}
|
||||
|
||||
public static byte[] concatenate(byte[][] arrays)
|
||||
{
|
||||
int size = 0;
|
||||
for (int i = 0; i != arrays.length; i++)
|
||||
{
|
||||
size += arrays[i].length;
|
||||
}
|
||||
|
||||
byte[] rv = new byte[size];
|
||||
|
||||
int offSet = 0;
|
||||
for (int i = 0; i != arrays.length; i++)
|
||||
{
|
||||
System.arraycopy(arrays[i], 0, rv, offSet, arrays[i].length);
|
||||
offSet += arrays[i].length;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] concatenate(byte[] a, byte[] b)
|
||||
{
|
||||
byte[] rv = Arrays.copyOf(a, a.length + b.length);
|
||||
System.arraycopy(b, 0, rv, a.length, b.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] append(byte[] a, byte b)
|
||||
{
|
||||
byte[] rv = Arrays.copyOf(a, a.length + 1);
|
||||
rv[a.length] = b;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static boolean constantTimeAreEqual(byte[] a, byte[] b)
|
||||
{
|
||||
return a.length == b.length && DataHelper.eqCT(a, 0, b, 0, a.length);
|
||||
}
|
||||
}
|
4
core/java/src/org/bouncycastle/util/package-info.java
Normal file
4
core/java/src/org/bouncycastle/util/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* General purpose utility classes used throughout the APIs.
|
||||
*/
|
||||
package org.bouncycastle.util;
|
21
licenses/LICENSE-Bouncycastle.txt
Normal file
21
licenses/LICENSE-Bouncycastle.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* The Bouncy Castle License
|
||||
*
|
||||
* Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
@@ -39,8 +39,10 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
private final boolean isInitiator;
|
||||
private DHState localKeyPair;
|
||||
private DHState localEphemeral;
|
||||
private DHState localHybrid;
|
||||
private DHState remotePublicKey;
|
||||
private DHState remoteEphemeral;
|
||||
private DHState remoteHybrid;
|
||||
private int action;
|
||||
private final int requirements;
|
||||
private int patternIndex;
|
||||
@@ -139,6 +141,28 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256";
|
||||
/** SSU2 */
|
||||
public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256";
|
||||
/**
|
||||
* Hybrid Ratchet
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final String protocolName5 = "Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256";
|
||||
public static final String protocolName6 = "Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256";
|
||||
public static final String protocolName7 = "Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256";
|
||||
/**
|
||||
* Hybrid NTCP2
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final String protocolName8 = "Noise_XKhfsaesobfse+hs2+hs3_25519+MLKEM512_ChaChaPoly_SHA256";
|
||||
public static final String protocolName9 = "Noise_XKhfsaesobfse+hs2+hs3_25519+MLKEM768_ChaChaPoly_SHA256";
|
||||
public static final String protocolName10 = "Noise_XKhfsaesobfse+hs2+hs3_25519+MLKEM1024_ChaChaPoly_SHA256";
|
||||
/**
|
||||
* Hybrid SSU2
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final String protocolName11 = "Noise_XKhfschaobfse+hs1+hs2+hs3_25519+MLKEM512_ChaChaPoly_SHA256";
|
||||
public static final String protocolName12 = "Noise_XKhfschaobfse+hs1+hs2+hs3_25519+MLKEM768_ChaChaPoly_SHA256";
|
||||
public static final String protocolName13 = "Noise_XKhfschaobfse+hs1+hs2+hs3_25519+MLKEM1024_ChaChaPoly_SHA256";
|
||||
|
||||
private static final String prefix;
|
||||
private final String patternId;
|
||||
/** NTCP2 */
|
||||
@@ -151,13 +175,39 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
public static final String PATTERN_ID_N_NO_RESPONSE = "N!";
|
||||
/** SSU2 */
|
||||
public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2";
|
||||
private static String dh;
|
||||
/** Hybrid Base */
|
||||
private static final String PATTERN_ID_IKHFS = "IKhfs";
|
||||
private static final String PATTERN_ID_XKHFS = "XKhfs";
|
||||
/**
|
||||
* Hybrid Ratchet
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final String PATTERN_ID_IKHFS_512 = "IKhfs512";
|
||||
public static final String PATTERN_ID_IKHFS_768 = "IKhfs768";
|
||||
public static final String PATTERN_ID_IKHFS_1024 = "IKhfs1024";
|
||||
/**
|
||||
* Hybrid NTCP2
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final String PATTERN_ID_XKHFS_512 = "XKhfs512";
|
||||
public static final String PATTERN_ID_XKHFS_768 = "XKhfs768";
|
||||
public static final String PATTERN_ID_XKHFS_1024 = "XKhfs1024";
|
||||
/**
|
||||
* Hybrid SSU2
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public static final String PATTERN_ID_XKHFS_512_SSU2 = "XKhfs512-SSU2";
|
||||
public static final String PATTERN_ID_XKHFS_768_SSU2 = "XKhfs768-SSU2";
|
||||
// no 1024, too big
|
||||
|
||||
private static final String dh;
|
||||
private static final String cipher;
|
||||
private static final String hash;
|
||||
private final short[] pattern;
|
||||
private static final short[] PATTERN_XK;
|
||||
private static final short[] PATTERN_IK;
|
||||
private static final short[] PATTERN_N;
|
||||
private static final short[] PATTERN_IKHFS;
|
||||
|
||||
static {
|
||||
// Parse the protocol name into its components.
|
||||
@@ -200,6 +250,22 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
id = components[1].substring(0, 2);
|
||||
if (!PATTERN_ID_XK.equals(id))
|
||||
throw new IllegalArgumentException();
|
||||
// IK Hybrid
|
||||
components = protocolName5.split("_");
|
||||
id = components[1].substring(0, 5);
|
||||
if (!PATTERN_ID_IKHFS.equals(id))
|
||||
throw new IllegalArgumentException();
|
||||
PATTERN_IKHFS = Pattern.lookup(id);
|
||||
if (PATTERN_IKHFS == null)
|
||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||
components = protocolName6.split("_");
|
||||
id = components[1].substring(0, 5);
|
||||
if (!PATTERN_ID_IKHFS.equals(id))
|
||||
throw new IllegalArgumentException();
|
||||
components = protocolName7.split("_");
|
||||
id = components[1].substring(0, 5);
|
||||
if (!PATTERN_ID_IKHFS.equals(id))
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,6 +283,26 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
* that is specified in the protocolName is not supported.
|
||||
*/
|
||||
public HandshakeState(String patternId, int role, KeyFactory xdh) throws NoSuchAlgorithmException
|
||||
{
|
||||
this(patternId, role, xdh, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Noise handshake.
|
||||
* Noise protocol name is hardcoded.
|
||||
*
|
||||
* @param patternId XK, IK, or N
|
||||
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
|
||||
* @param xdh The key pair factory for ephemeral keys
|
||||
* @param hdh The key pair factory for hybrid keys, or null for non-hybrid
|
||||
*
|
||||
* @throws IllegalArgumentException The protocolName is not
|
||||
* formatted correctly, or the role is not recognized.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException One of the cryptographic algorithms
|
||||
* that is specified in the protocolName is not supported.
|
||||
*/
|
||||
public HandshakeState(String patternId, int role, KeyFactory xdh, KeyFactory hdh) throws NoSuchAlgorithmException
|
||||
{
|
||||
this.patternId = patternId;
|
||||
if (patternId.equals(PATTERN_ID_XK))
|
||||
@@ -229,6 +315,10 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
pattern = PATTERN_N;
|
||||
else if (patternId.equals(PATTERN_ID_XK_SSU2))
|
||||
pattern = PATTERN_XK;
|
||||
else if (patternId.equals(PATTERN_ID_IKHFS_512) ||
|
||||
patternId.equals(PATTERN_ID_IKHFS_768) ||
|
||||
patternId.equals(PATTERN_ID_IKHFS_1024))
|
||||
pattern = PATTERN_IKHFS;
|
||||
else
|
||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||
short flags = pattern[0];
|
||||
@@ -256,10 +346,20 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
localKeyPair = new Curve25519DHState(xdh);
|
||||
if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0)
|
||||
localEphemeral = new Curve25519DHState(xdh);
|
||||
if ((flags & Pattern.FLAG_LOCAL_HYBRID) != 0) {
|
||||
if (isInitiator && hdh == null)
|
||||
throw new IllegalArgumentException("Hybrid patterns require hybrid key generator");
|
||||
localHybrid = isInitiator ? new MLKEMDHState(hdh, patternId) : new MLKEMDHState(patternId);
|
||||
}
|
||||
if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0)
|
||||
remotePublicKey = new Curve25519DHState(xdh);
|
||||
if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0)
|
||||
remoteEphemeral = new Curve25519DHState(xdh);
|
||||
if ((flags & Pattern.FLAG_REMOTE_HYBRID) != 0) {
|
||||
if (!isInitiator && hdh == null)
|
||||
throw new IllegalArgumentException("Hybrid patterns require hybrid key generator");
|
||||
remoteHybrid = isInitiator ? new MLKEMDHState(patternId) : new MLKEMDHState(hdh, patternId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -419,6 +519,32 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keypair object for the local hybrid key.
|
||||
*
|
||||
* I2P
|
||||
*
|
||||
* @return The keypair, or null if a local hybrid key is not required or has not been generated.
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public DHState getLocalHybridKeyPair()
|
||||
{
|
||||
return localHybrid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keypair object for the remote hybrid key.
|
||||
*
|
||||
* I2P
|
||||
*
|
||||
* @return The keypair, or null if a remote hybrid key is not required or has not been generated.
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public DHState getRemoteHybridKeyPair()
|
||||
{
|
||||
return remoteHybrid;
|
||||
}
|
||||
|
||||
// Empty value for when the prologue is not supplied.
|
||||
private static final byte[] emptyPrologue = new byte [0];
|
||||
|
||||
@@ -472,17 +598,11 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
if (isInitiator) {
|
||||
if ((requirements & LOCAL_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(localKeyPair);
|
||||
if ((requirements & FALLBACK_PREMSG) != 0) {
|
||||
symmetric.mixPublicKey(remoteEphemeral);
|
||||
}
|
||||
if ((requirements & REMOTE_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(remotePublicKey);
|
||||
} else {
|
||||
if ((requirements & REMOTE_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(remotePublicKey);
|
||||
if ((requirements & FALLBACK_PREMSG) != 0) {
|
||||
symmetric.mixPublicKey(localEphemeral);
|
||||
}
|
||||
if ((requirements & LOCAL_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(localKeyPair);
|
||||
}
|
||||
@@ -675,6 +795,57 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
}
|
||||
break;
|
||||
|
||||
case Pattern.F:
|
||||
{
|
||||
// Generate a local hybrid keypair and add the public
|
||||
// key to the message. If we are running fixed vector tests,
|
||||
// then a fixed hybrid key may have already been provided.
|
||||
if (localHybrid == null)
|
||||
throw new IllegalStateException("Pattern definition error");
|
||||
byte[] shared = null;
|
||||
if (isInitiator) {
|
||||
// Only Alice generates a keypair
|
||||
localHybrid.generateKeyPair();
|
||||
} else {
|
||||
// Only Bob. We have to do the FF part here,
|
||||
// so we split up mixDH()
|
||||
// and do the localHybrid.calculate() first
|
||||
// and the mixKey() after.
|
||||
// mixDH(localHybrid, remoteHybrid)
|
||||
// First part
|
||||
len = localHybrid.getSharedKeyLength();
|
||||
shared = new byte [len];
|
||||
// this creates the ciphertext and puts it in localHybrid.publicKey
|
||||
localHybrid.calculate(shared, 0, remoteHybrid);
|
||||
//System.out.println("State middle of F, len=" + len);
|
||||
//System.out.println(toString());
|
||||
}
|
||||
len = localHybrid.getPublicKeyLength();
|
||||
macLen = symmetric.getMACLength();
|
||||
if (space < (len + macLen))
|
||||
throw new ShortBufferException();
|
||||
localHybrid.getPublicKey(message, messagePosn);
|
||||
messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len);
|
||||
if (!isInitiator) {
|
||||
// Second part
|
||||
// We do the rest of the FF part here while we have the shared key
|
||||
symmetric.mixKey(shared, 0, shared.length);
|
||||
Noise.destroy(shared);
|
||||
}
|
||||
//System.out.println("State after F, len=" + len);
|
||||
//System.out.println(toString());
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case Pattern.FF:
|
||||
{
|
||||
// DH operation with initiator and responder hybrid keys.
|
||||
// We are Bob.
|
||||
// This is a NOOP, we did the mixDH() in Pattern.F above.
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// Unknown token code. Abort.
|
||||
@@ -857,6 +1028,39 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
mixDH(localKeyPair, remotePublicKey);
|
||||
}
|
||||
break;
|
||||
|
||||
case Pattern.F:
|
||||
{
|
||||
// Decrypt and read the remote hybrid ephemeral key.
|
||||
if (remoteHybrid == null)
|
||||
throw new IllegalStateException("Pattern definition error");
|
||||
len = remoteHybrid.getPublicKeyLength();
|
||||
macLen = symmetric.getMACLength();
|
||||
if (space < (len + macLen))
|
||||
throw new ShortBufferException();
|
||||
byte[] temp = new byte [len];
|
||||
try {
|
||||
if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len)
|
||||
throw new ShortBufferException();
|
||||
remoteHybrid.setPublicKey(temp, 0);
|
||||
} finally {
|
||||
Noise.destroy(temp);
|
||||
}
|
||||
//System.out.println("State after F");
|
||||
//System.out.println(toString());
|
||||
messageOffset += len + macLen;
|
||||
}
|
||||
break;
|
||||
|
||||
case Pattern.FF:
|
||||
{
|
||||
// DH operation with initiator and responder hybrid keys.
|
||||
// We are Alice.
|
||||
mixDH(localHybrid, remoteHybrid);
|
||||
//System.out.println("State after FF");
|
||||
//System.out.println(toString());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
@@ -960,10 +1164,14 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
localKeyPair.destroy();
|
||||
if (localEphemeral != null)
|
||||
localEphemeral.destroy();
|
||||
if (localHybrid != null)
|
||||
localHybrid.destroy();
|
||||
if (remotePublicKey != null)
|
||||
remotePublicKey.destroy();
|
||||
if (remoteEphemeral != null)
|
||||
remoteEphemeral.destroy();
|
||||
if (remoteHybrid != null)
|
||||
remoteHybrid.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -992,11 +1200,13 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
requirements |= REMOTE_REQUIRED;
|
||||
requirements |= REMOTE_PREMSG;
|
||||
}
|
||||
/*
|
||||
if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ |
|
||||
Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) {
|
||||
if (isFallback)
|
||||
requirements |= FALLBACK_PREMSG;
|
||||
}
|
||||
*/
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@@ -1085,6 +1295,34 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
}
|
||||
buf.append('\n');
|
||||
|
||||
dh = localHybrid;
|
||||
if (dh != null) {
|
||||
buf.append("Local hybrid public key (e1/ekem1) : ");
|
||||
if (dh != null && dh.hasPublicKey()) {
|
||||
tmp = new byte[dh.getPublicKeyLength()];
|
||||
dh.getPublicKey(tmp, 0);
|
||||
buf.append(tmp.length).append(" bytes ");
|
||||
buf.append(net.i2p.data.Base64.encode(tmp));
|
||||
} else {
|
||||
buf.append("null");
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
|
||||
dh = remoteHybrid;
|
||||
if (dh != null) {
|
||||
buf.append("Remote hybrid public key (e1/ekem1) : ");
|
||||
if (dh != null && dh.hasPublicKey()) {
|
||||
tmp = new byte[dh.getPublicKeyLength()];
|
||||
dh.getPublicKey(tmp, 0);
|
||||
buf.append(tmp.length).append(" bytes ");
|
||||
buf.append(net.i2p.data.Base64.encode(tmp));
|
||||
} else {
|
||||
buf.append("null");
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.protocol;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.KeyFactory;
|
||||
import net.i2p.crypto.KeyPair;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.router.crypto.pqc.MLKEM;
|
||||
|
||||
/**
|
||||
* Implementation of the MLKEM algorithm for the Noise protocol.
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
class MLKEMDHState implements DHState, Cloneable {
|
||||
|
||||
private final EncType type;
|
||||
private final byte[] publicKey;
|
||||
private final byte[] privateKey;
|
||||
private int mode;
|
||||
private final KeyFactory _hdh;
|
||||
|
||||
/**
|
||||
* Bob side, do not call generateKeyPair()
|
||||
*/
|
||||
public MLKEMDHState(String patternId)
|
||||
{
|
||||
this(null, patternId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice side
|
||||
*/
|
||||
public MLKEMDHState(KeyFactory hdh, String patternId)
|
||||
{
|
||||
boolean isAlice = hdh != null;
|
||||
if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) {
|
||||
type = isAlice ? EncType.MLKEM512_X25519_INT : EncType.MLKEM512_X25519_CT;
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) {
|
||||
type = isAlice ? EncType.MLKEM768_X25519_INT : EncType.MLKEM512_X25519_CT;
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) {
|
||||
type = isAlice ? EncType.MLKEM1024_X25519_INT : EncType.MLKEM512_X25519_CT;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||
}
|
||||
publicKey = new byte [type.getPubkeyLen()];
|
||||
privateKey = isAlice ? new byte [type.getPrivkeyLen()] : null;
|
||||
mode = 0;
|
||||
_hdh = hdh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
clearKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDHName() {
|
||||
return "MLKEM";
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Alice/Bob sizes are different
|
||||
*/
|
||||
@Override
|
||||
public int getPublicKeyLength() {
|
||||
return type.getPubkeyLen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Alice/Bob sizes are different
|
||||
* @return 0 for Bob
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public int getPrivateKeyLength() {
|
||||
return type.getPrivkeyLen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSharedKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice side ONLY
|
||||
*/
|
||||
@Override
|
||||
public void generateKeyPair() {
|
||||
if (_hdh == null)
|
||||
throw new IllegalStateException("Don't keygen PQ on Bob side");
|
||||
KeyPair kp = _hdh.getKeys();
|
||||
System.arraycopy(kp.getPrivate().getData(), 0, privateKey, 0, type.getPrivkeyLen());
|
||||
System.arraycopy(kp.getPublic().getData(), 0, publicKey, 0, type.getPubkeyLen());
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPublicKey(byte[] key, int offset) {
|
||||
System.arraycopy(publicKey, 0, key, offset, type.getPubkeyLen());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, publicKey, 0, type.getPubkeyLen());
|
||||
if (privateKey != null)
|
||||
Arrays.fill(privateKey, (byte)0);
|
||||
mode = 0x01;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void getPrivateKey(byte[] key, int offset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void setPrivateKey(byte[] key, int offset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void setKeys(byte[] privkey, int privoffset, byte[] pubkey, int puboffset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToNullPublicKey() {
|
||||
Arrays.fill(publicKey, (byte)0);
|
||||
Arrays.fill(privateKey, (byte)0);
|
||||
mode = 0x01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearKey() {
|
||||
Noise.destroy(publicKey);
|
||||
Noise.destroy(privateKey);
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPublicKey() {
|
||||
return (mode & 0x01) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrivateKey() {
|
||||
return (mode & 0x02) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullPublicKey() {
|
||||
if ((mode & 0x01) == 0)
|
||||
return false;
|
||||
int temp = 0;
|
||||
for (int index = 0; index < publicKey.length; ++index)
|
||||
temp |= publicKey[index];
|
||||
return temp == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P
|
||||
*/
|
||||
@Override
|
||||
public boolean hasEncodedPublicKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void getEncodedPublicKey(byte[] key, int offset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect: If we are Bob, copies the ciphertext to our public key
|
||||
* so it may be written out in the message.
|
||||
*/
|
||||
@Override
|
||||
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
|
||||
if (!(publicDH instanceof MLKEMDHState))
|
||||
throw new IllegalArgumentException("Incompatible DH algorithms");
|
||||
try {
|
||||
if (hasPrivateKey()) {
|
||||
// we are Alice
|
||||
byte[] sk = MLKEM.decaps(type, ((MLKEMDHState)publicDH).publicKey, privateKey);
|
||||
System.arraycopy(sk, 0, sharedKey, offset, sk.length);
|
||||
} else if (!hasPublicKey()) {
|
||||
// we are Bob
|
||||
byte[][] rv = MLKEM.encaps(type, ((MLKEMDHState)publicDH).publicKey);
|
||||
byte[] ct = rv[0];
|
||||
byte[] sk = rv[1];
|
||||
System.arraycopy(sk, 0, sharedKey, offset, sk.length);
|
||||
setPublicKey(ct, 0);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
System.out.println("Calculated shared PQ key: " + net.i2p.data.Base64.encode(sharedKey, offset, 32));
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalArgumentException(gse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(DHState other) {
|
||||
if (!(other instanceof MLKEMDHState))
|
||||
throw new IllegalStateException("Mismatched DH key objects");
|
||||
if (other == this)
|
||||
return;
|
||||
MLKEMDHState dh = (MLKEMDHState)other;
|
||||
if (dh.privateKey != null)
|
||||
System.arraycopy(dh.privateKey, 0, privateKey, 0, type.getPrivkeyLen());
|
||||
if (dh.publicKey != null)
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, type.getPubkeyLen());
|
||||
mode = dh.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P
|
||||
*/
|
||||
@Override
|
||||
public MLKEMDHState clone() throws CloneNotSupportedException {
|
||||
return (MLKEMDHState) super.clone();
|
||||
}
|
||||
}
|
@@ -97,6 +97,56 @@ class Pattern {
|
||||
SE
|
||||
};
|
||||
|
||||
/**
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static final short[] noise_pattern_XKhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
F,
|
||||
FF,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
/**
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static final short[] noise_pattern_IKhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
F,
|
||||
S,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
F,
|
||||
FF,
|
||||
SE
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up the description information for a pattern.
|
||||
*
|
||||
@@ -111,6 +161,10 @@ class Pattern {
|
||||
return noise_pattern_XK;
|
||||
else if (name.equals("IK"))
|
||||
return noise_pattern_IK;
|
||||
else if (name.equals("XKhfs"))
|
||||
return noise_pattern_XKhfs;
|
||||
else if (name.equals("IKhfs"))
|
||||
return noise_pattern_IKhfs;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@@ -41,17 +41,26 @@ class SymmetricState implements Destroyable, Cloneable {
|
||||
private static final byte[] INIT_CK_IK;
|
||||
private static final byte[] INIT_CK_N;
|
||||
private static final byte[] INIT_CK_XK_SSU2;
|
||||
private static final byte[] INIT_CK_IKHFS_512;
|
||||
private static final byte[] INIT_CK_IKHFS_768;
|
||||
private static final byte[] INIT_CK_IKHFS_1024;
|
||||
// precalculated hash of the hash of the Noise name = mixHash(nullPrologue)
|
||||
private static final byte[] INIT_HASH_XK = new byte[32];
|
||||
private static final byte[] INIT_HASH_IK = new byte[32];
|
||||
private static final byte[] INIT_HASH_N = new byte[32];
|
||||
private static final byte[] INIT_HASH_XK_SSU2 = new byte[32];
|
||||
private static final byte[] INIT_HASH_IKHFS_512 = new byte[32];
|
||||
private static final byte[] INIT_HASH_IKHFS_768 = new byte[32];
|
||||
private static final byte[] INIT_HASH_IKHFS_1024 = new byte[32];
|
||||
|
||||
static {
|
||||
INIT_CK_XK = initHash(HandshakeState.protocolName);
|
||||
INIT_CK_IK = initHash(HandshakeState.protocolName2);
|
||||
INIT_CK_N = initHash(HandshakeState.protocolName3);
|
||||
INIT_CK_XK_SSU2 = initHash(HandshakeState.protocolName4);
|
||||
INIT_CK_IKHFS_512 = initHash(HandshakeState.protocolName5);
|
||||
INIT_CK_IKHFS_768 = initHash(HandshakeState.protocolName6);
|
||||
INIT_CK_IKHFS_1024 = initHash(HandshakeState.protocolName7);
|
||||
try {
|
||||
MessageDigest md = Noise.createHash("SHA256");
|
||||
md.update(INIT_CK_XK, 0, 32);
|
||||
@@ -62,6 +71,12 @@ class SymmetricState implements Destroyable, Cloneable {
|
||||
md.digest(INIT_HASH_N, 0, 32);
|
||||
md.update(INIT_CK_XK_SSU2, 0, 32);
|
||||
md.digest(INIT_HASH_XK_SSU2, 0, 32);
|
||||
md.update(INIT_CK_IKHFS_512, 0, 32);
|
||||
md.digest(INIT_HASH_IKHFS_512, 0, 32);
|
||||
md.update(INIT_CK_IKHFS_768, 0, 32);
|
||||
md.digest(INIT_HASH_IKHFS_768, 0, 32);
|
||||
md.update(INIT_CK_IKHFS_1024, 0, 32);
|
||||
md.digest(INIT_HASH_IKHFS_1024, 0, 32);
|
||||
Noise.releaseHash(md);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
@@ -136,6 +151,15 @@ class SymmetricState implements Destroyable, Cloneable {
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) {
|
||||
initCK = INIT_CK_XK_SSU2;
|
||||
initHash = INIT_HASH_XK_SSU2;
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) {
|
||||
initCK = INIT_CK_IKHFS_512;
|
||||
initHash = INIT_HASH_IKHFS_512;
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) {
|
||||
initCK = INIT_CK_IKHFS_768;
|
||||
initHash = INIT_HASH_IKHFS_768;
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) {
|
||||
initCK = INIT_CK_IKHFS_1024;
|
||||
initHash = INIT_HASH_IKHFS_1024;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ public class LeaseSetKeys {
|
||||
private final SigningPrivateKey _revocationKey;
|
||||
private final PrivateKey _decryptionKey;
|
||||
private final PrivateKey _decryptionKeyEC;
|
||||
private final PrivateKey _decryptionKeyPQ;
|
||||
|
||||
/**
|
||||
* Unmodifiable, ElGamal only
|
||||
@@ -43,6 +44,41 @@ public class LeaseSetKeys {
|
||||
*/
|
||||
public static final Set<EncType> SET_BOTH = Collections.unmodifiableSet(EnumSet.of(EncType.ELGAMAL_2048, EncType.ECIES_X25519));
|
||||
private static final Set<EncType> SET_NONE = Collections.emptySet();
|
||||
/**
|
||||
* Unmodifiable, PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM512_X25519));
|
||||
/**
|
||||
* Unmodifiable, PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM768_X25519));
|
||||
/**
|
||||
* Unmodifiable, PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM1024_X25519));
|
||||
/**
|
||||
* Unmodifiable, ECIES-X25519 and PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_EC_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519));
|
||||
/**
|
||||
* Unmodifiable, ECIES-X25519 and PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_EC_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM768_X25519));
|
||||
/**
|
||||
* Unmodifiable, ECIES-X25519 and PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_EC_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM1024_X25519));
|
||||
/**
|
||||
* Unmodifiable, ECIES-X25519 and PQ only
|
||||
* @since public since 0.9.80
|
||||
*/
|
||||
public static final Set<EncType> SET_EC_PQ_ALL = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519, EncType.MLKEM768_X25519, EncType.MLKEM1024_X25519));
|
||||
|
||||
/**
|
||||
* Client with a single key
|
||||
@@ -57,9 +93,15 @@ public class LeaseSetKeys {
|
||||
if (type == EncType.ELGAMAL_2048) {
|
||||
_decryptionKey = decryptionKey;
|
||||
_decryptionKeyEC = null;
|
||||
_decryptionKeyPQ = null;
|
||||
} else if (type == EncType.ECIES_X25519) {
|
||||
_decryptionKey = null;
|
||||
_decryptionKeyEC = decryptionKey;
|
||||
_decryptionKeyPQ = null;
|
||||
} else if (type.isPQ()) {
|
||||
_decryptionKey = null;
|
||||
_decryptionKeyEC =null;
|
||||
_decryptionKeyPQ = decryptionKey;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown type " + type);
|
||||
}
|
||||
@@ -68,9 +110,13 @@ public class LeaseSetKeys {
|
||||
/**
|
||||
* Client with multiple keys
|
||||
*
|
||||
* The ONLY valid combinations are X25519 + ElG or X25519 + (MLKEM512 OR MLKEM768 OR MLKEM1024).
|
||||
* Other combinations will throw IllegalArgumentException.
|
||||
*
|
||||
* @param dest unused
|
||||
* @param revocationKey unused, may be null
|
||||
* @param decryptionKeys non-null, non-empty
|
||||
* @throws IllegalArgumentException
|
||||
* @since 0.9.44
|
||||
*/
|
||||
public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, List<PrivateKey> decryptionKeys) {
|
||||
@@ -79,22 +125,32 @@ public class LeaseSetKeys {
|
||||
_revocationKey = revocationKey;
|
||||
PrivateKey elg = null;
|
||||
PrivateKey ec = null;
|
||||
PrivateKey pq = null;
|
||||
for (PrivateKey pk : decryptionKeys) {
|
||||
EncType type = pk.getType();
|
||||
if (type == EncType.ELGAMAL_2048) {
|
||||
if (elg != null)
|
||||
throw new IllegalArgumentException("Multiple keys same type");
|
||||
if (pq != null)
|
||||
throw new IllegalArgumentException("Invalid combination ElG + PQ");
|
||||
elg = pk;
|
||||
} else if (type == EncType.ECIES_X25519) {
|
||||
if (ec != null)
|
||||
throw new IllegalArgumentException("Multiple keys same type");
|
||||
ec = pk;
|
||||
} else if (type.isPQ()) {
|
||||
if (pq != null)
|
||||
throw new IllegalArgumentException("Multiple keys same type");
|
||||
if (elg != null)
|
||||
throw new IllegalArgumentException("Invalid combination ElG + PQ");
|
||||
pq = pk;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown type " + type);
|
||||
}
|
||||
}
|
||||
_decryptionKey = elg;
|
||||
_decryptionKeyEC = ec;
|
||||
_decryptionKeyPQ = pq;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,9 +184,17 @@ public class LeaseSetKeys {
|
||||
return _decryptionKey;
|
||||
if (type == EncType.ECIES_X25519)
|
||||
return _decryptionKeyEC;
|
||||
if (type.isPQ() && _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type)
|
||||
return _decryptionKeyPQ;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PQ key (any type) or null if the LS does not support PQ
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public PrivateKey getPQDecryptionKey() { return _decryptionKeyPQ; }
|
||||
|
||||
/**
|
||||
* Do we support this type of encryption?
|
||||
*
|
||||
@@ -141,6 +205,8 @@ public class LeaseSetKeys {
|
||||
return _decryptionKey != null;
|
||||
if (type == EncType.ECIES_X25519)
|
||||
return _decryptionKeyEC != null;
|
||||
if (type.isPQ())
|
||||
return _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -152,6 +218,27 @@ public class LeaseSetKeys {
|
||||
public Set<EncType> getSupportedEncryption() {
|
||||
if (_decryptionKey != null)
|
||||
return (_decryptionKeyEC != null) ? SET_BOTH : SET_ELG;
|
||||
if (_decryptionKeyPQ != null) {
|
||||
if (_decryptionKeyEC != null) {
|
||||
switch (_decryptionKeyPQ.getType()) {
|
||||
case MLKEM512_X25519:
|
||||
return SET_EC_PQ1;
|
||||
case MLKEM768_X25519:
|
||||
return SET_EC_PQ2;
|
||||
case MLKEM1024_X25519:
|
||||
return SET_EC_PQ3;
|
||||
}
|
||||
} else {
|
||||
switch (_decryptionKeyPQ.getType()) {
|
||||
case MLKEM512_X25519:
|
||||
return SET_PQ1;
|
||||
case MLKEM768_X25519:
|
||||
return SET_PQ2;
|
||||
case MLKEM1024_X25519:
|
||||
return SET_PQ3;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (_decryptionKeyEC != null) ? SET_EC : SET_NONE;
|
||||
}
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
@@ -597,6 +598,8 @@ class ClientConnectionRunner {
|
||||
int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
|
||||
boolean hasElg = false;
|
||||
boolean hasEC = false;
|
||||
boolean hasPQ = false;
|
||||
int pqType = 0;
|
||||
// router may be null in unit tests, avoid NPEs in ratchet
|
||||
// we won't actually be using any SKM anyway
|
||||
if (opts != null && _context.router() != null) {
|
||||
@@ -612,10 +615,18 @@ class ClientConnectionRunner {
|
||||
if (senc != null) {
|
||||
String[] senca = DataHelper.split(senc, ",");
|
||||
for (String sencaa : senca) {
|
||||
if (sencaa.equals("0"))
|
||||
if (sencaa.equals("0")) {
|
||||
hasElg = true;
|
||||
else if (sencaa.equals("4"))
|
||||
} else if (sencaa.equals("4")) {
|
||||
hasEC = true;
|
||||
} else if (sencaa.equals("5") || sencaa.equals("6") || sencaa.equals("7")) {
|
||||
if (hasPQ) {
|
||||
_log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32());
|
||||
return SessionStatusMessage.STATUS_INVALID;
|
||||
}
|
||||
pqType = Integer.parseInt(sencaa);
|
||||
hasPQ = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasElg = true;
|
||||
@@ -624,6 +635,10 @@ class ClientConnectionRunner {
|
||||
hasElg = true;
|
||||
}
|
||||
if (hasElg) {
|
||||
if (hasPQ) {
|
||||
_log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32());
|
||||
return SessionStatusMessage.STATUS_INVALID;
|
||||
}
|
||||
TransientSessionKeyManager tskm = new TransientSessionKeyManager(_context, tags, thresh);
|
||||
if (hasEC) {
|
||||
RatchetSKM rskm = new RatchetSKM(_context, dest);
|
||||
@@ -631,6 +646,15 @@ class ClientConnectionRunner {
|
||||
} else {
|
||||
_sessionKeyManager = tskm;
|
||||
}
|
||||
} else if (hasPQ) {
|
||||
// fixmeeeeeeeeeeeeeeeeeeeeeee
|
||||
if (hasEC) {
|
||||
RatchetSKM rskm1 = new RatchetSKM(_context, dest);
|
||||
RatchetSKM rskm2 = new RatchetSKM(_context, dest);
|
||||
_sessionKeyManager = new MuxedPQSKM(rskm1, rskm2);
|
||||
} else {
|
||||
_sessionKeyManager = new RatchetSKM(_context, dest);
|
||||
}
|
||||
} else {
|
||||
if (hasEC) {
|
||||
_sessionKeyManager = new RatchetSKM(_context, dest);
|
||||
|
@@ -17,11 +17,14 @@ import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.EncryptedLeaseSet;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.KeyCertificate;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.LeaseSet2;
|
||||
import net.i2p.data.Payload;
|
||||
@@ -226,9 +229,10 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
private void handleCreateSession(CreateSessionMessage message) {
|
||||
SessionConfig in = message.getSessionConfig();
|
||||
Destination dest = in.getDestination();
|
||||
if (dest.getEncType() != EncType.ELGAMAL_2048) {
|
||||
EncType etype = dest.getEncType();
|
||||
if (etype != EncType.ELGAMAL_2048 && etype != EncType.NONE) {
|
||||
// Enc type in key cert, proposal 145, unsupported
|
||||
_runner.disconnectClient("Non-ElGamal encryption type in key certificate unsupported");
|
||||
_runner.disconnectClient("Encryption type must be ElGamal or NULL");
|
||||
return;
|
||||
}
|
||||
if (in.verifySignature()) {
|
||||
@@ -236,8 +240,16 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_log.debug("Signature verified correctly on create session message");
|
||||
} else {
|
||||
// For now, we do NOT send a SessionStatusMessage - see javadoc above
|
||||
int itype = dest.getCertificate().getCertificateType();
|
||||
SigType stype = SigType.getByCode(itype);
|
||||
SigType stype = SigType.DSA_SHA1;
|
||||
int itype = stype.getCode();
|
||||
Certificate cert = dest.getCertificate();
|
||||
if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
try {
|
||||
KeyCertificate kcert = cert.toKeyCertificate();
|
||||
itype = kcert.getSigTypeCode();
|
||||
stype = SigType.getByCode(itype);
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
if (stype == null || !stype.isAvailable()) {
|
||||
_log.error("Client requested unsupported signature type " + itype);
|
||||
_runner.disconnectClient("Unsupported signature type " + itype);
|
||||
@@ -261,6 +273,14 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
return;
|
||||
}
|
||||
|
||||
SigType stype = dest.getSigType();
|
||||
if ((stype.isPQ() && etype != EncType.NONE) ||
|
||||
(!stype.isPQ() && etype != EncType.ELGAMAL_2048)) {
|
||||
String msg = "Invalid combination of enc. type: " + etype + " and sig. type: " + stype;
|
||||
_log.error(msg);
|
||||
_runner.disconnectClient(msg);
|
||||
}
|
||||
|
||||
// Auth, since 0.8.2
|
||||
Properties inProps = in.getOptions();
|
||||
if (!checkAuth(inProps))
|
||||
@@ -311,7 +331,6 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
}
|
||||
String lsType = props.getProperty("i2cp.leaseSetType");
|
||||
if ("5".equals(lsType)) {
|
||||
SigType stype = dest.getSigningPublicKey().getType();
|
||||
if (stype != SigType.EdDSA_SHA512_Ed25519 &&
|
||||
stype != SigType.RedDSA_SHA512_Ed25519) {
|
||||
_runner.disconnectClient("Invalid sig type for encrypted LS");
|
||||
|
234
router/java/src/net/i2p/router/crypto/pqc/MLKEM.java
Normal file
234
router/java/src/net/i2p/router/crypto/pqc/MLKEM.java
Normal file
@@ -0,0 +1,234 @@
|
||||
package net.i2p.router.crypto.pqc;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.SecretWithEncapsulation;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMExtractor;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMGenerator;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyGenerationParameters;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyPairGenerator;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EncAlgo;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.KeyFactory;
|
||||
import net.i2p.crypto.KeyPair;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Wrapper around bouncycastle
|
||||
*
|
||||
* @since 0.9.69
|
||||
*/
|
||||
public final class MLKEM {
|
||||
|
||||
/** all non-threaded for now */
|
||||
public static final KeyFactory MLKEM512KeyFactory = new MLKEMFactory(EncType.MLKEM512_X25519_INT);
|
||||
public static final KeyFactory MLKEM768KeyFactory = new MLKEMFactory(EncType.MLKEM768_X25519_INT);
|
||||
public static final KeyFactory MLKEM1024KeyFactory = new MLKEMFactory(EncType.MLKEM1024_X25519_INT);
|
||||
|
||||
private static class MLKEMFactory implements KeyFactory {
|
||||
private final EncType t;
|
||||
public MLKEMFactory(EncType type) { t = type; }
|
||||
public KeyPair getKeys() {
|
||||
try {
|
||||
return MLKEM.getKeys(t);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalStateException(gse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice side
|
||||
* @param type must be one of the internal types MLKEM*_INT
|
||||
* @return encapkey decapkey
|
||||
*/
|
||||
public static KeyPair getKeys(EncType type) throws GeneralSecurityException {
|
||||
byte[][] keys = generateKeys(type);
|
||||
PublicKey pub = new PublicKey(type, keys[0]);
|
||||
PrivateKey priv = new PrivateKey(type, keys[1]);
|
||||
return new KeyPair(pub, priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice side
|
||||
* @param type must be one of the internal types MLKEM*_INT
|
||||
* @return encapkey decapkey
|
||||
*/
|
||||
public static byte[][] generateKeys(EncType type) throws GeneralSecurityException {
|
||||
MLKEMParameters param = getParam(type);
|
||||
MLKEMKeyPairGenerator kpg = new MLKEMKeyPairGenerator();
|
||||
kpg.init(new MLKEMKeyGenerationParameters(RandomSource.getInstance(), param));
|
||||
AsymmetricCipherKeyPair pair = kpg.generateKeyPair();
|
||||
MLKEMPublicKeyParameters pubkey = (MLKEMPublicKeyParameters) pair.getPublic();
|
||||
MLKEMPrivateKeyParameters privkey = (MLKEMPrivateKeyParameters) pair.getPrivate();
|
||||
byte[][] keys = new byte[2][];
|
||||
keys[0] = pubkey.getEncoded();
|
||||
keys[1] = privkey.getEncoded();
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bob side
|
||||
* @return ciphertext sharedkey, non-null
|
||||
*/
|
||||
public static byte[][] encaps(EncType type, byte[] pub)
|
||||
throws GeneralSecurityException {
|
||||
MLKEMParameters param = getParam(type);
|
||||
MLKEMGenerator gen = new MLKEMGenerator(I2PAppContext.getGlobalContext().random());
|
||||
MLKEMPublicKeyParameters ppub = new MLKEMPublicKeyParameters(param, pub);
|
||||
SecretWithEncapsulation swe;
|
||||
try {
|
||||
swe = gen.generateEncapsulated(ppub);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new GeneralSecurityException(iae);
|
||||
}
|
||||
byte[][] keys = new byte[2][];
|
||||
keys[0] = swe.getEncapsulation();
|
||||
keys[1] = swe.getSecret();
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice side
|
||||
* Note that this will not fail???
|
||||
*
|
||||
* @return sharedkey, 32 bytes, non-null
|
||||
*/
|
||||
public static byte[] decaps(EncType type, byte[] ciphertext, byte[] decapkey)
|
||||
throws GeneralSecurityException {
|
||||
MLKEMParameters param = getParam(type);
|
||||
MLKEMPrivateKeyParameters priv = new MLKEMPrivateKeyParameters(param, decapkey);
|
||||
MLKEMExtractor ext = new MLKEMExtractor(priv);
|
||||
// todo check for "implicit rejection" ?
|
||||
return ext.extractSecret(ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* EncType to params
|
||||
*/
|
||||
private static MLKEMParameters getParam(EncType type) throws GeneralSecurityException {
|
||||
switch(type) {
|
||||
case MLKEM512_X25519_INT:
|
||||
case MLKEM512_X25519_CT:
|
||||
return MLKEMParameters.ml_kem_512;
|
||||
|
||||
case MLKEM768_X25519_INT:
|
||||
case MLKEM768_X25519_CT:
|
||||
return MLKEMParameters.ml_kem_768;
|
||||
|
||||
case MLKEM1024_X25519_INT:
|
||||
case MLKEM1024_X25519_CT:
|
||||
return MLKEMParameters.ml_kem_1024;
|
||||
|
||||
default:
|
||||
throw new InvalidKeyException("unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage: MLKEM [enctype...]
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
main2(args);
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage: MLKEM [enctype...]
|
||||
*/
|
||||
private static void main2(String args[]) {
|
||||
RandomSource.getInstance().nextBoolean();
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
int runs = 200; // warmup
|
||||
java.util.Collection<EncType> toTest;
|
||||
if (args.length > 0) {
|
||||
toTest = new java.util.ArrayList<EncType>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
EncType type = EncType.parseEncType(args[i]);
|
||||
if (type != null)
|
||||
toTest.add(type);
|
||||
else
|
||||
System.out.println("Unknown type: " + args[i]);
|
||||
}
|
||||
if (toTest.isEmpty()) {
|
||||
System.out.println("No types to test");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
toTest = java.util.Arrays.asList(EncType.values());
|
||||
}
|
||||
for (int j = 0; j < 2; j++) {
|
||||
for (EncType type : toTest) {
|
||||
if (type.getBaseAlgorithm() != EncAlgo.ECIES_MLKEM)
|
||||
continue;
|
||||
if (!type.isAvailable()) {
|
||||
System.out.println("Skipping unavailable: " + type);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
System.out.println("Testing " + type);
|
||||
testEnc(type, runs);
|
||||
} catch (GeneralSecurityException e) {
|
||||
System.out.println("error testing " + type);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
runs = 1000;
|
||||
}
|
||||
}
|
||||
|
||||
private static void testEnc(EncType type, int runs) throws GeneralSecurityException {
|
||||
double gtime = 0;
|
||||
long stime = 0;
|
||||
long vtime = 0;
|
||||
byte[][] keys = null;
|
||||
long st = System.nanoTime();
|
||||
for (int i = 0; i < runs; i++) {
|
||||
keys = generateKeys(type);
|
||||
}
|
||||
long en = System.nanoTime();
|
||||
gtime = ((en - st) / (1000*1000d)) / runs;
|
||||
System.out.println(type + " key gen " + runs + " times: " + gtime + " ms each");
|
||||
byte[] pubkey = keys[0];
|
||||
byte[] privkey = keys[1];
|
||||
|
||||
//System.out.println("privkey " + keys[1]);
|
||||
for (int i = 0; i < runs; i++) {
|
||||
long start = System.nanoTime();
|
||||
byte[][] bob = encaps(type, pubkey);
|
||||
byte[] ct = bob[0];
|
||||
byte[] s1 = bob[1];
|
||||
long mid = System.nanoTime();
|
||||
byte[] s2 = decaps(type, ct, privkey);
|
||||
boolean ok = DataHelper.eq(s1, s2);
|
||||
long end = System.nanoTime();
|
||||
stime += mid - start;
|
||||
vtime += end - mid;
|
||||
if (!ok)
|
||||
throw new GeneralSecurityException(type + " shared key fail on run " + i);
|
||||
}
|
||||
stime /= 1000*1000;
|
||||
vtime /= 1000*1000;
|
||||
// we do two of each per run above
|
||||
int runs2 = runs * 2;
|
||||
System.out.println(type + " encap/decap " + runs + " times: " + (vtime+stime) + " ms = " +
|
||||
(((double) stime) / runs2) + " each encap, " +
|
||||
(((double) vtime) / runs2) + " each decap, " +
|
||||
(((double) (stime + vtime)) / runs2) + " d+e");
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ import com.southernstorm.noise.protocol.HandshakeState;
|
||||
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.HKDF;
|
||||
import net.i2p.crypto.KeyFactory;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Certificate;
|
||||
@@ -33,6 +34,7 @@ import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.GarlicClove;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.crypto.pqc.MLKEM;
|
||||
import static net.i2p.router.crypto.ratchet.RatchetPayload.*;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.Router;
|
||||
@@ -53,6 +55,7 @@ public final class ECIESAEADEngine {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final MuxedEngine _muxedEngine;
|
||||
private final MuxedPQEngine _muxedPQEngine;
|
||||
private final HKDF _hkdf;
|
||||
private final Elg2KeyFactory _edhThread;
|
||||
private boolean _isRunning;
|
||||
@@ -86,6 +89,18 @@ public final class ECIESAEADEngine {
|
||||
private static final String INFO_0 = "SessionReplyTags";
|
||||
private static final String INFO_6 = "AttachPayloadKDF";
|
||||
|
||||
// These are the min sizes for the MLKEM New Session Message.
|
||||
// It contains an extra MLKEM key and MAC.
|
||||
// 112
|
||||
private static final int NS_MLKEM_OVERHEAD = NS_OVERHEAD + MACLEN;
|
||||
// 800 + 112 + 7 = 919
|
||||
private static final int MIN_NS_MLKEM512_SIZE = EncType.MLKEM512_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
|
||||
// 1184 + 112 + 7 = 1303
|
||||
private static final int MIN_NS_MLKEM768_SIZE = EncType.MLKEM768_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
|
||||
// 1568 + 112 + 7 = 1687
|
||||
private static final int MIN_NS_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
|
||||
|
||||
|
||||
/**
|
||||
* Caller MUST call startup() to get threaded generation.
|
||||
* Will still work without, will just generate inline.
|
||||
@@ -96,6 +111,7 @@ public final class ECIESAEADEngine {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(ECIESAEADEngine.class);
|
||||
_muxedEngine = new MuxedEngine(ctx);
|
||||
_muxedPQEngine = new MuxedPQEngine(ctx);
|
||||
_hkdf = new HKDF(ctx);
|
||||
_edhThread = new Elg2KeyFactory(ctx);
|
||||
|
||||
@@ -147,6 +163,18 @@ public final class ECIESAEADEngine {
|
||||
return _muxedEngine.decrypt(data, elgKey, ecKey, keyManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to decrypt the message with one or both of the given private keys
|
||||
*
|
||||
* @param ecKey must be EC, non-null
|
||||
* @param pqKey must be PQ, non-null
|
||||
* @return decrypted data or null on failure
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException {
|
||||
return _muxedPQEngine.decrypt(data, ecKey, pqKey, keyManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the message using the given private key
|
||||
* and using tags from the specified key manager.
|
||||
@@ -175,8 +203,7 @@ public final class ECIESAEADEngine {
|
||||
|
||||
private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey,
|
||||
RatchetSKM keyManager) throws DataFormatException {
|
||||
if (targetPrivateKey.getType() != EncType.ECIES_X25519)
|
||||
throw new IllegalArgumentException();
|
||||
checkType(targetPrivateKey.getType());
|
||||
if (data == null) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
|
||||
return null;
|
||||
@@ -316,8 +343,26 @@ public final class ECIESAEADEngine {
|
||||
private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey,
|
||||
RatchetSKM keyManager) throws DataFormatException {
|
||||
CloveSet decrypted;
|
||||
int minns;
|
||||
switch(targetPrivateKey.getType()) {
|
||||
case ECIES_X25519:
|
||||
minns = MIN_NS_SIZE;
|
||||
break;
|
||||
case MLKEM512_X25519:
|
||||
minns = MIN_NS_MLKEM512_SIZE;
|
||||
break;
|
||||
case MLKEM768_X25519:
|
||||
minns = MIN_NS_MLKEM768_SIZE;
|
||||
break;
|
||||
case MLKEM1024_X25519:
|
||||
minns = MIN_NS_MLKEM1024_SIZE;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
}
|
||||
boolean isRouter = keyManager.getDestination() == null;
|
||||
if (data.length >= MIN_NS_SIZE || (isRouter && data.length >= MIN_NS_N_SIZE)) {
|
||||
if (data.length >= minns || (isRouter && data.length >= MIN_NS_N_SIZE)) {
|
||||
if (isRouter)
|
||||
decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager);
|
||||
else
|
||||
@@ -338,6 +383,56 @@ public final class ECIESAEADEngine {
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if unsupported
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static void checkType(EncType type) {
|
||||
switch(type) {
|
||||
case ECIES_X25519:
|
||||
case MLKEM512_X25519:
|
||||
case MLKEM768_X25519:
|
||||
case MLKEM1024_X25519:
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported key type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static String getNoisePattern(EncType type) {
|
||||
switch(type) {
|
||||
case ECIES_X25519:
|
||||
return HandshakeState.PATTERN_ID_IK;
|
||||
case MLKEM512_X25519:
|
||||
return HandshakeState.PATTERN_ID_IKHFS_512;
|
||||
case MLKEM768_X25519:
|
||||
return HandshakeState.PATTERN_ID_IKHFS_768;
|
||||
case MLKEM1024_X25519:
|
||||
return HandshakeState.PATTERN_ID_IKHFS_1024;
|
||||
default:
|
||||
throw new IllegalArgumentException("No pattern for " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.80
|
||||
*/
|
||||
private static KeyFactory getHybridKeyFactory(EncType type) {
|
||||
switch(type) {
|
||||
case MLKEM512_X25519:
|
||||
return MLKEM.MLKEM512KeyFactory;
|
||||
case MLKEM768_X25519:
|
||||
return MLKEM.MLKEM768KeyFactory;
|
||||
case MLKEM1024_X25519:
|
||||
return MLKEM.MLKEM1024KeyFactory;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scenario 1: New Session Message
|
||||
*
|
||||
@@ -383,8 +478,10 @@ public final class ECIESAEADEngine {
|
||||
System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
|
||||
|
||||
HandshakeState state;
|
||||
EncType type = targetPrivateKey.getType();
|
||||
try {
|
||||
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
|
||||
String pattern = getNoisePattern(type);
|
||||
state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread, getHybridKeyFactory(type));
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalStateException("bad proto", gse);
|
||||
}
|
||||
@@ -395,6 +492,10 @@ public final class ECIESAEADEngine {
|
||||
_log.debug("State before decrypt new session: " + state);
|
||||
|
||||
int payloadlen = data.length - (KEYLEN + KEYLEN + MACLEN + MACLEN);
|
||||
DHState hyb = state.getRemoteHybridKeyPair();
|
||||
if (hyb != null) {
|
||||
payloadlen -= hyb.getPublicKeyLength() + MACLEN;
|
||||
}
|
||||
byte[] payload = new byte[payloadlen];
|
||||
try {
|
||||
state.readMessage(data, 0, data.length, payload, 0);
|
||||
@@ -638,8 +739,13 @@ public final class ECIESAEADEngine {
|
||||
state.mixHash(tag, 0, TAGLEN);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after mixhash tag before decrypt new session reply: " + state);
|
||||
int tmplen = 48;
|
||||
DHState hyb = state.getRemoteHybridKeyPair();
|
||||
if (hyb != null) {
|
||||
tmplen += hyb.getPublicKeyLength() + MACLEN;
|
||||
}
|
||||
try {
|
||||
state.readMessage(data, 8, 48, ZEROLEN, 0);
|
||||
state.readMessage(data, 8, tmplen, ZEROLEN, 0);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldWarn()) {
|
||||
_log.warn("Decrypt fail NSR part 1", gse);
|
||||
@@ -872,8 +978,7 @@ public final class ECIESAEADEngine {
|
||||
private byte[] x_encrypt(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
||||
RatchetSKM keyManager,
|
||||
ReplyCallback callback) {
|
||||
if (target.getType() != EncType.ECIES_X25519)
|
||||
throw new IllegalArgumentException();
|
||||
checkType(target.getType());
|
||||
if (Arrays.equals(target.getData(), NULLPK)) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Zero static key target");
|
||||
@@ -943,9 +1048,13 @@ public final class ECIESAEADEngine {
|
||||
private byte[] encryptNewSession(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
||||
RatchetSKM keyManager,
|
||||
ReplyCallback callback) {
|
||||
EncType type = target.getType();
|
||||
if (type != priv.getType())
|
||||
throw new IllegalArgumentException("Key mismatch " + target + ' ' + priv);
|
||||
HandshakeState state;
|
||||
try {
|
||||
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread);
|
||||
String pattern = getNoisePattern(target.getType());
|
||||
state = new HandshakeState(pattern, HandshakeState.INITIATOR, _edhThread, getHybridKeyFactory(type));
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalStateException("bad proto", gse);
|
||||
}
|
||||
@@ -962,7 +1071,12 @@ public final class ECIESAEADEngine {
|
||||
|
||||
byte[] payload = createPayload(cloves, cloves.getExpiration(), NS_OVERHEAD);
|
||||
|
||||
byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
||||
int enclen = KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN;
|
||||
DHState hyb = state.getLocalHybridKeyPair();
|
||||
if (hyb != null) {
|
||||
enclen += hyb.getPublicKeyLength() + MACLEN;
|
||||
}
|
||||
byte[] enc = new byte[enclen];
|
||||
try {
|
||||
state.writeMessage(enc, 0, payload, 0, payload.length);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@@ -1071,7 +1185,12 @@ public final class ECIESAEADEngine {
|
||||
byte[] payload = createPayload(cloves, 0, NSR_OVERHEAD);
|
||||
|
||||
// part 1 - tag and empty payload
|
||||
byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
||||
int enclen = TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN;
|
||||
DHState hyb = state.getLocalHybridKeyPair();
|
||||
if (hyb != null) {
|
||||
enclen += hyb.getPublicKeyLength() + MACLEN;
|
||||
}
|
||||
byte[] enc = new byte[enclen];
|
||||
System.arraycopy(tag, 0, enc, 0, TAGLEN);
|
||||
try {
|
||||
state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0);
|
||||
|
@@ -0,0 +1,96 @@
|
||||
package net.i2p.router.crypto.ratchet;
|
||||
|
||||
import net.i2p.crypto.EncAlgo;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.message.CloveSet;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handles the actual decryption using the
|
||||
* supplied keys and data.
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
final class MuxedPQEngine {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
|
||||
public MuxedPQEngine(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(MuxedPQEngine.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the message with the given private keys
|
||||
*
|
||||
* @param ecKey must be EC, non-null
|
||||
* @param pqKey must be PQ, non-null
|
||||
* @return decrypted data or null on failure
|
||||
*/
|
||||
public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException {
|
||||
if (ecKey.getType() != EncType.ECIES_X25519 ||
|
||||
pqKey.getType().getBaseAlgorithm() != EncAlgo.ECIES_MLKEM)
|
||||
throw new IllegalArgumentException();
|
||||
final boolean debug = _log.shouldDebug();
|
||||
CloveSet rv = null;
|
||||
// Try in-order from fastest to slowest
|
||||
boolean preferRatchet = keyManager.preferRatchet();
|
||||
if (preferRatchet) {
|
||||
// Ratchet Tag
|
||||
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
|
||||
if (rv != null)
|
||||
return rv;
|
||||
if (debug)
|
||||
_log.debug("Ratchet tag not found before PQ");
|
||||
}
|
||||
// PQ
|
||||
// Ratchet Tag
|
||||
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
|
||||
if (rv != null)
|
||||
return rv;
|
||||
if (debug)
|
||||
_log.debug("PQ tag not found");
|
||||
if (!preferRatchet) {
|
||||
// Ratchet Tag
|
||||
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
|
||||
if (rv != null)
|
||||
return rv;
|
||||
if (debug)
|
||||
_log.debug("Ratchet tag not found after PQ");
|
||||
}
|
||||
|
||||
if (preferRatchet) {
|
||||
// Ratchet DH
|
||||
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
|
||||
boolean ok = rv != null;
|
||||
keyManager.reportDecryptResult(true, ok);
|
||||
if (ok)
|
||||
return rv;
|
||||
if (debug)
|
||||
_log.debug("Ratchet NS decrypt failed before PQ");
|
||||
}
|
||||
|
||||
// PQ DH
|
||||
// Minimum size checks for the larger New Session message are in ECIESAEADEngine.x_decryptSlow().
|
||||
rv = _context.eciesEngine().decryptSlow(data, pqKey, keyManager.getECSKM());
|
||||
boolean isok = rv != null;
|
||||
keyManager.reportDecryptResult(false, isok);
|
||||
if (isok)
|
||||
return rv;
|
||||
if (debug)
|
||||
_log.debug("PQ NS decrypt failed");
|
||||
|
||||
if (!preferRatchet) {
|
||||
// Ratchet DH
|
||||
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
|
||||
boolean ok = rv != null;
|
||||
keyManager.reportDecryptResult(true, ok);
|
||||
if (!ok && debug)
|
||||
_log.debug("Ratchet NS decrypt failed after PQ");
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
229
router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java
Normal file
229
router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java
Normal file
@@ -0,0 +1,229 @@
|
||||
package net.i2p.router.crypto.ratchet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.TagSetHandle;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
|
||||
/**
|
||||
* Both EC and PQ
|
||||
*
|
||||
* @since 0.9.80
|
||||
*/
|
||||
public class MuxedPQSKM extends SessionKeyManager {
|
||||
|
||||
private final RatchetSKM _ec;
|
||||
private final RatchetSKM _pq;
|
||||
private final AtomicInteger _ecCounter = new AtomicInteger();
|
||||
private final AtomicInteger _pqCounter = new AtomicInteger();
|
||||
// PQ is about this much slower than EC
|
||||
private static final int PQ_SLOW_FACTOR = 2;
|
||||
private static final int RESTART_COUNTERS = 500;
|
||||
|
||||
public MuxedPQSKM(RatchetSKM ec, RatchetSKM pq) {
|
||||
_ec = ec;
|
||||
_pq = pq;
|
||||
}
|
||||
|
||||
public RatchetSKM getECSKM() { return _ec; }
|
||||
|
||||
public RatchetSKM getPQSKM() { return _pq; }
|
||||
|
||||
/**
|
||||
* Should we try the Ratchet slow decrypt before PQ slow decrypt?
|
||||
* Adaptive test based on previous mix of traffic for this SKM,
|
||||
* as reported by reportDecryptResult().
|
||||
*
|
||||
* @since 0.9.46
|
||||
*/
|
||||
boolean preferRatchet() {
|
||||
int ec = _ecCounter.get();
|
||||
int pq = _pqCounter.get();
|
||||
if (ec > RESTART_COUNTERS / 10 &&
|
||||
pq > RESTART_COUNTERS / 10 &&
|
||||
ec + pq > RESTART_COUNTERS) {
|
||||
_ecCounter.set(0);
|
||||
_pqCounter.set(0);
|
||||
return true;
|
||||
}
|
||||
return ec >= pq / PQ_SLOW_FACTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the result of a slow decrypt attempt.
|
||||
*
|
||||
* @param isRatchet true for EC, false for PQ
|
||||
* @param success true for successful decrypt
|
||||
* @since 0.9.46
|
||||
*/
|
||||
void reportDecryptResult(boolean isRatchet, boolean success) {
|
||||
if (success) {
|
||||
if (isRatchet)
|
||||
_ecCounter.incrementAndGet();
|
||||
else
|
||||
_pqCounter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public SessionKey getCurrentKey(PublicKey target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public SessionKey getCurrentOrNewKey(PublicKey target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public void createSession(PublicKey target, SessionKey key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public SessionKey createSession(PublicKey target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* EC only
|
||||
*/
|
||||
public RatchetEntry consumeNextAvailableTag(PublicKey target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTagsToSend() { return 0; };
|
||||
|
||||
@Override
|
||||
public int getLowThreshold() { return 0; };
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldSendTags(PublicKey target, SessionKey key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldSendTags(PublicKey target, SessionKey key, int lowThreshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailableTags(PublicKey target, SessionKey key) {
|
||||
EncType type = target.getType();
|
||||
if (type == EncType.ECIES_X25519)
|
||||
return _ec.getAvailableTags(target, key);
|
||||
else
|
||||
return _pq.getAvailableTags(target, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAvailableTimeLeft(PublicKey target, SessionKey key) {
|
||||
EncType type = target.getType();
|
||||
if (type == EncType.ECIES_X25519)
|
||||
return _ec.getAvailableTimeLeft(target, key);
|
||||
else
|
||||
return _pq.getAvailableTimeLeft(target, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags) {
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags, long expire) {
|
||||
}
|
||||
|
||||
/**
|
||||
* EC only
|
||||
* One time session
|
||||
* @param expire time from now
|
||||
* @since 0.9.51
|
||||
*/
|
||||
public void tagsReceived(SessionKey key, RatchetSessionTag tag, long expire) {
|
||||
_ec.tagsReceived(key, tag, expire);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionKey consumeTag(SessionTag tag) {
|
||||
RatchetSessionTag rstag = new RatchetSessionTag(tag.getData());
|
||||
SessionKey rv = _ec.consumeTag(rstag);
|
||||
if (rv == null) {
|
||||
rv = _pq.consumeTag(rstag);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
_ec.shutdown();
|
||||
_pq.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderStatusHTML(Writer out) throws IOException {
|
||||
_ec.renderStatusHTML(out);
|
||||
_pq.renderStatusHTML(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* ElG only
|
||||
*/
|
||||
@Override
|
||||
public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) {
|
||||
}
|
||||
}
|
@@ -31,6 +31,7 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.DecayingHashSet;
|
||||
import net.i2p.util.Log;
|
||||
@@ -201,7 +202,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
||||
*/
|
||||
boolean createSession(PublicKey target, Destination d, HandshakeState state, ReplyCallback callback) {
|
||||
EncType type = target.getType();
|
||||
if (type != EncType.ECIES_X25519)
|
||||
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(type))
|
||||
throw new IllegalArgumentException("Bad public key type " + type);
|
||||
OutboundSession sess = new OutboundSession(target, d, null, state, callback);
|
||||
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
||||
@@ -247,7 +248,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
||||
boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state,
|
||||
ReplyCallback callback, SplitKeys split) {
|
||||
EncType type = target.getType();
|
||||
if (type != EncType.ECIES_X25519)
|
||||
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(type))
|
||||
throw new IllegalArgumentException("Bad public key type " + type);
|
||||
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
||||
if (isInbound) {
|
||||
|
@@ -32,6 +32,7 @@ import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||
@@ -46,13 +47,13 @@ import net.i2p.util.Log;
|
||||
public class GarlicMessageBuilder {
|
||||
|
||||
/**
|
||||
* ELGAMAL_2048 only.
|
||||
* ELGAMAL_2048 only; returns false for others
|
||||
*
|
||||
* @param local non-null; do not use this method for the router's SessionKeyManager
|
||||
* @param minTagOverride 0 for no override, > 0 to override SKM's settings
|
||||
*/
|
||||
static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) {
|
||||
if (key.getType() == EncType.ECIES_X25519)
|
||||
if (LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
|
||||
return false;
|
||||
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
|
||||
if (skm == null)
|
||||
@@ -275,7 +276,7 @@ public class GarlicMessageBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* ECIES_X25519 only.
|
||||
* ECIES_X25519 and PQ only.
|
||||
* Called by OCMJH only.
|
||||
*
|
||||
* @param ctx scope
|
||||
@@ -289,7 +290,8 @@ public class GarlicMessageBuilder {
|
||||
Hash from, Destination to, SessionKeyManager skm,
|
||||
ReplyCallback callback) {
|
||||
PublicKey key = config.getRecipientPublicKey();
|
||||
if (key.getType() != EncType.ECIES_X25519)
|
||||
EncType type = key.getType();
|
||||
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(type))
|
||||
throw new IllegalArgumentException();
|
||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||
GarlicMessage msg = new GarlicMessage(ctx);
|
||||
@@ -300,7 +302,7 @@ public class GarlicMessageBuilder {
|
||||
log.warn("No LSK for " + from.toBase32());
|
||||
return null;
|
||||
}
|
||||
PrivateKey priv = lsk.getDecryptionKey(EncType.ECIES_X25519);
|
||||
PrivateKey priv = lsk.getDecryptionKey(type);
|
||||
if (priv == null) {
|
||||
if (log.shouldWarn())
|
||||
log.warn("No key for " + from.toBase32());
|
||||
@@ -312,6 +314,8 @@ public class GarlicMessageBuilder {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
if (log.shouldWarn())
|
||||
log.warn("No SKM for " + from.toBase32());
|
||||
@@ -338,7 +342,7 @@ public class GarlicMessageBuilder {
|
||||
|
||||
/**
|
||||
* Encrypt from an anonymous source.
|
||||
* ECIES_X25519 only.
|
||||
* ECIES_X25519 and PQ only.
|
||||
* Called by MessageWrapper only.
|
||||
*
|
||||
* @param ctx scope
|
||||
@@ -348,7 +352,7 @@ public class GarlicMessageBuilder {
|
||||
*/
|
||||
public static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config) {
|
||||
PublicKey key = config.getRecipientPublicKey();
|
||||
if (key.getType() != EncType.ECIES_X25519)
|
||||
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
|
||||
throw new IllegalArgumentException();
|
||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||
GarlicMessage msg = new GarlicMessage(ctx);
|
||||
|
@@ -19,6 +19,7 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.i2np.GarlicClove;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
import net.i2p.util.Log;
|
||||
@@ -44,7 +45,7 @@ public class GarlicMessageParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports both ELGAMAL_2048 and ECIES_X25519.
|
||||
* Supports ELGAMAL_2048, ECIES_X25519, and PQ
|
||||
*
|
||||
* @param encryptionKey either type
|
||||
* @param skm use tags from this session key manager
|
||||
@@ -65,6 +66,8 @@ public class GarlicMessageParser {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No SKM to decrypt ECIES");
|
||||
@@ -80,6 +83,27 @@ public class GarlicMessageParser {
|
||||
_log.warn("ECIES decrypt fail");
|
||||
return null;
|
||||
}
|
||||
} else if (type.isPQ()) {
|
||||
RatchetSKM rskm;
|
||||
if (skm instanceof RatchetSKM) {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getPQSKM();
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No SKM to decrypt PQ");
|
||||
return null;
|
||||
}
|
||||
CloveSet rv = _context.eciesEngine().decrypt(encData, encryptionKey, rskm);
|
||||
if (rv != null) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("PQ decrypt success, cloves: " + rv.getCloveCount());
|
||||
return rv;
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("PQ decrypt fail");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Can't decrypt with key type " + type);
|
||||
@@ -112,7 +136,7 @@ public class GarlicMessageParser {
|
||||
/**
|
||||
* Supports both ELGAMAL_2048 and ECIES_X25519.
|
||||
*
|
||||
* @param elgKey must be ElG, non-null
|
||||
* @param elgKey must be ElG OR PQ, non-null
|
||||
* @param ecKey must be EC, non-null
|
||||
* @param skm use tags from this session key manager
|
||||
* @return null on error
|
||||
@@ -125,6 +149,10 @@ public class GarlicMessageParser {
|
||||
if (skm instanceof MuxedSKM) {
|
||||
MuxedSKM mskm = (MuxedSKM) skm;
|
||||
rv = _context.eciesEngine().decrypt(encData, elgKey, ecKey, mskm);
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
MuxedPQSKM mskm = (MuxedPQSKM) skm;
|
||||
// EC is first
|
||||
rv = _context.eciesEngine().decrypt(encData, ecKey, elgKey, mskm);
|
||||
} else if (skm instanceof RatchetSKM) {
|
||||
// unlikely, if we have two keys we should have a MuxedSKM
|
||||
RatchetSKM rskm = (RatchetSKM) skm;
|
||||
|
@@ -65,15 +65,24 @@ public class GarlicMessageReceiver {
|
||||
if (keys != null && skm != null) {
|
||||
decryptionKey = keys.getDecryptionKey();
|
||||
decryptionKey2 = keys.getDecryptionKey(EncType.ECIES_X25519);
|
||||
if (decryptionKey == null && decryptionKey2 == null) {
|
||||
// this will return any of the PQ types
|
||||
PrivateKey decryptionKey3 = keys.getPQDecryptionKey();
|
||||
if (decryptionKey == null && decryptionKey2 == null && decryptionKey3 == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No key to decrypt for " + _clientDestination.toBase32());
|
||||
return;
|
||||
}
|
||||
// ElG + PQ disallowed
|
||||
if (decryptionKey == null) {
|
||||
// swap
|
||||
decryptionKey = decryptionKey2;
|
||||
decryptionKey2 = null;
|
||||
if (decryptionKey3 != null) {
|
||||
// PQ first if present
|
||||
decryptionKey = decryptionKey3;
|
||||
} else {
|
||||
// EC only
|
||||
decryptionKey = decryptionKey2;
|
||||
decryptionKey2 = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@@ -124,7 +124,7 @@ class OutboundClientMessageJobHelper {
|
||||
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
|
||||
if (skm == null)
|
||||
return null;
|
||||
boolean isECIES = recipientPK.getType() == EncType.ECIES_X25519;
|
||||
boolean isECIES = recipientPK.getType() != EncType.ELGAMAL_2048;
|
||||
// force ack off if ECIES
|
||||
boolean ackInGarlic = isECIES ? false : requireAck;
|
||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove,
|
||||
|
@@ -280,7 +280,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
public String getName() { return "Outbound client message"; }
|
||||
|
||||
public void runJob() {
|
||||
if (_to.getEncType() != EncType.ELGAMAL_2048) {
|
||||
EncType type = _to.getEncType();
|
||||
if (type != EncType.ELGAMAL_2048 && type != EncType.NONE) {
|
||||
// Enc type in key cert, proposal 145, unsupported
|
||||
dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION);
|
||||
return;
|
||||
|
@@ -359,7 +359,7 @@ public class IterativeSearchJob extends FloodSearchJob {
|
||||
}
|
||||
LeaseSetKeys lsk = ctx.keyManager().getKeys(_fromLocalDest);
|
||||
supportsRatchet = lsk != null &&
|
||||
lsk.isSupported(EncType.ECIES_X25519) &&
|
||||
(lsk.isSupported(EncType.ECIES_X25519) || lsk.getPQDecryptionKey() != null) &&
|
||||
DatabaseLookupMessage.supportsRatchetReplies(ri);
|
||||
supportsElGamal = !supportsRatchet &&
|
||||
lsk != null &&
|
||||
|
@@ -17,6 +17,7 @@ import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||
@@ -242,6 +243,8 @@ public class MessageWrapper {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
throw new IllegalStateException("skm not a ratchet " + skm);
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||
import net.i2p.router.networkdb.kademlia.MessageWrapper;
|
||||
import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession;
|
||||
@@ -317,6 +318,9 @@ abstract class BuildRequestor {
|
||||
} else if (replySKM instanceof MuxedSKM) {
|
||||
MuxedSKM mskm = (MuxedSKM) replySKM;
|
||||
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
|
||||
} else if (replySKM instanceof MuxedPQSKM) {
|
||||
MuxedPQSKM mskm = (MuxedPQSKM) replySKM;
|
||||
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
|
||||
} else {
|
||||
// non-EC client, shouldn't happen, checked at top of createTunnelBuildMessage() below
|
||||
if (log.shouldWarn())
|
||||
|
@@ -13,6 +13,7 @@ import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
@@ -368,6 +369,8 @@ class TestJob extends JobImpl {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
// shouldn't happen
|
||||
rskm = null;
|
||||
|
@@ -0,0 +1,24 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
/**
|
||||
* Interface describing secret with encapsulation details.
|
||||
*/
|
||||
public interface SecretWithEncapsulation
|
||||
extends Destroyable
|
||||
{
|
||||
/**
|
||||
* Return the secret associated with the encapsulation.
|
||||
*
|
||||
* @return the secret the encapsulation is for.
|
||||
*/
|
||||
byte[] getSecret();
|
||||
|
||||
/**
|
||||
* Return the data that carries the secret in its encapsulated form.
|
||||
*
|
||||
* @return the encapsulation of the secret.
|
||||
*/
|
||||
byte[] getEncapsulation();
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package org.bouncycastle.pqc.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
|
||||
public interface KEMParameters
|
||||
extends CipherParameters
|
||||
{
|
||||
}
|
84
router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java
Normal file
84
router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package org.bouncycastle.pqc.crypto.mlkem;
|
||||
|
||||
final class CBD
|
||||
{
|
||||
|
||||
public static void mlkemCBD(Poly r, byte[] bytes, int eta)
|
||||
{
|
||||
long t, d;
|
||||
int a, b;
|
||||
|
||||
switch (eta)
|
||||
{
|
||||
case 3:
|
||||
for (int i = 0; i < MLKEMEngine.KyberN / 4; i++)
|
||||
{
|
||||
t = convertByteTo24BitUnsignedInt(bytes, 3 * i);
|
||||
d = t & 0x00249249;
|
||||
d = d + ((t >> 1) & 0x00249249);
|
||||
d = d + ((t >> 2) & 0x00249249);
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
a = (short)((d >> (6 * j + 0)) & 0x7);
|
||||
b = (short)((d >> (6 * j + 3)) & 0x7);
|
||||
// System.out.printf("a = %d, b = %d\n", a, b);
|
||||
r.setCoeffIndex(4 * i + j, (short)(a - b));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Only for Kyber512 where eta = 2
|
||||
for (int i = 0; i < MLKEMEngine.KyberN / 8; i++)
|
||||
{
|
||||
t = convertByteTo32BitUnsignedInt(bytes, 4 * i); // ? Problem
|
||||
d = t & 0x55555555;
|
||||
d = d + ((t >> 1) & 0x55555555);
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
a = (short)((d >> (4 * j + 0)) & 0x3);
|
||||
b = (short)((d >> (4 * j + eta)) & 0x3);
|
||||
r.setCoeffIndex(8 * i + j, (short)(a - b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Array of Bytes to a 32-bit Unsigned Integer
|
||||
* Returns a 32-bit unsigned integer as a long
|
||||
*
|
||||
* @param x
|
||||
* @return
|
||||
*/
|
||||
private static long convertByteTo32BitUnsignedInt(byte[] x, int offset)
|
||||
{
|
||||
// Convert first byte to an unsigned integer
|
||||
// byte x & 0xFF allows us to grab the last 8 bits
|
||||
long r = (long)(x[offset] & 0xFF);
|
||||
|
||||
// Perform the same operation then left bit shift to store the next 8 bits without
|
||||
// altering the previous bits
|
||||
r = r | (long)((long)(x[offset + 1] & 0xFF) << 8);
|
||||
r = r | (long)((long)(x[offset + 2] & 0xFF) << 16);
|
||||
r = r | (long)((long)(x[offset + 3] & 0xFF) << 24);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Array of Bytes to a 24-bit Unsigned Integer
|
||||
* Returns a 24-bit unsigned integer as a long from byte x
|
||||
*
|
||||
* @param x
|
||||
* @return
|
||||
*/
|
||||
private static long convertByteTo24BitUnsignedInt(byte[] x, int offset)
|
||||
{
|
||||
// Refer to convertByteTo32-BitUnsignedInt for explanation
|
||||
long r = (long)(x[offset] & 0xFF);
|
||||
r = r | (long)((long)(x[offset + 1] & 0xFF) << 8);
|
||||
r = r | (long)((long)(x[offset + 2] & 0xFF) << 16);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user