Migrate ChaChaCore, Poly1305, and Destroyable to Kotlin

- Translated `ChaChaCore.java` and `Poly1305.java` to Kotlin.
- Replaced JVM-specific dependencies (`java.util.Arrays`, `System.arraycopy`)
  with Kotlin Standard Library equivalents (`.fill()`, `.copyInto()`)

- Refactored `while` loops into idiomatic Kotlin `for` loops.
This commit is contained in:
yet300 2026-05-08 16:30:20 +04:00
parent 1d99434a25
commit 6ac2769f63
3 changed files with 427 additions and 456 deletions

View File

@ -19,182 +19,164 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package com.bitchat.android.noise.southernstorm.crypto;
package com.bitchat.android.noise.southernstorm.crypto
/**
* Implementation of the ChaCha20 core hash transformation.
*/
public final class ChaChaCore {
object ChaChaCore {
/**
* Hashes an input block with ChaCha20.
*
* @param output The output block, which must contain at least 16
* elements and must not overlap with the input.
* @param input The input block, which must contain at least 16
* elements.
*/
fun hash(output: IntArray, input: IntArray) {
// Copy the input to the output to start with.
input.copyInto(output, 0, 0, 16)
private ChaChaCore() {}
/**
* Hashes an input block with ChaCha20.
*
* @param output The output block, which must contain at least 16
* elements and must not overlap with the input.
* @param input The input block, which must contain at least 16
* elements.
*/
public static void hash(int[] output, int[] input)
{
int index;
// Copy the input to the output to start with.
for (index = 0; index < 16; ++index)
output[index] = input[index];
// Perform the 20 ChaCha rounds in groups of two.
for (index = 0; index < 20; index += 2) {
// Column round.
quarterRound(output, 0, 4, 8, 12);
quarterRound(output, 1, 5, 9, 13);
quarterRound(output, 2, 6, 10, 14);
quarterRound(output, 3, 7, 11, 15);
// Perform the 20 ChaCha rounds in groups of two.
for (index in 0 until 20 step 2) {
// Column round.
quarterRound(output, 0, 4, 8, 12)
quarterRound(output, 1, 5, 9, 13)
quarterRound(output, 2, 6, 10, 14)
quarterRound(output, 3, 7, 11, 15)
// Diagonal round.
quarterRound(output, 0, 5, 10, 15);
quarterRound(output, 1, 6, 11, 12);
quarterRound(output, 2, 7, 8, 13);
quarterRound(output, 3, 4, 9, 14);
}
// Diagonal round.
quarterRound(output, 0, 5, 10, 15)
quarterRound(output, 1, 6, 11, 12)
quarterRound(output, 2, 7, 8, 13)
quarterRound(output, 3, 4, 9, 14)
}
// Add the input block to the output.
for (index = 0; index < 16; ++index)
output[index] += input[index];
}
// Add the input block to the output.
for (index in 0 until 16) {
output[index] += input[index]
}
}
private static int char4(char c1, char c2, char c3, char c4)
{
return (((int)c1) & 0xFF) | ((((int)c2) & 0xFF) << 8) | ((((int)c3) & 0xFF) << 16) | ((((int)c4) & 0xFF) << 24);
}
private fun char4(c1: Char, c2: Char, c3: Char, c4: Char): Int =
(c1.code and 0xFF) or
((c2.code and 0xFF) shl 8) or
((c3.code and 0xFF) shl 16) or
((c4.code and 0xFF) shl 24)
private static int fromLittleEndian(byte[] key, int offset)
{
return (key[offset] & 0xFF) | ((key[offset + 1] & 0xFF) << 8) | ((key[offset + 2] & 0xFF) << 16) | ((key[offset + 3] & 0xFF) << 24);
}
private fun fromLittleEndian(key: ByteArray, offset: Int): Int =
(key[offset].toInt() and 0xFF) or
((key[offset + 1].toInt() and 0xFF) shl 8) or
((key[offset + 2].toInt() and 0xFF) shl 16) or
((key[offset + 3].toInt() and 0xFF) shl 24)
/**
* Initializes a ChaCha20 block with a 128-bit key.
*
* @param output The output block, which must consist of at
* least 16 words.
* @param key The buffer containing the key.
* @param offset Offset of the key in the buffer.
*/
public static void initKey128(int[] output, byte[] key, int offset)
{
output[0] = char4('e', 'x', 'p', 'a');
output[1] = char4('n', 'd', ' ', '1');
output[2] = char4('6', '-', 'b', 'y');
output[3] = char4('t', 'e', ' ', 'k');
output[4] = fromLittleEndian(key, offset);
output[5] = fromLittleEndian(key, offset + 4);
output[6] = fromLittleEndian(key, offset + 8);
output[7] = fromLittleEndian(key, offset + 12);
output[8] = output[4];
output[9] = output[5];
output[10] = output[6];
output[11] = output[7];
output[12] = 0;
output[13] = 0;
output[14] = 0;
output[15] = 0;
}
/**
* Initializes a ChaCha20 block with a 256-bit key.
*
* @param output The output block, which must consist of at
* least 16 words.
* @param key The buffer containing the key.
* @param offset Offset of the key in the buffer.
*/
public static void initKey256(int[] output, byte[] key, int offset)
{
output[0] = char4('e', 'x', 'p', 'a');
output[1] = char4('n', 'd', ' ', '3');
output[2] = char4('2', '-', 'b', 'y');
output[3] = char4('t', 'e', ' ', 'k');
output[4] = fromLittleEndian(key, offset);
output[5] = fromLittleEndian(key, offset + 4);
output[6] = fromLittleEndian(key, offset + 8);
output[7] = fromLittleEndian(key, offset + 12);
output[8] = fromLittleEndian(key, offset + 16);
output[9] = fromLittleEndian(key, offset + 20);
output[10] = fromLittleEndian(key, offset + 24);
output[11] = fromLittleEndian(key, offset + 28);
output[12] = 0;
output[13] = 0;
output[14] = 0;
output[15] = 0;
}
/**
* Initializes a ChaCha20 block with a 128-bit key.
*
* @param output The output block, which must consist of at
* least 16 words.
* @param key The buffer containing the key.
* @param offset Offset of the key in the buffer.
*/
fun initKey128(output: IntArray, key: ByteArray, offset: Int) {
output[0] = char4('e', 'x', 'p', 'a')
output[1] = char4('n', 'd', ' ', '1')
output[2] = char4('6', '-', 'b', 'y')
output[3] = char4('t', 'e', ' ', 'k')
output[4] = fromLittleEndian(key, offset)
output[5] = fromLittleEndian(key, offset + 4)
output[6] = fromLittleEndian(key, offset + 8)
output[7] = fromLittleEndian(key, offset + 12)
output[8] = output[4]
output[9] = output[5]
output[10] = output[6]
output[11] = output[7]
output[12] = 0
output[13] = 0
output[14] = 0
output[15] = 0
}
/**
* Initializes the 64-bit initialization vector in a ChaCha20 block.
*
* @param output The output block, which must consist of at
* least 16 words and must have been initialized by initKey256()
* or initKey128().
* @param iv The 64-bit initialization vector value.
*
* The counter portion of the output block is set to zero.
*/
public static void initIV(int[] output, long iv)
{
output[12] = 0;
output[13] = 0;
output[14] = (int)iv;
output[15] = (int)(iv >> 32);
}
/**
* Initializes the 64-bit initialization vector and counter in a ChaCha20 block.
*
* @param output The output block, which must consist of at
* least 16 words and must have been initialized by initKey256()
* or initKey128().
* @param iv The 64-bit initialization vector value.
* @param counter The 64-bit counter value.
*/
public static void initIV(int[] output, long iv, long counter)
{
output[12] = (int)counter;
output[13] = (int)(counter >> 32);
output[14] = (int)iv;
output[15] = (int)(iv >> 32);
}
private static int leftRotate16(int v)
{
return v << 16 | (v >>> 16);
}
/**
* Initializes a ChaCha20 block with a 256-bit key.
*
* @param output The output block, which must consist of at
* least 16 words.
* @param key The buffer containing the key.
* @param offset Offset of the key in the buffer.
*/
fun initKey256(output: IntArray, key: ByteArray, offset: Int) {
output[0] = char4('e', 'x', 'p', 'a')
output[1] = char4('n', 'd', ' ', '3')
output[2] = char4('2', '-', 'b', 'y')
output[3] = char4('t', 'e', ' ', 'k')
output[4] = fromLittleEndian(key, offset)
output[5] = fromLittleEndian(key, offset + 4)
output[6] = fromLittleEndian(key, offset + 8)
output[7] = fromLittleEndian(key, offset + 12)
output[8] = fromLittleEndian(key, offset + 16)
output[9] = fromLittleEndian(key, offset + 20)
output[10] = fromLittleEndian(key, offset + 24)
output[11] = fromLittleEndian(key, offset + 28)
output[12] = 0
output[13] = 0
output[14] = 0
output[15] = 0
}
private static int leftRotate12(int v)
{
return v << 12 | (v >>> 20);
}
/**
* Initializes the 64-bit initialization vector in a ChaCha20 block.
*
* @param output The output block, which must consist of at
* least 16 words and must have been initialized by initKey256()
* or initKey128().
* @param iv The 64-bit initialization vector value.
*
* The counter portion of the output block is set to zero.
*/
fun initIV(output: IntArray, iv: Long) {
output[12] = 0
output[13] = 0
output[14] = iv.toInt()
output[15] = (iv shr 32).toInt()
}
private static int leftRotate8(int v)
{
return v << 8 | (v >>> 24);
}
/**
* Initializes the 64-bit initialization vector and counter in a ChaCha20 block.
*
* @param output The output block, which must consist of at
* least 16 words and must have been initialized by initKey256()
* or initKey128().
* @param iv The 64-bit initialization vector value.
* @param counter The 64-bit counter value.
*/
fun initIV(output: IntArray, iv: Long, counter: Long) {
output[12] = counter.toInt()
output[13] = (counter shr 32).toInt()
output[14] = iv.toInt()
output[15] = (iv shr 32).toInt()
}
private static int leftRotate7(int v)
{
return v << 7 | (v >>> 25);
}
private fun leftRotate16(v: Int): Int = (v shl 16) or (v ushr 16)
private static void quarterRound(int[] v, int a, int b, int c, int d)
{
v[a] += v[b];
v[d] = leftRotate16(v[d] ^ v[a]);
v[c] += v[d];
v[b] = leftRotate12(v[b] ^ v[c]);
v[a] += v[b];
v[d] = leftRotate8(v[d] ^ v[a]);
v[c] += v[d];
v[b] = leftRotate7(v[b] ^ v[c]);
}
private fun leftRotate12(v: Int): Int = (v shl 12) or (v ushr 20)
private fun leftRotate8(v: Int): Int = (v shl 8) or (v ushr 24)
private fun leftRotate7(v: Int): Int = (v shl 7) or (v ushr 25)
private fun quarterRound(v: IntArray, a: Int, b: Int, c: Int, d: Int) {
v[a] += v[b]
v[d] = leftRotate16(v[d] xor v[a])
v[c] += v[d]
v[b] = leftRotate12(v[b] xor v[c])
v[a] += v[b]
v[d] = leftRotate8(v[d] xor v[a])
v[c] += v[d]
v[b] = leftRotate7(v[b] xor v[c])
}
}

View File

@ -19,309 +19,299 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package com.bitchat.android.noise.southernstorm.crypto
package com.bitchat.android.noise.southernstorm.crypto;
import com.bitchat.android.noise.southernstorm.protocol.Destroyable
import java.util.Arrays;
import com.bitchat.android.noise.southernstorm.protocol.Destroyable;
/**
* Simple implementation of the Poly1305 message authenticator.
*/
public final class Poly1305 implements Destroyable {
class Poly1305 : Destroyable {
// The 130-bit intermediate values are broken up into five 26-bit words.
private val nonce = ByteArray(16)
private val block = ByteArray(16)
private val h = IntArray(5)
private val r = IntArray(5)
private val c = IntArray(5)
private val t = LongArray(10)
private var posn = 0
// The 130-bit intermediate values are broken up into five 26-bit words.
private byte[] nonce;
private byte[] block;
private int[] h;
private int[] r;
private int[] c;
private long[] t;
private int posn;
/**
* Constructs a new Poly1305 message authenticator.
*/
public Poly1305()
{
nonce = new byte [16];
block = new byte [16];
h = new int [5];
r = new int [5];
c = new int [5];
t = new long [10];
posn = 0;
}
/**
* Resets the message authenticator with a new key.
*
* @param key The buffer containing the 32 byte key.
* @param offset The offset into the buffer of the first key byte.
*/
fun reset(key: ByteArray, offset: Int) {
/**
* Resets the message authenticator with a new key.
*
* @param key The buffer containing the 32 byte key.
* @param offset The offset into the buffer of the first key byte.
*/
public void reset(byte[] key, int offset)
{
System.arraycopy(key, offset + 16, nonce, 0, 16);
Arrays.fill(h, 0);
posn = 0;
// Convert the first 16 bytes of the key into a 130-bit
// "r" value while masking off the bits that we don't need.
r[0] = ((key[offset] & 0xFF)) |
((key[offset + 1] & 0xFF) << 8) |
((key[offset + 2] & 0xFF) << 16) |
((key[offset + 3] & 0x03) << 24);
r[1] = ((key[offset + 3] & 0x0C) >> 2) |
((key[offset + 4] & 0xFC) << 6) |
((key[offset + 5] & 0xFF) << 14) |
((key[offset + 6] & 0x0F) << 22);
r[2] = ((key[offset + 6] & 0xF0) >> 4) |
((key[offset + 7] & 0x0F) << 4) |
((key[offset + 8] & 0xFC) << 12) |
((key[offset + 9] & 0x3F) << 20);
r[3] = ((key[offset + 9] & 0xC0) >> 6) |
((key[offset + 10] & 0xFF) << 2) |
((key[offset + 11] & 0x0F) << 10) |
((key[offset + 12] & 0xFC) << 18);
r[4] = ((key[offset + 13] & 0xFF)) |
((key[offset + 14] & 0xFF) << 8) |
((key[offset + 15] & 0x0F) << 16);
}
// Kotlin KMP Array Copy
key.copyInto(destination = nonce, destinationOffset = 0, startIndex = offset + 16, endIndex = offset + 32)
/**
* Updates the message authenticator with more input data.
*
* @param data The buffer containing the input data.
* @param offset The offset of the first byte of input.
* @param length The number of bytes of input.
*/
public void update(byte[] data, int offset, int length)
{
while (length > 0) {
if (posn == 0 && length >= 16) {
// We can process the chunk directly out of the input buffer.
processChunk(data, offset, false);
offset += 16;
length -= 16;
} else {
// Collect up partial bytes in the block buffer.
int temp = 16 - posn;
if (temp > length)
temp = length;
System.arraycopy(data, offset, block, posn, temp);
offset += temp;
length -= temp;
posn += temp;
if (posn >= 16) {
processChunk(block, 0, false);
posn = 0;
}
}
}
}
h.fill(0)
posn = 0
/**
* Pads the input with zeroes to a multiple of 16 bytes.
*/
public void pad()
{
if (posn != 0) {
Arrays.fill(block, posn, 16, (byte)0);
processChunk(block, 0, false);
posn = 0;
}
}
/**
* Finishes the message authenticator and returns the 16-byte token.
*
* @param token The buffer to receive the token.
* @param offset The offset of the token in the buffer.
*/
public void finish(byte[] token, int offset)
{
// Pad and flush the final chunk.
if (posn != 0) {
block[posn] = (byte)1;
Arrays.fill(block, posn + 1, 16, (byte)0);
processChunk(block, 0, true);
}
// At this point, processChunk() has left h as a partially reduced
// result that is less than (2^130 - 5) * 6. Perform one more
// reduction and a trial subtraction to produce the final result.
// Convert the first 16 bytes of the key into a 130-bit
// "r" value while masking off the bits that we don't need.
r[0] = ((key[offset].toInt() and 0xFF)) or
((key[offset + 1].toInt() and 0xFF) shl 8) or
((key[offset + 2].toInt() and 0xFF) shl 16) or
((key[offset + 3].toInt() and 0x03) shl 24)
r[1] = ((key[offset + 3].toInt() and 0x0C) shr 2) or
((key[offset + 4].toInt() and 0xFC) shl 6) or
((key[offset + 5].toInt() and 0xFF) shl 14) or
((key[offset + 6].toInt() and 0x0F) shl 22)
r[2] = ((key[offset + 6].toInt() and 0xF0) shr 4) or
((key[offset + 7].toInt() and 0x0F) shl 4) or
((key[offset + 8].toInt() and 0xFC) shl 12) or
((key[offset + 9].toInt() and 0x3F) shl 20)
r[3] = ((key[offset + 9].toInt() and 0xC0) shr 6) or
((key[offset + 10].toInt() and 0xFF) shl 2) or
((key[offset + 11].toInt() and 0x0F) shl 10) or
((key[offset + 12].toInt() and 0xFC) shl 18)
r[4] = ((key[offset + 13].toInt() and 0xFF)) or
((key[offset + 14].toInt() and 0xFF) shl 8) or
((key[offset + 15].toInt() and 0x0F) shl 16)
}
// Multiply the high bits of h by 5 and add them to the 130 low bits.
int carry = (h[4] >> 26) * 5 + h[0];
h[0] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[1];
h[1] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[2];
h[2] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[3];
h[3] = carry & 0x03FFFFFF;
h[4] = (carry >> 26) + (h[4] & 0x03FFFFFF);
/**
* Updates the message authenticator with more input data.
*
* @param data The buffer containing the input data.
* @param offset The offset of the first byte of input.
* @param length The number of bytes of input.
*/
fun update(data: ByteArray, offset: Int, length: Int) {
var offset = offset
var length = length
while (length > 0) {
if (posn == 0 && length >= 16) {
// We can process the chunk directly out of the input buffer.
processChunk(data, offset, false)
offset += 16
length -= 16
} else {
// Collect up partial bytes in the block buffer.
var temp = 16 - posn
if (temp > length) temp = length
System.arraycopy(data, offset, block, posn, temp)
offset += temp
length -= temp
posn += temp
if (posn >= 16) {
processChunk(block, 0, false)
posn = 0
}
}
}
}
// Subtract (2^130 - 5) from h by computing c = h + 5 - 2^130.
// The "minus 2^130" step is implicit.
carry = 5 + h[0];
c[0] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[1];
c[1] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[2];
c[2] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[3];
c[3] = carry & 0x03FFFFFF;
c[4] = (carry >> 26) + h[4];
/**
* Pads the input with zeroes to a multiple of 16 bytes.
*/
fun pad() {
if (posn != 0) {
// Kotlin Array Fill
block.fill(0.toByte(), fromIndex = posn, toIndex = 16)
processChunk(block, 0, false)
posn = 0
}
}
// Borrow occurs if bit 2^130 of the previous c result is zero.
// Carefully turn this into a selection mask so we can select either
// h or c as the final result.
int mask = -((c[4] >> 26) & 0x01);
int nmask = ~mask;
h[0] = (h[0] & nmask) | (c[0] & mask);
h[1] = (h[1] & nmask) | (c[1] & mask);
h[2] = (h[2] & nmask) | (c[2] & mask);
h[3] = (h[3] & nmask) | (c[3] & mask);
h[4] = (h[4] & nmask) | (c[4] & mask);
// Convert h into little-endian in the block buffer.
block[0] = (byte)(h[0]);
block[1] = (byte)(h[0] >> 8);
block[2] = (byte)(h[0] >> 16);
block[3] = (byte)((h[0] >> 24) | (h[1] << 2));
block[4] = (byte)(h[1] >> 6);
block[5] = (byte)(h[1] >> 14);
block[6] = (byte)((h[1] >> 22) | (h[2] << 4));
block[7] = (byte)(h[2] >> 4);
block[8] = (byte)(h[2] >> 12);
block[9] = (byte)((h[2] >> 20) | (h[3] << 6));
block[10] = (byte)(h[3] >> 2);
block[11] = (byte)(h[3] >> 10);
block[12] = (byte)(h[3] >> 18);
block[13] = (byte)(h[4]);
block[14] = (byte)(h[4] >> 8);
block[15] = (byte)(h[4] >> 16);
// Add the nonce and write the final result to the token.
carry = (nonce[0] & 0xFF) + (block[0] & 0xFF);
token[offset] = (byte)carry;
for (int x = 1; x < 16; ++x) {
carry = (carry >> 8) + (nonce[x] & 0xFF) + (block[x] & 0xFF);
token[offset + x] = (byte)carry;
}
}
/**
* Finishes the message authenticator and returns the 16-byte token.
*
* @param token The buffer to receive the token.
* @param offset The offset of the token in the buffer.
*/
fun finish(token: ByteArray, offset: Int) {
// Pad and flush the final chunk.
if (posn != 0) {
block[posn] = 1.toByte()
block.fill(0.toByte(), fromIndex = posn + 1, toIndex = 16)
processChunk(block, 0, true)
}
/**
* Processes the next chunk of input data.
*
* @param chunk Buffer containing the input data chunk.
* @param offset Offset of the first byte of the 16-byte chunk.
* @param finalChunk Set to true if this is the final chunk.
*/
private void processChunk(byte[] chunk, int offset, boolean finalChunk)
{
int x;
// Unpack the 128-bit chunk into a 130-bit value in "c".
c[0] = ((chunk[offset] & 0xFF)) |
((chunk[offset + 1] & 0xFF) << 8) |
((chunk[offset + 2] & 0xFF) << 16) |
((chunk[offset + 3] & 0x03) << 24);
c[1] = ((chunk[offset + 3] & 0xFC) >> 2) |
((chunk[offset + 4] & 0xFF) << 6) |
((chunk[offset + 5] & 0xFF) << 14) |
((chunk[offset + 6] & 0x0F) << 22);
c[2] = ((chunk[offset + 6] & 0xF0) >> 4) |
((chunk[offset + 7] & 0xFF) << 4) |
((chunk[offset + 8] & 0xFF) << 12) |
((chunk[offset + 9] & 0x3F) << 20);
c[3] = ((chunk[offset + 9] & 0xC0) >> 6) |
((chunk[offset + 10] & 0xFF) << 2) |
((chunk[offset + 11] & 0xFF) << 10) |
((chunk[offset + 12] & 0xFF) << 18);
c[4] = ((chunk[offset + 13] & 0xFF)) |
((chunk[offset + 14] & 0xFF) << 8) |
((chunk[offset + 15] & 0xFF) << 16);
if (!finalChunk)
c[4] |= (1 << 24);
// Compute h = ((h + c) * r) mod (2^130 - 5)
// Start with h += c. We assume that h is less than (2^130 - 5) * 6
// and that c is less than 2^129, so the result will be less than 2^133.
h[0] += c[0];
h[1] += c[1];
h[2] += c[2];
h[3] += c[3];
h[4] += c[4];
// Multiply h by r. We know that r is less than 2^124 because the
// top 4 bits were AND-ed off by reset(). That makes h * r less
// than 2^257. Which is less than the (2^130 - 6)^2 we want for
// the modulo reduction step that follows. The intermediate limbs
// are 52 bits in size, which allows us to collect up carries in the
// extra bits of the 64 bit longs and propagate them later.
long hv = h[0];
t[0] = hv * r[0];
t[1] = hv * r[1];
t[2] = hv * r[2];
t[3] = hv * r[3];
t[4] = hv * r[4];
for (x = 1; x < 5; ++x) {
hv = h[x];
t[x] += hv * r[0];
t[x + 1] += hv * r[1];
t[x + 2] += hv * r[2];
t[x + 3] += hv * r[3];
t[x + 4] = hv * r[4];
}
// Propagate carries to convert the t limbs from 52-bit back to 26-bit.
// The low bits are placed into h and the high bits are placed into c.
h[0] = ((int)t[0]) & 0x03FFFFFF;
hv = t[1] + (t[0] >> 26);
h[1] = ((int)hv) & 0x03FFFFFF;
hv = t[2] + (hv >> 26);
h[2] = ((int)hv) & 0x03FFFFFF;
hv = t[3] + (hv >> 26);
h[3] = ((int)hv) & 0x03FFFFFF;
hv = t[4] + (hv >> 26);
h[4] = ((int)hv) & 0x03FFFFFF;
hv = t[5] + (hv >> 26);
c[0] = ((int)hv) & 0x03FFFFFF;
hv = t[6] + (hv >> 26);
c[1] = ((int)hv) & 0x03FFFFFF;
hv = t[7] + (hv >> 26);
c[2] = ((int)hv) & 0x03FFFFFF;
hv = t[8] + (hv >> 26);
c[3] = ((int)hv) & 0x03FFFFFF;
hv = t[9] + (hv >> 26);
c[4] = ((int)hv);
// Reduce h * r modulo (2^130 - 5) by multiplying the high 130 bits by 5
// and adding them to the low 130 bits. This will leave the result at
// most 5 subtractions away from the answer we want.
int carry = h[0] + c[0] * 5;
h[0] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[1] + c[1] * 5;
h[1] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[2] + c[2] * 5;
h[2] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[3] + c[3] * 5;
h[3] = carry & 0x03FFFFFF;
carry = (carry >> 26) + h[4] + c[4] * 5;
h[4] = carry;
}
// At this point, processChunk() has left h as a partially reduced
// result that is less than (2^130 - 5) * 6. Perform one more
// reduction and a trial subtraction to produce the final result.
@Override
public void destroy() {
Arrays.fill(nonce, (byte)0);
Arrays.fill(block, (byte)0);
Arrays.fill(h, (int)0);
Arrays.fill(r, (int)0);
Arrays.fill(c, (int)0);
Arrays.fill(t, (long)0);
}
// Multiply the high bits of h by 5 and add them to the 130 low bits.
var carry = (h[4] shr 26) * 5 + h[0]
h[0] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[1]
h[1] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[2]
h[2] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[3]
h[3] = carry and 0x03FFFFFF
h[4] = (carry shr 26) + (h[4] and 0x03FFFFFF)
// Subtract (2^130 - 5) from h by computing c = h + 5 - 2^130.
// The "minus 2^130" step is implicit.
carry = 5 + h[0]
c[0] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[1]
c[1] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[2]
c[2] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[3]
c[3] = carry and 0x03FFFFFF
c[4] = (carry shr 26) + h[4]
// Borrow occurs if bit 2^130 of the previous c result is zero.
// Carefully turn this into a selection mask so we can select either
// h or c as the final result.
val mask = -((c[4] shr 26) and 0x01)
val nmask = mask.inv()
h[0] = (h[0] and nmask) or (c[0] and mask)
h[1] = (h[1] and nmask) or (c[1] and mask)
h[2] = (h[2] and nmask) or (c[2] and mask)
h[3] = (h[3] and nmask) or (c[3] and mask)
h[4] = (h[4] and nmask) or (c[4] and mask)
// Convert h into little-endian in the block buffer.
block[0] = (h[0]).toByte()
block[1] = (h[0] shr 8).toByte()
block[2] = (h[0] shr 16).toByte()
block[3] = ((h[0] shr 24) or (h[1] shl 2)).toByte()
block[4] = (h[1] shr 6).toByte()
block[5] = (h[1] shr 14).toByte()
block[6] = ((h[1] shr 22) or (h[2] shl 4)).toByte()
block[7] = (h[2] shr 4).toByte()
block[8] = (h[2] shr 12).toByte()
block[9] = ((h[2] shr 20) or (h[3] shl 6)).toByte()
block[10] = (h[3] shr 2).toByte()
block[11] = (h[3] shr 10).toByte()
block[12] = (h[3] shr 18).toByte()
block[13] = (h[4]).toByte()
block[14] = (h[4] shr 8).toByte()
block[15] = (h[4] shr 16).toByte()
// Add the nonce and write the final result to the token.
carry = (nonce[0].toInt() and 0xFF) + (block[0].toInt() and 0xFF)
token[offset] = carry.toByte()
for (x in 1..15) {
carry = (carry shr 8) + (nonce[x].toInt() and 0xFF) + (block[x].toInt() and 0xFF)
token[offset + x] = carry.toByte()
}
}
/**
* Processes the next chunk of input data.
*
* @param chunk Buffer containing the input data chunk.
* @param offset Offset of the first byte of the 16-byte chunk.
* @param finalChunk Set to true if this is the final chunk.
*/
private fun processChunk(chunk: ByteArray, offset: Int, finalChunk: Boolean) {
// Unpack the 128-bit chunk into a 130-bit value in "c".
c[0] = ((chunk[offset].toInt() and 0xFF)) or
((chunk[offset + 1].toInt() and 0xFF) shl 8) or
((chunk[offset + 2].toInt() and 0xFF) shl 16) or
((chunk[offset + 3].toInt() and 0x03) shl 24)
c[1] = ((chunk[offset + 3].toInt() and 0xFC) shr 2) or
((chunk[offset + 4].toInt() and 0xFF) shl 6) or
((chunk[offset + 5].toInt() and 0xFF) shl 14) or
((chunk[offset + 6].toInt() and 0x0F) shl 22)
c[2] = ((chunk[offset + 6].toInt() and 0xF0) shr 4) or
((chunk[offset + 7].toInt() and 0xFF) shl 4) or
((chunk[offset + 8].toInt() and 0xFF) shl 12) or
((chunk[offset + 9].toInt() and 0x3F) shl 20)
c[3] = ((chunk[offset + 9].toInt() and 0xC0) shr 6) or
((chunk[offset + 10].toInt() and 0xFF) shl 2) or
((chunk[offset + 11].toInt() and 0xFF) shl 10) or
((chunk[offset + 12].toInt() and 0xFF) shl 18)
c[4] = ((chunk[offset + 13].toInt() and 0xFF)) or
((chunk[offset + 14].toInt() and 0xFF) shl 8) or
((chunk[offset + 15].toInt() and 0xFF) shl 16)
if (!finalChunk) c[4] = c[4] or (1 shl 24)
// Compute h = ((h + c) * r) mod (2^130 - 5)
// Start with h += c. We assume that h is less than (2^130 - 5) * 6
// and that c is less than 2^129, so the result will be less than 2^133.
h[0] += c[0]
h[1] += c[1]
h[2] += c[2]
h[3] += c[3]
h[4] += c[4]
// Multiply h by r. We know that r is less than 2^124 because the
// top 4 bits were AND-ed off by reset(). That makes h * r less
// than 2^257. Which is less than the (2^130 - 6)^2 we want for
// the modulo reduction step that follows. The intermediate limbs
// are 52 bits in size, which allows us to collect up carries in the
// extra bits of the 64 bit longs and propagate them later.
var hv = h[0].toLong()
t[0] = hv * r[0]
t[1] = hv * r[1]
t[2] = hv * r[2]
t[3] = hv * r[3]
t[4] = hv * r[4]
for (x in 1 until 5) {
hv = h[x].toLong()
t[x] += hv * r[0]
t[x + 1] += hv * r[1]
t[x + 2] += hv * r[2]
t[x + 3] += hv * r[3]
t[x + 4] = hv * r[4]
}
// Propagate carries to convert the t limbs from 52-bit back to 26-bit.
// The low bits are placed into h and the high bits are placed into c.
h[0] = (t[0].toInt()) and 0x03FFFFFF
hv = t[1] + (t[0] shr 26)
h[1] = (hv.toInt()) and 0x03FFFFFF
hv = t[2] + (hv shr 26)
h[2] = (hv.toInt()) and 0x03FFFFFF
hv = t[3] + (hv shr 26)
h[3] = (hv.toInt()) and 0x03FFFFFF
hv = t[4] + (hv shr 26)
h[4] = (hv.toInt()) and 0x03FFFFFF
hv = t[5] + (hv shr 26)
c[0] = (hv.toInt()) and 0x03FFFFFF
hv = t[6] + (hv shr 26)
c[1] = (hv.toInt()) and 0x03FFFFFF
hv = t[7] + (hv shr 26)
c[2] = (hv.toInt()) and 0x03FFFFFF
hv = t[8] + (hv shr 26)
c[3] = (hv.toInt()) and 0x03FFFFFF
hv = t[9] + (hv shr 26)
c[4] = (hv.toInt())
// Reduce h * r modulo (2^130 - 5) by multiplying the high 130 bits by 5
// and adding them to the low 130 bits. This will leave the result at
// most 5 subtractions away from the answer we want.
var carry = h[0] + c[0] * 5
h[0] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[1] + c[1] * 5
h[1] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[2] + c[2] * 5
h[2] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[3] + c[3] * 5
h[3] = carry and 0x03FFFFFF
carry = (carry shr 26) + h[4] + c[4] * 5
h[4] = carry
}
override fun destroy() {
// Kotlin Array Fill
nonce.fill(0.toByte())
block.fill(0.toByte())
h.fill(0)
r.fill(0)
c.fill(0)
t.fill(0L)
}
}

View File

@ -20,7 +20,7 @@
* DEALINGS IN THE SOFTWARE.
*/
package com.bitchat.android.noise.southernstorm.protocol;
package com.bitchat.android.noise.southernstorm.protocol
/**
* Interface for objects that implement destroying.
@ -34,10 +34,9 @@ package com.bitchat.android.noise.southernstorm.protocol;
* The Noise.destroy() function can help with destroying byte arrays
* that hold sensitive values.
*/
public interface Destroyable {
/**
* Destroys all sensitive state in the current object.
*/
void destroy();
interface Destroyable {
/**
* Destroys all sensitive state in the current object.
*/
fun destroy()
}