diff --git a/LICENSE.txt b/LICENSE.txt index 8af9cfc4cf..861c68cf4f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -128,6 +128,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) diff --git a/apps/i2ptunnel/jsp/editClient.jsi b/apps/i2ptunnel/jsp/editClient.jsi index 3f2d3d7df1..10daa693bc 100644 --- a/apps/i2ptunnel/jsp/editClient.jsi +++ b/apps/i2ptunnel/jsp/editClient.jsi @@ -604,6 +604,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); %>
+ * 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; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/LongDigest.java b/router/java/src/org/bouncycastle/crypto/digests/LongDigest.java new file mode 100644 index 0000000000..9fba7aae76 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/LongDigest.java @@ -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(); +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java b/router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java new file mode 100644 index 0000000000..8c93fb0282 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java @@ -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/ + *
+ * 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); + } +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java b/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java new file mode 100644 index 0000000000..85753972ae --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java @@ -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. + * + *
+ * block word digest + * SHA-1 512 32 160 + * SHA-256 512 32 256 + * SHA-384 1024 64 384 + * SHA-512 1024 64 512 + *+ */ +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); + } +} + diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java b/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java new file mode 100644 index 0000000000..998f1286ab --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java @@ -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/ + *
+ * 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; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/package-info.java b/router/java/src/org/bouncycastle/crypto/digests/package-info.java new file mode 100644 index 0000000000..a8e7db290d --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/package-info.java @@ -0,0 +1,4 @@ +/** + * Message digest classes. + */ +package org.bouncycastle.crypto.digests; diff --git a/router/java/src/org/bouncycastle/crypto/package-info.java b/router/java/src/org/bouncycastle/crypto/package-info.java new file mode 100644 index 0000000000..73d7c3c6a3 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/package-info.java @@ -0,0 +1,4 @@ +/** + * Base classes for the lightweight API. + */ +package org.bouncycastle.crypto; diff --git a/router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java b/router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java new file mode 100644 index 0000000000..03ba7253b5 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java @@ -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; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java b/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java new file mode 100644 index 0000000000..e8778cbe20 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java @@ -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; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java b/router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java new file mode 100644 index 0000000000..9492f5a3ee --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java @@ -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; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/params/package-info.java b/router/java/src/org/bouncycastle/crypto/params/package-info.java new file mode 100644 index 0000000000..04fc776863 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for parameter objects for ciphers and generators. + */ +package org.bouncycastle.crypto.params; diff --git a/router/java/src/org/bouncycastle/package-info.java b/router/java/src/org/bouncycastle/package-info.java new file mode 100644 index 0000000000..ad1c9ca40e --- /dev/null +++ b/router/java/src/org/bouncycastle/package-info.java @@ -0,0 +1,6 @@ +/** + * This is a small portion of bouncycastle 1.80 for MLKEM, modified to reduce dependencies. + * + * @since 0.9.67 + */ +package org.bouncycastle; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java new file mode 100644 index 0000000000..48a1084475 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java @@ -0,0 +1,8 @@ +package org.bouncycastle.pqc.crypto; + +import org.bouncycastle.crypto.CipherParameters; + +public interface KEMParameters + extends CipherParameters +{ +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java new file mode 100644 index 0000000000..a7df3c7fc8 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java @@ -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; + } + + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java new file mode 100644 index 0000000000..6c5b49f36a --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java @@ -0,0 +1,327 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; +import java.util.Arrays; + +import org.bouncycastle.util.Util; + +class MLKEMEngine +{ + private SecureRandom random; + private MLKEMIndCpa indCpa; + + // constant parameters + public final static int KyberN = 256; + public final static int KyberQ = 3329; + public final static int KyberQinv = 62209; + + public final static int KyberSymBytes = 32; // Number of bytes for Hashes and Seeds + private final static int KyberSharedSecretBytes = 32; // Number of Bytes for Shared Secret + + public final static int KyberPolyBytes = 384; + + private final static int KyberEta2 = 2; + + private final static int KyberIndCpaMsgBytes = KyberSymBytes; + + + // parameters for Kyber{k} + private final int KyberK; + private final int KyberPolyVecBytes; + private final int KyberPolyCompressedBytes; + private final int KyberPolyVecCompressedBytes; + private final int KyberEta1; + private final int KyberIndCpaPublicKeyBytes; + private final int KyberIndCpaSecretKeyBytes; + private final int KyberIndCpaBytes; + private final int KyberPublicKeyBytes; + private final int KyberSecretKeyBytes; + private final int KyberCipherTextBytes; + + // Crypto + private final int CryptoBytes; + private final int CryptoSecretKeyBytes; + private final int CryptoPublicKeyBytes; + private final int CryptoCipherTextBytes; + + private final int sessionKeyLength; + private final Symmetric symmetric; + + public Symmetric getSymmetric() + { + return symmetric; + } + public static int getKyberEta2() + { + return KyberEta2; + } + + public static int getKyberIndCpaMsgBytes() + { + return KyberIndCpaMsgBytes; + } + + public int getCryptoCipherTextBytes() + { + return CryptoCipherTextBytes; + } + + public int getCryptoPublicKeyBytes() + { + return CryptoPublicKeyBytes; + } + + public int getCryptoSecretKeyBytes() + { + return CryptoSecretKeyBytes; + } + + public int getCryptoBytes() + { + return CryptoBytes; + } + + public int getKyberCipherTextBytes() + { + return KyberCipherTextBytes; + } + + public int getKyberSecretKeyBytes() + { + return KyberSecretKeyBytes; + } + + public int getKyberIndCpaPublicKeyBytes() + { + return KyberIndCpaPublicKeyBytes; + } + + + public int getKyberIndCpaSecretKeyBytes() + { + return KyberIndCpaSecretKeyBytes; + } + + public int getKyberIndCpaBytes() + { + return KyberIndCpaBytes; + } + + public int getKyberPublicKeyBytes() + { + return KyberPublicKeyBytes; + } + + public int getKyberPolyCompressedBytes() + { + return KyberPolyCompressedBytes; + } + + public int getKyberK() + { + return KyberK; + } + + public int getKyberPolyVecBytes() + { + return KyberPolyVecBytes; + } + + public int getKyberPolyVecCompressedBytes() + { + return KyberPolyVecCompressedBytes; + } + + public int getKyberEta1() + { + return KyberEta1; + } + + public MLKEMEngine(int k) + { + this.KyberK = k; + switch (k) + { + case 2: + KyberEta1 = 3; + KyberPolyCompressedBytes = 128; + KyberPolyVecCompressedBytes = k * 320; + sessionKeyLength = 32; + break; + case 3: + KyberEta1 = 2; + KyberPolyCompressedBytes = 128; + KyberPolyVecCompressedBytes = k * 320; + sessionKeyLength = 32; + break; + case 4: + KyberEta1 = 2; + KyberPolyCompressedBytes = 160; + KyberPolyVecCompressedBytes = k * 352; + sessionKeyLength = 32; + break; + default: + throw new IllegalArgumentException("K: " + k + " is not supported for Crystals Kyber"); + } + + this.KyberPolyVecBytes = k * KyberPolyBytes; + this.KyberIndCpaPublicKeyBytes = KyberPolyVecBytes + KyberSymBytes; + this.KyberIndCpaSecretKeyBytes = KyberPolyVecBytes; + this.KyberIndCpaBytes = KyberPolyVecCompressedBytes + KyberPolyCompressedBytes; + this.KyberPublicKeyBytes = KyberIndCpaPublicKeyBytes; + this.KyberSecretKeyBytes = KyberIndCpaSecretKeyBytes + KyberIndCpaPublicKeyBytes + 2 * KyberSymBytes; + this.KyberCipherTextBytes = KyberIndCpaBytes; + + // Define Crypto Params + this.CryptoBytes = KyberSharedSecretBytes; + this.CryptoSecretKeyBytes = KyberSecretKeyBytes; + this.CryptoPublicKeyBytes = KyberPublicKeyBytes; + this.CryptoCipherTextBytes = KyberCipherTextBytes; + + this.symmetric = new Symmetric.ShakeSymmetric(); + + this.indCpa = new MLKEMIndCpa(this); + } + + public void init(SecureRandom random) + { + this.random = random; + } + + public byte[][] generateKemKeyPair() + { + byte[] d = new byte[KyberSymBytes]; + byte[] z = new byte[KyberSymBytes]; + random.nextBytes(d); + random.nextBytes(z); + + return generateKemKeyPairInternal(d, z); + } + + //Internal functions are deterministic. No randomness is sampled inside them + public byte[][] generateKemKeyPairInternal(byte[] d, byte[] z) + { + byte[][] indCpaKeyPair = indCpa.generateKeyPair(d); + + byte[] s = new byte[KyberIndCpaSecretKeyBytes]; + + System.arraycopy(indCpaKeyPair[1], 0, s, 0, KyberIndCpaSecretKeyBytes); + + byte[] hashedPublicKey = new byte[32]; + + symmetric.hash_h(hashedPublicKey, indCpaKeyPair[0], 0); + + byte[] outputPublicKey = new byte[KyberIndCpaPublicKeyBytes]; + System.arraycopy(indCpaKeyPair[0], 0, outputPublicKey, 0, KyberIndCpaPublicKeyBytes); + return new byte[][] + { + Arrays.copyOfRange(outputPublicKey, 0, outputPublicKey.length - 32), + Arrays.copyOfRange(outputPublicKey, outputPublicKey.length - 32, outputPublicKey.length), + s, + hashedPublicKey, + z, + Util.concatenate(d, z) + }; + } + + public byte[][] kemEncryptInternal(byte[] publicKeyInput, byte[] randBytes) + { + byte[] outputCipherText; + + byte[] buf = new byte[2 * KyberSymBytes]; + byte[] kr = new byte[2 * KyberSymBytes]; + + System.arraycopy(randBytes, 0, buf, 0, KyberSymBytes); + + // SHA3-256 Public Key + symmetric.hash_h(buf, publicKeyInput, KyberSymBytes); + + // SHA3-512( SHA3-256(RandBytes) || SHA3-256(PublicKey) ) + symmetric.hash_g(kr, buf); + + // IndCpa Encryption + outputCipherText = indCpa.encrypt(publicKeyInput, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, 32, kr.length)); + + byte[] outputSharedSecret = new byte[sessionKeyLength]; + + System.arraycopy(kr, 0, outputSharedSecret, 0, outputSharedSecret.length); + + byte[][] outBuf = new byte[2][]; + outBuf[0] = outputSharedSecret; + outBuf[1] = outputCipherText; + return outBuf; + } + + public byte[] kemDecryptInternal(byte[] secretKey, byte[] cipherText) + { + byte[] buf = new byte[2 * KyberSymBytes], + kr = new byte[2 * KyberSymBytes]; + + byte[] publicKey = Arrays.copyOfRange(secretKey, KyberIndCpaSecretKeyBytes, secretKey.length); + + System.arraycopy(indCpa.decrypt(secretKey, cipherText), 0, buf, 0, KyberSymBytes); + + System.arraycopy(secretKey, KyberSecretKeyBytes - 2 * KyberSymBytes, buf, KyberSymBytes, KyberSymBytes); + + symmetric.hash_g(kr, buf); + + byte[] implicit_rejection = new byte[KyberSymBytes + KyberCipherTextBytes]; + + System.arraycopy(secretKey, KyberSecretKeyBytes - KyberSymBytes, implicit_rejection, 0, KyberSymBytes); + + System.arraycopy(cipherText, 0, implicit_rejection, KyberSymBytes, KyberCipherTextBytes); + + symmetric.kdf(implicit_rejection, implicit_rejection ); // J(z||c) + + byte[] cmp = indCpa.encrypt(publicKey, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, KyberSymBytes, kr.length)); + + boolean fail = !(Util.constantTimeAreEqual(cipherText, cmp)); + + cmov(kr, implicit_rejection, KyberSymBytes, fail); + + return Arrays.copyOfRange(kr, 0, sessionKeyLength); + } + + public byte[][] kemEncrypt(byte[] publicKeyInput, byte[] randBytes) + { + //TODO: do input validation elsewhere? + // Input validation (6.2 ML-KEM Encaps) + // Type Check + if (publicKeyInput.length != KyberIndCpaPublicKeyBytes) + { + throw new IllegalArgumentException("Input validation Error: Type check failed for ml-kem encapsulation"); + } + // Modulus Check + PolyVec polyVec = new PolyVec(this); + byte[] seed = indCpa.unpackPublicKey(polyVec, publicKeyInput); + byte[] ek = indCpa.packPublicKey(polyVec, seed); + if (!Arrays.equals(ek, publicKeyInput)) + { + throw new IllegalArgumentException("Input validation: Modulus check failed for ml-kem encapsulation"); + } + + return kemEncryptInternal(publicKeyInput, randBytes); + } + public byte[] kemDecrypt(byte[] secretKey, byte[] cipherText) + { + //TODO: do input validation + return kemDecryptInternal(secretKey, cipherText); + } + + private void cmov(byte[] r, byte[] x, int xlen, boolean b) + { + if (b) + { + System.arraycopy(x, 0, r, 0, xlen); + } + else + { + System.arraycopy(r, 0, r, 0, xlen); + } + } + + public void getRandomBytes(byte[] buf) + { + this.random.nextBytes(buf); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java new file mode 100644 index 0000000000..e36c3c6ae3 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java @@ -0,0 +1,28 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +public class MLKEMExtractor +{ + private final MLKEMPrivateKeyParameters privateKey; + private final MLKEMEngine engine; + + public MLKEMExtractor(MLKEMPrivateKeyParameters privateKey) + { + if (privateKey == null) + { + throw new NullPointerException("'privateKey' cannot be null"); + } + + this.privateKey = privateKey; + this.engine = privateKey.getParameters().getEngine(); + } + + public byte[] extractSecret(byte[] encapsulation) + { + return engine.kemDecrypt(privateKey.getEncoded(), encapsulation); + } + + public int getEncapsulationLength() + { + return engine.getCryptoCipherTextBytes(); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java new file mode 100644 index 0000000000..cfeb318009 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java @@ -0,0 +1,40 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.SecretWithEncapsulation; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.pqc.crypto.util.SecretWithEncapsulationImpl; + +public class MLKEMGenerator +{ + // the source of randomness + private final SecureRandom sr; + + public MLKEMGenerator(SecureRandom random) + { + this.sr = random; + } + + public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recipientKey) + { + MLKEMPublicKeyParameters key = (MLKEMPublicKeyParameters)recipientKey; + MLKEMEngine engine = key.getParameters().getEngine(); + engine.init(sr); + + byte[] randBytes = new byte[32]; + engine.getRandomBytes(randBytes); + + byte[][] kemEncrypt = engine.kemEncrypt(key.getEncoded(), randBytes); + return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]); + } + public SecretWithEncapsulation internalGenerateEncapsulated(AsymmetricKeyParameter recipientKey, byte[] randBytes) + { + MLKEMPublicKeyParameters key = (MLKEMPublicKeyParameters)recipientKey; + MLKEMEngine engine = key.getParameters().getEngine(); + engine.init(sr); + + byte[][] kemEncrypt = engine.kemEncryptInternal(key.getEncoded(), randBytes); + return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java new file mode 100644 index 0000000000..3e30319137 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java @@ -0,0 +1,446 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.util.Arrays; + +import org.bouncycastle.util.Util; + +class MLKEMIndCpa +{ + private MLKEMEngine engine; + private int kyberK; + private int indCpaPublicKeyBytes; + private int polyVecBytes; + private int indCpaBytes; + private int polyVecCompressedBytes; + private int polyCompressedBytes; + + private Symmetric symmetric; + + public MLKEMIndCpa(MLKEMEngine engine) + { + this.engine = engine; + this.kyberK = engine.getKyberK(); + this.indCpaPublicKeyBytes = engine.getKyberPublicKeyBytes(); + this.polyVecBytes = engine.getKyberPolyVecBytes(); + this.indCpaBytes = engine.getKyberIndCpaBytes(); + this.polyVecCompressedBytes = engine.getKyberPolyVecCompressedBytes(); + this.polyCompressedBytes = engine.getKyberPolyCompressedBytes(); + this.symmetric = engine.getSymmetric(); + + KyberGenerateMatrixNBlocks = + ( + ( + 12 * MLKEMEngine.KyberN + / 8 * (1 << 12) + / MLKEMEngine.KyberQ + symmetric.xofBlockBytes + ) + / symmetric.xofBlockBytes + ); + } + + + /** + * Generates IndCpa Key Pair + * + * @return KeyPair where each key is represented as bytes + */ + byte[][] generateKeyPair(byte[] d) + { + PolyVec secretKey = new PolyVec(engine), + publicKey = new PolyVec(engine), + e = new PolyVec(engine); + + // (p, sigma) <- G(d || k) + + byte[] buf = new byte[64]; + symmetric.hash_g(buf, Util.append(d, (byte)kyberK)); + + byte[] publicSeed = new byte[32]; // p in docs + byte[] noiseSeed = new byte[32]; // sigma in docs + System.arraycopy(buf, 0, publicSeed, 0, 32); + System.arraycopy(buf, 32, noiseSeed, 0, 32); + + byte count = (byte)0; + + // Helper.printByteArray(buf); + + + PolyVec[] aMatrix = new PolyVec[kyberK]; + + int i; + for (i = 0; i < kyberK; i++) + { + aMatrix[i] = new PolyVec(engine); + } + + generateMatrix(aMatrix, publicSeed, false); + + // System.out.println("aMatrix = "); + // for(i = 0; i < kyberK; i++) { + // System.out.print("["); + // for (int j = 0; j < kyberK; j++) { + // System.out.print("["); + // for (int k = 0; k < KyberEngine.KyberN; k++) { + // System.out.printf("%d ,", aMatrix[i].getVectorIndex(j).getCoeffIndex(k)); + // } + // System.out.print("], \n"); + // } + // System.out.print("]\n"); + // } + + for (i = 0; i < kyberK; i++) + { + secretKey.getVectorIndex(i).getEta1Noise(noiseSeed, count); + + // System.out.print("SecretKeyPolyVec["+i+"] = ["); + // for (int j =0; j < KyberEngine.KyberN; j++) { + // System.out.print(secretKey.getVectorIndex(i).getCoeffIndex(j) + ", "); + // } + // System.out.println("]"); + count = (byte)(count + (byte)1); + } + + for (i = 0; i < kyberK; i++) + { + e.getVectorIndex(i).getEta1Noise(noiseSeed, count); + count = (byte)(count + (byte)1); + } + + secretKey.polyVecNtt(); + + // System.out.print("SecretKeyPolyVec = ["); + // for (i = 0; i < kyberK; i++) { + // System.out.print("["); + // for (int j =0; j < KyberEngine.KyberN; j++) { + // System.out.print(secretKey.getVectorIndex(i).getCoeffIndex(j) + ", "); + // } + // System.out.println("],"); + // } + // System.out.println("]"); + + + e.polyVecNtt(); + + for (i = 0; i < kyberK; i++) + { + PolyVec.pointwiseAccountMontgomery(publicKey.getVectorIndex(i), aMatrix[i], secretKey, engine); + publicKey.getVectorIndex(i).convertToMont(); + } + + // System.out.print("PublicKey PolyVec = ["); + // Helper.printPolyVec(publicKey, kyberK); + + publicKey.addPoly(e); + publicKey.reducePoly(); + + return new byte[][]{packPublicKey(publicKey, publicSeed), packSecretKey(secretKey)}; + } + + public byte[] encrypt(byte[] publicKeyInput, byte[] msg, byte[] coins) + { + int i; + byte[] seed; + byte nonce = (byte)0; + PolyVec sp = new PolyVec(engine), + publicKeyPolyVec = new PolyVec(engine), + errorPolyVector = new PolyVec(engine), + bp = new PolyVec(engine); + PolyVec[] aMatrixTranspose = new PolyVec[engine.getKyberK()]; + Poly errorPoly = new Poly(engine), + v = new Poly(engine), + k = new Poly(engine); + + + // System.out.print("publickeyinput = "); + // Helper.printByteArray(publicKeyInput); + // System.out.println(); + + seed = unpackPublicKey(publicKeyPolyVec, publicKeyInput); + + // System.out.print("publickeyPolyVec = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(publicKeyPolyVec.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + // System.out.print("seed = "); + // Helper.printByteArray(seed); + // System.out.println(); + + k.fromMsg(msg); + + for (i = 0; i < kyberK; i++) + { + aMatrixTranspose[i] = new PolyVec(engine); + } + + generateMatrix(aMatrixTranspose, seed, true); + + // System.out.print("matrix transposed = "); + // for (i = 0; i < kyberK; i++) { + // System.out.print("["); + // for(int j = 0; j < kyberK; j++) { + // System.out.print("["); + // for (int l = 0; l < 256; l++) { + // System.out.printf("%d ,", aMatrixTranspose[i].getVectorIndex(j).getCoeffIndex(l)); + // } + // System.out.print("] ,\n"); + // } + // System.out.println("] ,"); + // } + + + for (i = 0; i < kyberK; i++) + { + sp.getVectorIndex(i).getEta1Noise(coins, nonce); + nonce = (byte)(nonce + (byte)1); + } + + + for (i = 0; i < kyberK; i++) + { + errorPolyVector.getVectorIndex(i).getEta2Noise(coins, nonce); + nonce = (byte)(nonce + (byte)1); + } + errorPoly.getEta2Noise(coins, nonce); + + sp.polyVecNtt(); + + // System.out.print("sp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(sp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + + // System.out.print("sp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(sp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + for (i = 0; i < kyberK; i++) + { + + PolyVec.pointwiseAccountMontgomery(bp.getVectorIndex(i), aMatrixTranspose[i], sp, engine); + } + // System.out.print("bp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + PolyVec.pointwiseAccountMontgomery(v, publicKeyPolyVec, sp, engine); + + bp.polyVecInverseNttToMont(); + + v.polyInverseNttToMont(); + + bp.addPoly(errorPolyVector); + + + v.addCoeffs(errorPoly); + v.addCoeffs(k); + + bp.reducePoly(); + v.reduce(); + + // System.out.print("bp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + + // System.out.print("v = "); + // Helper.printShortArray(v.getCoeffs()); + // System.out.println(); + + + byte[] outputCipherText = packCipherText(bp, v); + + return outputCipherText; + } + + private byte[] packCipherText(PolyVec b, Poly v) + { + byte[] outBuf = new byte[indCpaBytes]; + System.arraycopy(b.compressPolyVec(), 0, outBuf, 0, polyVecCompressedBytes); + System.arraycopy(v.compressPoly(), 0, outBuf, polyVecCompressedBytes, polyCompressedBytes); + // System.out.print("outBuf = ["); + // Helper.printByteArray(outBuf); + return outBuf; + } + + private void unpackCipherText(PolyVec b, Poly v, byte[] cipherText) + { + byte[] compressedPolyVecCipherText = Arrays.copyOfRange(cipherText, 0, engine.getKyberPolyVecCompressedBytes()); + b.decompressPolyVec(compressedPolyVecCipherText); + + byte[] compressedPolyCipherText = Arrays.copyOfRange(cipherText, engine.getKyberPolyVecCompressedBytes(), cipherText.length); + v.decompressPoly(compressedPolyCipherText); + } + + public byte[] packPublicKey(PolyVec publicKeyPolyVec, byte[] seed) + { + byte[] buf = new byte[indCpaPublicKeyBytes]; + System.arraycopy(publicKeyPolyVec.toBytes(), 0, buf, 0, polyVecBytes); + System.arraycopy(seed, 0, buf, polyVecBytes, MLKEMEngine.KyberSymBytes); + return buf; + } + + public byte[] unpackPublicKey(PolyVec publicKeyPolyVec, byte[] publicKey) + { + byte[] outputSeed = new byte[MLKEMEngine.KyberSymBytes]; + publicKeyPolyVec.fromBytes(publicKey); + System.arraycopy(publicKey, polyVecBytes, outputSeed, 0, MLKEMEngine.KyberSymBytes); + return outputSeed; + } + + public byte[] packSecretKey(PolyVec secretKeyPolyVec) + { + return secretKeyPolyVec.toBytes(); + } + + public void unpackSecretKey(PolyVec secretKeyPolyVec, byte[] secretKey) + { + secretKeyPolyVec.fromBytes(secretKey); + } + + public final int KyberGenerateMatrixNBlocks; + + public void generateMatrix(PolyVec[] aMatrix, byte[] seed, boolean transposed) + { + int i, j, k, ctr, off; + byte[] buf = new byte[KyberGenerateMatrixNBlocks * symmetric.xofBlockBytes + 2]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < kyberK; j++) + { + if (transposed) + { + symmetric.xofAbsorb(seed, (byte) i, (byte) j); + } + else + { + symmetric.xofAbsorb(seed, (byte) j, (byte) i); + } + symmetric.xofSqueezeBlocks(buf, 0, symmetric.xofBlockBytes * KyberGenerateMatrixNBlocks); + + int buflen = KyberGenerateMatrixNBlocks * symmetric.xofBlockBytes; + ctr = rejectionSampling(aMatrix[i].getVectorIndex(j), 0, MLKEMEngine.KyberN, buf, buflen); + + while (ctr < MLKEMEngine.KyberN) + { + off = buflen % 3; + for (k = 0; k < off; k++) + { + buf[k] = buf[buflen - off + k]; + } + symmetric.xofSqueezeBlocks(buf, off, symmetric.xofBlockBytes * 2); + buflen = off + symmetric.xofBlockBytes; + // Error in code Section Unsure + ctr += rejectionSampling(aMatrix[i].getVectorIndex(j), ctr, MLKEMEngine.KyberN - ctr, buf, buflen); + } + } + } + + } + + private static int rejectionSampling(Poly outputBuffer, int coeffOff, int len, byte[] inpBuf, int inpBufLen) + { + int ctr, pos; + short val0, val1; + ctr = pos = 0; + while (ctr < len && pos + 3 <= inpBufLen) + { + val0 = (short)(((((short)(inpBuf[pos] & 0xFF)) >> 0) | (((short)(inpBuf[pos + 1] & 0xFF)) << 8)) & 0xFFF); + val1 = (short)(((((short)(inpBuf[pos + 1] & 0xFF)) >> 4) | (((short)(inpBuf[pos + 2] & 0xFF)) << 4)) & 0xFFF); + pos = pos + 3; + if (val0 < (short)MLKEMEngine.KyberQ) + { + outputBuffer.setCoeffIndex(coeffOff + ctr, (short)val0); + ctr++; + } + if (ctr < len && val1 < (short)MLKEMEngine.KyberQ) + { + outputBuffer.setCoeffIndex(coeffOff + ctr, (short)val1); + ctr++; + } + } + return ctr; + + } + + public byte[] decrypt(byte[] secretKey, byte[] cipherText) + { + byte[] outputMessage = new byte[MLKEMEngine.getKyberIndCpaMsgBytes()]; + + PolyVec bp = new PolyVec(engine), secretKeyPolyVec = new PolyVec(engine); + Poly v = new Poly(engine), mp = new Poly(engine); + + unpackCipherText(bp, v, cipherText); + + // System.out.print("bp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + + // System.out.print("v = "); + // Helper.printShortArray(v.getCoeffs()); + // System.out.println(); + + unpackSecretKey(secretKeyPolyVec, secretKey); + + // System.out.print("decrypt secretkey = ");; + // Helper.printByteArray(secretKey); + + // System.out.print("SecretKeyPolyVec = ["); + // for (i = 0; i < kyberK; i++) { + // System.out.print("["); + // for (int j =0; j < KyberEngine.KyberN; j++) { + // System.out.print(secretKeyPolyVec.getVectorIndex(i).getCoeffIndex(j) + ", "); + // } + // System.out.println("],"); + // } + // System.out.println("]"); + + // System.out.print("bp before ntt = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + bp.polyVecNtt(); + + // System.out.print("bp after ntt = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + PolyVec.pointwiseAccountMontgomery(mp, secretKeyPolyVec, bp, engine); + + + mp.polyInverseNttToMont(); + + mp.polySubtract(v); + + mp.reduce(); + + outputMessage = mp.toMsg(); + + return outputMessage; + } + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java new file mode 100644 index 0000000000..7d92602345 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java @@ -0,0 +1,24 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class MLKEMKeyGenerationParameters + extends KeyGenerationParameters +{ + private final MLKEMParameters params; + + public MLKEMKeyGenerationParameters( + SecureRandom random, + MLKEMParameters mlkemParameters) + { + super(random, 256); + this.params = mlkemParameters; + } + + public MLKEMParameters getParameters() + { + return params; + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java new file mode 100644 index 0000000000..9f1d6d9dc2 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java @@ -0,0 +1,55 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class MLKEMKeyPairGenerator +{ + private MLKEMParameters mlkemParams; + + private SecureRandom random; + + private void initialize( + KeyGenerationParameters param) + { + this.mlkemParams = ((MLKEMKeyGenerationParameters)param).getParameters(); + this.random = param.getRandom(); + + } + + private AsymmetricCipherKeyPair genKeyPair() + { + MLKEMEngine engine = mlkemParams.getEngine(); + + engine.init(random); + + byte[][] keyPair = engine.generateKemKeyPair(); + + MLKEMPublicKeyParameters pubKey = new MLKEMPublicKeyParameters(mlkemParams, keyPair[0], keyPair[1]); + MLKEMPrivateKeyParameters privKey = new MLKEMPrivateKeyParameters(mlkemParams, keyPair[2], keyPair[3], keyPair[4], keyPair[0], keyPair[1], keyPair[5]); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } + + public void init(KeyGenerationParameters param) + { + this.initialize(param); + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + return genKeyPair(); + } + + public AsymmetricCipherKeyPair internalGenerateKeyPair(byte[] d, byte[] z) + { + byte[][] keyPair = mlkemParams.getEngine().generateKemKeyPairInternal(d, z); + + MLKEMPublicKeyParameters pubKey = new MLKEMPublicKeyParameters(mlkemParams, keyPair[0], keyPair[1]); + MLKEMPrivateKeyParameters privKey = new MLKEMPrivateKeyParameters(mlkemParams, keyPair[2], keyPair[3], keyPair[4], keyPair[0], keyPair[1], keyPair[5]); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java new file mode 100644 index 0000000000..227a46c65a --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public class MLKEMKeyParameters + extends AsymmetricKeyParameter +{ + private MLKEMParameters params; + + public MLKEMKeyParameters( + boolean isPrivate, + MLKEMParameters params) + { + super(isPrivate); + this.params = params; + } + + public MLKEMParameters getParameters() + { + return params; + } + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java new file mode 100644 index 0000000000..0dbc69bece --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java @@ -0,0 +1,37 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.pqc.crypto.KEMParameters; + +public class MLKEMParameters + implements KEMParameters +{ + public static final MLKEMParameters ml_kem_512 = new MLKEMParameters("ML-KEM-512", 2, 256); + public static final MLKEMParameters ml_kem_768 = new MLKEMParameters("ML-KEM-768", 3, 256); + public static final MLKEMParameters ml_kem_1024 = new MLKEMParameters("ML-KEM-1024", 4, 256); + + private final String name; + private final int k; + private final int sessionKeySize; + + private MLKEMParameters(String name, int k, int sessionKeySize) + { + this.name = name; + this.k = k; + this.sessionKeySize = sessionKeySize; + } + + public String getName() + { + return name; + } + + public MLKEMEngine getEngine() + { + return new MLKEMEngine(k); + } + + public int getSessionKeySize() + { + return sessionKeySize; + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java new file mode 100644 index 0000000000..43d19653ba --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java @@ -0,0 +1,111 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.util.Arrays; + +import org.bouncycastle.util.Util; + +public class MLKEMPrivateKeyParameters + extends MLKEMKeyParameters +{ + final byte[] s; + final byte[] hpk; + final byte[] nonce; + final byte[] t; + final byte[] rho; + final byte[] seed; + + public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho) + { + this(params, s, hpk, nonce, t, rho, null); + } + + public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho, byte[] seed) + { + super(true, params); + + this.s = Util.clone(s); + this.hpk = Util.clone(hpk); + this.nonce = Util.clone(nonce); + this.t = Util.clone(t); + this.rho = Util.clone(rho); + this.seed = Util.clone(seed); + } + + public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] encoding) + { + super(true, params); + + MLKEMEngine eng = params.getEngine(); + if (encoding.length == MLKEMEngine.KyberSymBytes * 2) + { + byte[][] keyData = eng.generateKemKeyPairInternal( + Arrays.copyOfRange(encoding, 0, MLKEMEngine.KyberSymBytes), + Arrays.copyOfRange(encoding, MLKEMEngine.KyberSymBytes, encoding.length)); + this.s = keyData[2]; + this.hpk = keyData[3]; + this.nonce = keyData[4]; + this.t = keyData[0]; + this.rho = keyData[1]; + this.seed = keyData[5]; + } + else + { + int index = 0; + this.s = Arrays.copyOfRange(encoding, 0, eng.getKyberIndCpaSecretKeyBytes()); + index += eng.getKyberIndCpaSecretKeyBytes(); + this.t = Arrays.copyOfRange(encoding, index, index + eng.getKyberIndCpaPublicKeyBytes() - MLKEMEngine.KyberSymBytes); + index += eng.getKyberIndCpaPublicKeyBytes() - MLKEMEngine.KyberSymBytes; + this.rho = Arrays.copyOfRange(encoding, index, index + 32); + index += 32; + this.hpk = Arrays.copyOfRange(encoding, index, index + 32); + index += 32; + this.nonce = Arrays.copyOfRange(encoding, index, index + MLKEMEngine.KyberSymBytes); + this.seed = null; + } + } + + public byte[] getEncoded() + { + return Util.concatenate(new byte[][]{ s, t, rho, hpk, nonce }); + } + + public byte[] getHPK() + { + return Util.clone(hpk); + } + + public byte[] getNonce() + { + return Util.clone(nonce); + } + + public byte[] getPublicKey() + { + return MLKEMPublicKeyParameters.getEncoded(t, rho); + } + + public MLKEMPublicKeyParameters getPublicKeyParameters() + { + return new MLKEMPublicKeyParameters(getParameters(), t, rho); + } + + public byte[] getRho() + { + return Util.clone(rho); + } + + public byte[] getS() + { + return Util.clone(s); + } + + public byte[] getT() + { + return Util.clone(t); + } + + public byte[] getSeed() + { + return Util.clone(seed); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java new file mode 100644 index 0000000000..e2cc7afe2b --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java @@ -0,0 +1,46 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.util.Arrays; + +import org.bouncycastle.util.Util; + +public class MLKEMPublicKeyParameters + extends MLKEMKeyParameters +{ + static byte[] getEncoded(byte[] t, byte[] rho) + { + return Util.concatenate(t, rho); + } + + final byte[] t; + final byte[] rho; + + public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] t, byte[] rho) + { + super(false, params); + this.t = Util.clone(t); + this.rho = Util.clone(rho); + } + + public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] encoding) + { + super(false, params); + this.t = Arrays.copyOfRange(encoding, 0, encoding.length - MLKEMEngine.KyberSymBytes); + this.rho = Arrays.copyOfRange(encoding, encoding.length - MLKEMEngine.KyberSymBytes, encoding.length); + } + + public byte[] getEncoded() + { + return getEncoded(t, rho); + } + + public byte[] getRho() + { + return Util.clone(rho); + } + + public byte[] getT() + { + return Util.clone(t); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java new file mode 100644 index 0000000000..100647de08 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java @@ -0,0 +1,100 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +class Ntt +{ + + public static final short[] nttZetas = new short[]{ + 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, + 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, + 732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047, + 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830, + 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226, + 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, + 1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, + 418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, + 1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459, + 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628}; + + public static final short[] nttZetasInv = new short[]{ + 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, + 1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, + 1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685, + 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235, + 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652, + 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, + 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, + 2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, + 829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, + 3127, 3042, 1907, 1836, 1517, 359, 758, 1441}; + + public static short[] ntt(short[] inp) + { + short[] r = new short[MLKEMEngine.KyberN]; + System.arraycopy(inp, 0, r, 0, r.length); + int len, start, j, k; + short t, zeta; + + k = 1; + for (len = 128; len >= 2; len >>= 1) + { + for (start = 0; start < 256; start = j + len) + { + zeta = nttZetas[k++]; + for (j = start; j < start + len; ++j) + { + t = factorQMulMont(zeta, r[j + len]); + r[j + len] = (short)(r[j] - t); + r[j] = (short)(r[j] + t); + } + } + } + return r; + } + + public static short[] invNtt(short[] inp) + { + short[] r = new short[MLKEMEngine.KyberN]; + System.arraycopy(inp, 0, r, 0, MLKEMEngine.KyberN); + int len, start, j, k; + short t, zeta; + k = 0; + for (len = 2; len <= 128; len <<= 1) + { + for (start = 0; start < 256; start = j + len) + { + zeta = nttZetasInv[k++]; + for (j = start; j < start + len; ++j) + { + t = r[j]; + r[j] = Reduce.barretReduce((short)(t + r[j + len])); + r[j + len] = (short)(t - r[j + len]); + r[j + len] = factorQMulMont(zeta, r[j + len]); + + } + } + } + + for (j = 0; j < 256; ++j) + { + r[j] = factorQMulMont(r[j], Ntt.nttZetasInv[127]); + } + return r; + } + + public static short factorQMulMont(short a, short b) + { + return Reduce.montgomeryReduce((int)(a * b)); + } + + public static void baseMult(Poly outPoly, int outIndex, short a0, short a1, short b0, short b1, short zeta) + { + short outVal0 = factorQMulMont(a1, b1); + outVal0 = factorQMulMont(outVal0, zeta); + outVal0 += factorQMulMont(a0, b0); + outPoly.setCoeffIndex(outIndex, outVal0); + + short outVal1 = factorQMulMont(a0, b1); + outVal1 += factorQMulMont(a1, b0); + outPoly.setCoeffIndex(outIndex + 1, outVal1); + } +} \ No newline at end of file diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java new file mode 100644 index 0000000000..d7402a5ea7 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java @@ -0,0 +1,354 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +class Poly +{ + private short[] coeffs; + private MLKEMEngine engine; + private int polyCompressedBytes; + private int eta1; + private int eta2; + + private Symmetric symmetric; + + public Poly(MLKEMEngine engine) + { + this.coeffs = new short[MLKEMEngine.KyberN]; + this.engine = engine; + polyCompressedBytes = engine.getKyberPolyCompressedBytes(); + this.eta1 = engine.getKyberEta1(); + this.eta2 = MLKEMEngine.getKyberEta2(); + this.symmetric = engine.getSymmetric(); + } + + public short getCoeffIndex(int i) + { + return this.coeffs[i]; + } + + public short[] getCoeffs() + { + return this.coeffs; + } + + public void setCoeffIndex(int i, short val) + { + this.coeffs[i] = val; + } + + public void setCoeffs(short[] coeffs) + { + this.coeffs = coeffs; + } + + public void polyNtt() + { + this.setCoeffs(Ntt.ntt(this.getCoeffs())); + this.reduce(); + } + + public void polyInverseNttToMont() + { + this.setCoeffs(Ntt.invNtt(this.getCoeffs())); + } + + public void reduce() + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, Reduce.barretReduce(this.getCoeffIndex(i))); + } + } + + public static void baseMultMontgomery(Poly r, Poly a, Poly b) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN / 4; i++) + { + Ntt.baseMult(r, 4 * i, + a.getCoeffIndex(4 * i), a.getCoeffIndex(4 * i + 1), + b.getCoeffIndex(4 * i), b.getCoeffIndex(4 * i + 1), + Ntt.nttZetas[64 + i]); + Ntt.baseMult(r, 4 * i + 2, + a.getCoeffIndex(4 * i + 2), a.getCoeffIndex(4 * i + 3), + b.getCoeffIndex(4 * i + 2), b.getCoeffIndex(4 * i + 3), + (short)(-1 * Ntt.nttZetas[64 + i])); + } + } + + public void addCoeffs(Poly b) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, (short)(this.getCoeffIndex(i) + b.getCoeffIndex(i))); + } + } + + public void convertToMont() + { + int i; + final short f = (short)(((long)1 << 32) % MLKEMEngine.KyberQ); + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, Reduce.montgomeryReduce(this.getCoeffIndex(i) * f)); + } + } + + public byte[] compressPoly() + { + int i, j; + byte[] t = new byte[8]; + byte[] r = new byte[polyCompressedBytes]; + int count = 0; + this.conditionalSubQ(); + + // System.out.print("v = ["); + // Helper.printShortArray(this.coeffs); + // System.out.print("]\n"); + + if (polyCompressedBytes == 128) + { + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + for (j = 0; j < 8; j++) + { + /*t[j] = + (byte)((((((short)this.getCoeffIndex(8 * i + j)) << 4) + + + (KyberEngine.KyberQ / 2) + ) / KyberEngine.KyberQ) + & 15);*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + int t_j = this.getCoeffIndex(8 * i + j); + t_j <<= 4; + t_j += 1665; + t_j *= 80635; + t_j >>= 28; + t_j &= 15; + t[j] = (byte)t_j; + } + + r[count + 0] = (byte)(t[0] | (t[1] << 4)); + r[count + 1] = (byte)(t[2] | (t[3] << 4)); + r[count + 2] = (byte)(t[4] | (t[5] << 4)); + r[count + 3] = (byte)(t[6] | (t[7] << 4)); + count += 4; + } + } + else if (polyCompressedBytes == 160) + { + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + for (j = 0; j < 8; j++) + { + /*t[j] = + (byte)(((((this.getCoeffIndex(8 * i + j) << 5)) + + + (KyberEngine.KyberQ / 2) + ) / KyberEngine.KyberQ + ) & 31 + );*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + int t_j = this.getCoeffIndex(8 * i + j); + t_j <<= 5; + t_j += 1664; + t_j *= 40318; + t_j >>= 27; + t_j &= 31; + t[j] = (byte)t_j; + } + r[count + 0] = (byte)((t[0] >> 0) | (t[1] << 5)); + r[count + 1] = (byte)((t[1] >> 3) | (t[2] << 2) | (t[3] << 7)); + r[count + 2] = (byte)((t[3] >> 1) | (t[4] << 4)); + r[count + 3] = (byte)((t[4] >> 4) | (t[5] << 1) | (t[6] << 6)); + r[count + 4] = (byte)((t[6] >> 2) | (t[7] << 3)); + count += 5; + } + } + else + { + throw new RuntimeException("PolyCompressedBytes is neither 128 or 160!"); + } + + // System.out.print("r = "); + // Helper.printByteArray(r); + // System.out.println(); + + return r; + } + + public void decompressPoly(byte[] compressedPolyCipherText) + { + int i, count = 0; + + if (engine.getKyberPolyCompressedBytes() == 128) + { + for (i = 0; i < MLKEMEngine.KyberN / 2; i++) + { + this.setCoeffIndex(2 * i + 0, (short)((((short)((compressedPolyCipherText[count] & 0xFF) & 15) * MLKEMEngine.KyberQ) + 8) >> 4)); + this.setCoeffIndex(2 * i + 1, (short)((((short)((compressedPolyCipherText[count] & 0xFF) >> 4) * MLKEMEngine.KyberQ) + 8) >> 4)); + count += 1; + } + } + else if (engine.getKyberPolyCompressedBytes() == 160) + { + int j; + byte[] t = new byte[8]; + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + t[0] = (byte)((compressedPolyCipherText[count + 0] & 0xFF) >> 0); + t[1] = (byte)(((compressedPolyCipherText[count + 0] & 0xFF) >> 5) | ((compressedPolyCipherText[count + 1] & 0xFF) << 3)); + t[2] = (byte)((compressedPolyCipherText[count + 1] & 0xFF) >> 2); + t[3] = (byte)(((compressedPolyCipherText[count + 1] & 0xFF) >> 7) | ((compressedPolyCipherText[count + 2] & 0xFF) << 1)); + t[4] = (byte)(((compressedPolyCipherText[count + 2] & 0xFF) >> 4) | ((compressedPolyCipherText[count + 3] & 0xFF) << 4)); + t[5] = (byte)((compressedPolyCipherText[count + 3] & 0xFF) >> 1); + t[6] = (byte)(((compressedPolyCipherText[count + 3] & 0xFF) >> 6) | ((compressedPolyCipherText[count + 4] & 0xFF) << 2)); + t[7] = (byte)((compressedPolyCipherText[count + 4] & 0xFF) >> 3); + count += 5; + for (j = 0; j < 8; j++) + { + this.setCoeffIndex(8 * i + j, (short)(((t[j] & 31) * MLKEMEngine.KyberQ + 16) >> 5)); + } + } + } + else + { + throw new RuntimeException("PolyCompressedBytes is neither 128 or 160!"); + } + + } + + public byte[] toBytes() + { + byte[] r = new byte[MLKEMEngine.KyberPolyBytes]; + short t0, t1; + this.conditionalSubQ(); + for (int i = 0; i < MLKEMEngine.KyberN / 2; i++) + { + t0 = this.getCoeffIndex(2 * i); + t1 = this.getCoeffIndex(2 * i + 1); + r[3 * i] = (byte)(t0 >> 0); + r[3 * i + 1] = (byte)((t0 >> 8) | (t1 << 4)); + r[3 * i + 2] = (byte)(t1 >> 4); + } + + return r; + + } + + public void fromBytes(byte[] inpBytes) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN / 2; i++) + { + this.setCoeffIndex(2 * i, (short)( + ( + ((inpBytes[3 * i + 0] & 0xFF) >> 0) + | ((inpBytes[3 * i + 1] & 0xFF) << 8) + ) & 0xFFF) + ); + this.setCoeffIndex(2 * i + 1, (short)( + ( + ((inpBytes[3 * i + 1] & 0xFF) >> 4) + | (long)((inpBytes[3 * i + 2] & 0xFF) << 4) + ) & 0xFFF) + ); + } + } + + public byte[] toMsg() + { + int LOWER = MLKEMEngine.KyberQ >>> 2; + int UPPER = MLKEMEngine.KyberQ - LOWER; + + byte[] outMsg = new byte[MLKEMEngine.getKyberIndCpaMsgBytes()]; + + this.conditionalSubQ(); + + for (int i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + outMsg[i] = 0; + for (int j = 0; j < 8; j++) + { + int c_j = this.getCoeffIndex(8 * i + j); + + // KyberSlash: division by Q is not constant time. +// int t = (((c_j << 1) + (KyberEngine.KyberQ / 2)) / KyberEngine.KyberQ) & 1; + int t = ((LOWER - c_j) & (c_j - UPPER)) >>> 31; + + outMsg[i] |= (byte)(t << j); + } + } + return outMsg; + } + + public void fromMsg(byte[] msg) + { + int i, j; + short mask; + if (msg.length != MLKEMEngine.KyberN / 8) + { + throw new RuntimeException("KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!"); + } + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + for (j = 0; j < 8; j++) + { + mask = (short)((-1) * (short)(((msg[i] & 0xFF) >> j) & 1)); + this.setCoeffIndex(8 * i + j, (short)(mask & (short)((MLKEMEngine.KyberQ + 1) / 2))); + } + } + } + + public void conditionalSubQ() + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, Reduce.conditionalSubQ(this.getCoeffIndex(i))); + } + } + + public void getEta1Noise(byte[] seed, byte nonce) + { + byte[] buf = new byte[MLKEMEngine.KyberN * eta1 / 4]; + symmetric.prf(buf, seed, nonce); + CBD.mlkemCBD(this, buf, eta1); + } + + public void getEta2Noise(byte[] seed, byte nonce) + { + byte[] buf = new byte[MLKEMEngine.KyberN * eta2 / 4]; + symmetric.prf(buf, seed, nonce); + CBD.mlkemCBD(this, buf, eta2); + } + + public void polySubtract(Poly b) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, (short)(b.getCoeffIndex(i) - this.getCoeffIndex(i))); + } + } + + 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(); + } +} + diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java new file mode 100644 index 0000000000..d802840dd9 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java @@ -0,0 +1,272 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.util.Arrays; + +class PolyVec +{ + Poly[] vec; + private MLKEMEngine engine; + private int kyberK; + private int polyVecBytes; + + public PolyVec(MLKEMEngine engine) + { + this.engine = engine; + this.kyberK = engine.getKyberK(); + this.polyVecBytes = engine.getKyberPolyVecBytes(); + + this.vec = new Poly[kyberK]; + for (int i = 0; i < kyberK; i++) + { + vec[i] = new Poly(engine); + } + } + + public PolyVec() + throws Exception + { + throw new Exception("Requires Parameter"); + } + + public Poly getVectorIndex(int i) + { + return vec[i]; + } + + public void polyVecNtt() + { + int i; + for (i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).polyNtt(); + } + } + + public void polyVecInverseNttToMont() + { + for (int i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).polyInverseNttToMont(); + } + } + + public byte[] compressPolyVec() + { + int i, j, k; + + this.conditionalSubQ(); + short[] t; + byte[] r = new byte[engine.getKyberPolyVecCompressedBytes()]; + int count = 0; + if (engine.getKyberPolyVecCompressedBytes() == kyberK * 320) + { + t = new short[4]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 4; j++) + { + for (k = 0; k < 4; k++) + { + /*t[k] = (short) + ( + ( + ((this.getVectorIndex(i).getCoeffIndex(4 * j + k) << 10) + + (KyberEngine.KyberQ / 2)) + / KyberEngine.KyberQ) + & 0x3ff);*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + long t_k = this.getVectorIndex(i).getCoeffIndex(4 * j + k); + t_k <<= 10; + t_k += 1665; + t_k *= 1290167; + t_k >>= 32; + t_k &= 0x3ff; + t[k] = (short)t_k; + } + r[count + 0] = (byte)(t[0] >> 0); + r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 2)); + r[count + 2] = (byte)((t[1] >> 6) | (t[2] << 4)); + r[count + 3] = (byte)((t[2] >> 4) | (t[3] << 6)); + r[count + 4] = (byte)((t[3] >> 2)); + count += 5; + } + } + } + else if (engine.getKyberPolyVecCompressedBytes() == kyberK * 352) + { + t = new short[8]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 8; j++) + { + for (k = 0; k < 8; k++) + { + /*t[k] = (short) + ( + ( + ((this.getVectorIndex(i).getCoeffIndex(8 * j + k) << 11) + + (KyberEngine.KyberQ / 2)) + / KyberEngine.KyberQ) + & 0x7ff);*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + long t_k = this.getVectorIndex(i).getCoeffIndex(8 * j + k); + t_k <<= 11; + t_k += 1664; + t_k *= 645084; + t_k >>= 31; + t_k &= 0x7ff; + t[k] = (short)t_k; + } + r[count + 0] = (byte)((t[0] >> 0)); + r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 3)); + r[count + 2] = (byte)((t[1] >> 5) | (t[2] << 6)); + r[count + 3] = (byte)((t[2] >> 2)); + r[count + 4] = (byte)((t[2] >> 10) | (t[3] << 1)); + r[count + 5] = (byte)((t[3] >> 7) | (t[4] << 4)); + r[count + 6] = (byte)((t[4] >> 4) | (t[5] << 7)); + r[count + 7] = (byte)((t[5] >> 1)); + r[count + 8] = (byte)((t[5] >> 9) | (t[6] << 2)); + r[count + 9] = (byte)((t[6] >> 6) | (t[7] << 5)); + r[count + 10] = (byte)((t[7] >> 3)); + count += 11; + } + } + } + else + { + throw new RuntimeException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!"); + } + return r; + } + + public void decompressPolyVec(byte[] compressedPolyVecCipherText) + { + int i, j, k, count = 0; + + if (engine.getKyberPolyVecCompressedBytes() == (kyberK * 320)) + { + short[] t = new short[4]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 4; j++) + { + t[0] = (short)(((compressedPolyVecCipherText[count] & 0xFF) >> 0) | (short)((compressedPolyVecCipherText[count + 1] & 0xFF) << 8)); + t[1] = (short)(((compressedPolyVecCipherText[count + 1] & 0xFF) >> 2) | (short)((compressedPolyVecCipherText[count + 2] & 0xFF) << 6)); + t[2] = (short)(((compressedPolyVecCipherText[count + 2] & 0xFF) >> 4) | (short)((compressedPolyVecCipherText[count + 3] & 0xFF) << 4)); + t[3] = (short)(((compressedPolyVecCipherText[count + 3] & 0xFF) >> 6) | (short)((compressedPolyVecCipherText[count + 4] & 0xFF) << 2)); + count += 5; + for (k = 0; k < 4; k++) + { + this.vec[i].setCoeffIndex(4 * j + k, (short)(((t[k] & 0x3FF) * MLKEMEngine.KyberQ + 512) >> 10)); + } + } + + } + + } + else if (engine.getKyberPolyVecCompressedBytes() == (kyberK * 352)) + { + short[] t = new short[8]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 8; j++) + { + t[0] = (short)(((compressedPolyVecCipherText[count] & 0xFF) >> 0) | ((short)(compressedPolyVecCipherText[count + 1] & 0xFF) << 8)); + t[1] = (short)(((compressedPolyVecCipherText[count + 1] & 0xFF) >> 3) | ((short)(compressedPolyVecCipherText[count + 2] & 0xFF) << 5)); + t[2] = (short)(((compressedPolyVecCipherText[count + 2] & 0xFF) >> 6) | ((short)(compressedPolyVecCipherText[count + 3] & 0xFF) << 2) | ((short)((compressedPolyVecCipherText[count + 4] & 0xFF) << 10))); + t[3] = (short)(((compressedPolyVecCipherText[count + 4] & 0xFF) >> 1) | ((short)(compressedPolyVecCipherText[count + 5] & 0xFF) << 7)); + t[4] = (short)(((compressedPolyVecCipherText[count + 5] & 0xFF) >> 4) | ((short)(compressedPolyVecCipherText[count + 6] & 0xFF) << 4)); + t[5] = (short)(((compressedPolyVecCipherText[count + 6] & 0xFF) >> 7) | ((short)(compressedPolyVecCipherText[count + 7] & 0xFF) << 1) | ((short)((compressedPolyVecCipherText[count + 8] & 0xFF) << 9))); + t[6] = (short)(((compressedPolyVecCipherText[count + 8] & 0xFF) >> 2) | ((short)(compressedPolyVecCipherText[count + 9] & 0xFF) << 6)); + t[7] = (short)(((compressedPolyVecCipherText[count + 9] & 0xFF) >> 5) | ((short)(compressedPolyVecCipherText[count + 10] & 0xFF) << 3)); + count += 11; + for (k = 0; k < 8; k++) + { + this.vec[i].setCoeffIndex(8 * j + k, (short)(((t[k] & 0x7FF) * MLKEMEngine.KyberQ + 1024) >> 11)); + } + } + } + } + else + { + throw new RuntimeException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!"); + } + } + + public static void pointwiseAccountMontgomery(Poly out, PolyVec inp1, PolyVec inp2, MLKEMEngine engine) + { + int i; + Poly t = new Poly(engine); + + Poly.baseMultMontgomery(out, inp1.getVectorIndex(0), inp2.getVectorIndex(0)); + for (i = 1; i < engine.getKyberK(); i++) + { + Poly.baseMultMontgomery(t, inp1.getVectorIndex(i), inp2.getVectorIndex(i)); + out.addCoeffs(t); + } + out.reduce(); + } + + public void reducePoly() + { + int i; + for (i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).reduce(); + } + } + + public void addPoly(PolyVec b) + { + int i; + for (i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).addCoeffs(b.getVectorIndex(i)); + } + } + + public byte[] toBytes() + { + byte[] r = new byte[polyVecBytes]; + for (int i = 0; i < kyberK; i++) + { + System.arraycopy(this.vec[i].toBytes(), 0, r, i * MLKEMEngine.KyberPolyBytes, MLKEMEngine.KyberPolyBytes); + } + + return r; + } + + public void fromBytes(byte[] inputBytes) + { + for (int i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).fromBytes(Arrays.copyOfRange(inputBytes, i * MLKEMEngine.KyberPolyBytes, (i + 1) * MLKEMEngine.KyberPolyBytes)); + } + } + + public void conditionalSubQ() + { + for (int i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).conditionalSubQ(); + } + } + + public String toString() + { + StringBuffer out = new StringBuffer(); + out.append("["); + for (int i = 0; i < kyberK; i++) + { + out.append(vec[i].toString()); + if (i != kyberK - 1) + { + out.append(", "); + } + } + out.append("]"); + return out.toString(); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java new file mode 100644 index 0000000000..c0b51d2c07 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java @@ -0,0 +1,35 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +class Reduce +{ + + public static short montgomeryReduce(int a) + { + int t; + short u; + + u = (short)(a * MLKEMEngine.KyberQinv); + t = (int)(u * MLKEMEngine.KyberQ); + t = a - t; + t >>= 16; + return (short)t; + } + + public static short barretReduce(short a) + { + short t; + long shift = (((long)1) << 26); + short v = (short)((shift + (MLKEMEngine.KyberQ / 2)) / MLKEMEngine.KyberQ); + t = (short)((v * a) >> 26); + t = (short)(t * MLKEMEngine.KyberQ); + return (short)(a - t); + } + + public static short conditionalSubQ(short a) + { + a -= MLKEMEngine.KyberQ; + a += (a >> 15) & MLKEMEngine.KyberQ; + return a; + } + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java new file mode 100644 index 0000000000..40c309dedf --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java @@ -0,0 +1,94 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.crypto.digests.SHA3Digest; +import org.bouncycastle.crypto.digests.SHAKEDigest; + +abstract class Symmetric +{ + + final int xofBlockBytes; + + abstract void hash_h(byte[] out, byte[] in, int outOffset); + + abstract void hash_g(byte[] out, byte[] in); + + abstract void xofAbsorb(byte[] seed, byte x, byte y); + + abstract void xofSqueezeBlocks(byte[] out, int outOffset, int outLen); + + abstract void prf(byte[] out, byte[] key, byte nonce); + + abstract void kdf(byte[] out, byte[] in); + + Symmetric(int blockBytes) + { + this.xofBlockBytes = blockBytes; + } + + + static class ShakeSymmetric + extends Symmetric + { + private final SHAKEDigest xof; + private final SHA3Digest sha3Digest512; + private final SHA3Digest sha3Digest256; + private final SHAKEDigest shakeDigest; + + ShakeSymmetric() + { + super(168); + this.xof = new SHAKEDigest(128); + this.shakeDigest = new SHAKEDigest(256); + this.sha3Digest256 = new SHA3Digest(256); + this.sha3Digest512 = new SHA3Digest(512); + } + + @Override + void hash_h(byte[] out, byte[] in, int outOffset) + { + sha3Digest256.update(in, 0, in.length); + sha3Digest256.doFinal(out, outOffset); + } + + @Override + void hash_g(byte[] out, byte[] in) + { + sha3Digest512.update(in, 0, in.length); + sha3Digest512.doFinal(out, 0); + } + + @Override + void xofAbsorb(byte[] seed, byte a, byte b) + { + xof.reset(); + byte[] buf = new byte[seed.length + 2]; + System.arraycopy(seed, 0, buf, 0, seed.length); + buf[seed.length] = a; + buf[seed.length + 1] = b; + xof.update(buf, 0, seed.length + 2); + } + + @Override + void xofSqueezeBlocks(byte[] out, int outOffset, int outLen) + { + xof.doOutput(out, outOffset, outLen); + } + + @Override + void prf(byte[] out, byte[] seed, byte nonce) + { + byte[] extSeed = new byte[seed.length + 1]; + System.arraycopy(seed, 0, extSeed, 0, seed.length); + extSeed[seed.length] = nonce; + shakeDigest.update(extSeed, 0, extSeed.length); + shakeDigest.doFinal(out, 0, out.length); + } + + @Override + void kdf(byte[] out, byte[] in) + { + shakeDigest.update(in, 0, in.length); + shakeDigest.doFinal(out, 0, out.length); + } + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java new file mode 100644 index 0000000000..3bcbfa374f --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java @@ -0,0 +1,6 @@ +/** + * This is a small portion of bouncycastle 1.80 for MLKEM, modified to reduce dependencies. + * MLDSA and common classes are in i2p.jar (core). + * @since 0.9.67 + */ +package org.bouncycastle.pqc.crypto.mlkem; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java b/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java new file mode 100644 index 0000000000..9e07bd654d --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java @@ -0,0 +1,65 @@ +package org.bouncycastle.pqc.crypto.util; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.security.auth.DestroyFailedException; + +import org.bouncycastle.crypto.SecretWithEncapsulation; +import org.bouncycastle.util.Util; + +public class SecretWithEncapsulationImpl + implements SecretWithEncapsulation +{ + private final AtomicBoolean hasBeenDestroyed = new AtomicBoolean(false); + + private final byte[] sessionKey; + private final byte[] cipher_text; + + public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text) + { + this.sessionKey = sessionKey; + this.cipher_text = cipher_text; + } + + public byte[] getSecret() + { + byte[] clone = Util.clone(sessionKey); + + checkDestroyed(); + + return clone; + } + + public byte[] getEncapsulation() + { + byte[] clone = Util.clone(cipher_text); + + checkDestroyed(); + + return clone; + } + + public void destroy() + throws DestroyFailedException + { + if (!hasBeenDestroyed.getAndSet(true)) + { + Arrays.fill(sessionKey, (byte) 0); + Arrays.fill(cipher_text, (byte) 0); + } + } + + public boolean isDestroyed() + { + return hasBeenDestroyed.get(); + } + + void checkDestroyed() + { + if (isDestroyed()) + { + throw new IllegalStateException("data has been destroyed"); + } + } +} diff --git a/router/java/src/org/bouncycastle/util/Memoable.java b/router/java/src/org/bouncycastle/util/Memoable.java new file mode 100644 index 0000000000..3ee7a0cce1 --- /dev/null +++ b/router/java/src/org/bouncycastle/util/Memoable.java @@ -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. + *
+ * 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. + *
+ * Implementations of this method should 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); +} diff --git a/router/java/src/org/bouncycastle/util/Pack.java b/router/java/src/org/bouncycastle/util/Pack.java new file mode 100644 index 0000000000..d3a360975f --- /dev/null +++ b/router/java/src/org/bouncycastle/util/Pack.java @@ -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; + } + } + + +} diff --git a/router/java/src/org/bouncycastle/util/Util.java b/router/java/src/org/bouncycastle/util/Util.java new file mode 100644 index 0000000000..20873b3e79 --- /dev/null +++ b/router/java/src/org/bouncycastle/util/Util.java @@ -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); + } +} diff --git a/router/java/src/org/bouncycastle/util/package-info.java b/router/java/src/org/bouncycastle/util/package-info.java new file mode 100644 index 0000000000..2454b1dfeb --- /dev/null +++ b/router/java/src/org/bouncycastle/util/package-info.java @@ -0,0 +1,4 @@ +/** + * General purpose utility classes used throughout the APIs. + */ +package org.bouncycastle.util;