diff --git a/LICENSE_NOISEJAVA.txt b/LICENSE_NOISEJAVA.txt new file mode 100644 index 0000000..a606b2d --- /dev/null +++ b/LICENSE_NOISEJAVA.txt @@ -0,0 +1,19 @@ +Copyright (C) 2016 Southern Storm Software, Pty Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index e7a8681..faab4ff 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,6 @@ Read more here: https://esphome.io/components/api#advantages-over-mqtt ## FAQ -- My ESPHome thing reports `COMMUNICATION_ERROR: Parse error`. What is wrong? - - > This is most likely because you have encryption set on your ESPHome device. The binding does not support encrypted - > connections yet, so you need to disable encryption on your device. - - I get warnings like `No device_class reported by sensor ''. Add device_class to sensor configuration in ESPHome. Defaulting to plain Number without dimension` @@ -54,10 +49,8 @@ Also see https://community.openhab.org/t/esphome-binding-for-the-native-api/1468 - `device`: A device flashed with https://esphome.io/ firmware. -## Limitations as of 2024-02-21 +## Limitations as of 2024-03-28 -- **Only plaintext connections with password** are supported, not encrypted. This is insecure and should not be used on - untrusted networks, but is your only option at this time. I *intend* to add encryption. - Only - `sensor`, - `binary_sensor`, @@ -79,13 +72,16 @@ The binding uses mDNS to automatically discover devices on the network. ### `device` Thing Configuration -| Name | Type | Description | Default | Required | Advanced | -|-------------------|-----------|--------------------------------------------------------------------------------|---------|----------|----------| -| `hostname` | `text` | Hostname or IP address of the device. Typically something like 'myboard.local' | N/A | yes | no | -| `password` | `text` | Password to access the device if password protected | N/A | no | no | -| `port` | `integer` | IP Port of the device | 6053 | no | no | -| `pingInterval` | `integer` | Seconds between sending ping requests to device to check if alive | 10 | no | yes | -| `maxPingTimeouts` | `integer` | Number of missed ping requests before deeming device unresponsive. | 4 | no | yes | +| Name | Type | Description | Default | Required | Advanced | +|-------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------|----------|----------|----------| +| `hostname` | `text` | Hostname or IP address of the device. Typically something like 'myboard.local' | N/A | yes | no | +| `port` | `integer` | IP Port of the device | 6053 | no | no | +| `encryptionKey` | `text` | Encryption key as defined in `api: encryption: key: `. See https://esphome.io/components/api#configuration-variables | N/A | no | no | +| ~~`password`~~ | `text` | Password to access the device if password protected. **DEPRECATED. Use `encryptionKey` instead** | N/A | no | no | +| `pingInterval` | `integer` | Seconds between sending ping requests to device to check if alive | 10 | no | yes | +| `maxPingTimeouts` | `integer` | Number of missed ping requests before deeming device unresponsive. | 4 | no | yes | +| `server` | `text` | Expected name of ESPHome. Used to ensure that we're communicating with the correct device | | no | yes | +| `logPrefix` | `text` | Log prefix to use for this device. | hostname | no | yes | ## Channels @@ -96,7 +92,7 @@ Channels are auto-generated based on actual device configuration. ### Thing Configuration ``` -esphome:device:esp1 "ESPHome Test card 1" [ hostname="testkort1.local", password="MyPassword", pingInterval=10, maxPingTimeouts=4] +esphome:device:esp1 "ESPHome Test card 1" [ hostname="testkort1.local", encryptionKey="JVWAgubY1nCe3x/5xeyMBfaN9y68OOUMh5dACIeVmjk=", pingInterval=10, maxPingTimeouts=4] ``` ### Item Configuration diff --git a/pom.xml b/pom.xml index e7c9bcc..98649b4 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ no.seime.openhab.binding.esphome - Seime Add-ons :: Bundles :: ESPHome Native + Seime Add-ons :: Bundles :: ESPHome Native API 17 @@ -50,6 +50,7 @@ provided + org.slf4j slf4j-api @@ -78,6 +79,18 @@ 1.2.13 test + + junit + junit + 4.13.1 + test + + + javax.xml.bind + jaxb-api + 2.3.0 + test + diff --git a/src/main/java/com/southernstorm/noise/crypto/Blake2bMessageDigest.java b/src/main/java/com/southernstorm/noise/crypto/Blake2bMessageDigest.java new file mode 100644 index 0000000..25d510d --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/Blake2bMessageDigest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.southernstorm.noise.protocol.Destroyable; + +/** + * Fallback implementation of BLAKE2b for the Noise library. + * + * This implementation only supports message digesting with an output + * length of 64 bytes and a limit of 2^64 - 1 bytes of input. + * Keyed hashing and variable-length digests are not supported. + */ +public class Blake2bMessageDigest extends MessageDigest implements Destroyable { + + private long[] h; + private byte[] block; + private long[] m; + private long[] v; + private long length; + private int posn; + + /** + * Constructs a new BLAKE2b message digest object. + */ + public Blake2bMessageDigest() { + super("BLAKE2B-512"); + h = new long[8]; + block = new byte[128]; + m = new long[16]; + v = new long[16]; + engineReset(); + } + + @Override + protected byte[] engineDigest() { + byte[] digest = new byte[64]; + try { + engineDigest(digest, 0, 64); + } catch (DigestException e) { + // Shouldn't happen, but just in case. + Arrays.fill(digest, (byte) 0); + } + return digest; + } + + @Override + protected int engineDigest(byte[] buf, int offset, int len) throws DigestException { + if (len < 64) + throw new DigestException("Invalid digest length for BLAKE2b"); + Arrays.fill(block, posn, 128, (byte) 0); + transform(-1); + for (int index = 0; index < 8; ++index) { + long value = h[index]; + buf[offset++] = (byte) value; + buf[offset++] = (byte) (value >> 8); + buf[offset++] = (byte) (value >> 16); + buf[offset++] = (byte) (value >> 24); + buf[offset++] = (byte) (value >> 32); + buf[offset++] = (byte) (value >> 40); + buf[offset++] = (byte) (value >> 48); + buf[offset++] = (byte) (value >> 56); + } + return 32; + } + + @Override + protected int engineGetDigestLength() { + return 64; + } + + @Override + protected void engineReset() { + h[0] = 0x6a09e667f3bcc908L ^ 0x01010040; + h[1] = 0xbb67ae8584caa73bL; + h[2] = 0x3c6ef372fe94f82bL; + h[3] = 0xa54ff53a5f1d36f1L; + h[4] = 0x510e527fade682d1L; + h[5] = 0x9b05688c2b3e6c1fL; + h[6] = 0x1f83d9abfb41bd6bL; + h[7] = 0x5be0cd19137e2179L; + length = 0; + posn = 0; + } + + @Override + protected void engineUpdate(byte input) { + if (posn >= 128) { + transform(0); + posn = 0; + } + block[posn++] = input; + ++length; + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + while (len > 0) { + if (posn >= 128) { + transform(0); + posn = 0; + } + int temp = (128 - posn); + if (temp > len) + temp = len; + System.arraycopy(input, offset, block, posn, temp); + posn += temp; + length += temp; + offset += temp; + len -= temp; + } + } + + // Permutation on the message input state for BLAKE2b. + static final byte[][] sigma = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, }; + + private void transform(long f0) { + int index; + int offset; + + // Unpack the input block from little-endian into host-endian. + for (index = 0, offset = 0; index < 16; ++index, offset += 8) { + m[index] = (block[offset] & 0xFFL) | ((block[offset + 1] & 0xFFL) << 8) + | ((block[offset + 2] & 0xFFL) << 16) | ((block[offset + 3] & 0xFFL) << 24) + | ((block[offset + 4] & 0xFFL) << 32) | ((block[offset + 5] & 0xFFL) << 40) + | ((block[offset + 6] & 0xFFL) << 48) | ((block[offset + 7] & 0xFFL) << 56); + } + + // Format the block to be hashed. + for (index = 0; index < 8; ++index) + v[index] = h[index]; + v[8] = 0x6a09e667f3bcc908L; + v[9] = 0xbb67ae8584caa73bL; + v[10] = 0x3c6ef372fe94f82bL; + v[11] = 0xa54ff53a5f1d36f1L; + v[12] = 0x510e527fade682d1L ^ length; + v[13] = 0x9b05688c2b3e6c1fL; + v[14] = 0x1f83d9abfb41bd6bL ^ f0; + v[15] = 0x5be0cd19137e2179L; + + // Perform the 12 BLAKE2b rounds. + for (index = 0; index < 12; ++index) { + // Column round. + quarterRound(0, 4, 8, 12, 0, index); + quarterRound(1, 5, 9, 13, 1, index); + quarterRound(2, 6, 10, 14, 2, index); + quarterRound(3, 7, 11, 15, 3, index); + + // Diagonal round. + quarterRound(0, 5, 10, 15, 4, index); + quarterRound(1, 6, 11, 12, 5, index); + quarterRound(2, 7, 8, 13, 6, index); + quarterRound(3, 4, 9, 14, 7, index); + } + + // Combine the new and old hash values. + for (index = 0; index < 8; ++index) + h[index] ^= (v[index] ^ v[index + 8]); + } + + private static long rightRotate32(long v) { + return v << 32 | (v >>> 32); + } + + private static long rightRotate24(long v) { + return v << 40 | (v >>> 24); + } + + private static long rightRotate16(long v) { + return v << 48 | (v >>> 16); + } + + private static long rightRotate63(long v) { + return v << 1 | (v >>> 63); + } + + private void quarterRound(int a, int b, int c, int d, int i, int row) { + v[a] += v[b] + m[sigma[row][2 * i]]; + v[d] = rightRotate32(v[d] ^ v[a]); + v[c] += v[d]; + v[b] = rightRotate24(v[b] ^ v[c]); + v[a] += v[b] + m[sigma[row][2 * i + 1]]; + v[d] = rightRotate16(v[d] ^ v[a]); + v[c] += v[d]; + v[b] = rightRotate63(v[b] ^ v[c]); + } + + @Override + public void destroy() { + Arrays.fill(h, (long) 0); + Arrays.fill(block, (byte) 0); + Arrays.fill(m, (long) 0); + Arrays.fill(v, (long) 0); + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/Blake2sMessageDigest.java b/src/main/java/com/southernstorm/noise/crypto/Blake2sMessageDigest.java new file mode 100644 index 0000000..9973425 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/Blake2sMessageDigest.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.southernstorm.noise.protocol.Destroyable; + +/** + * Fallback implementation of BLAKE2s for the Noise library. + * + * This implementation only supports message digesting with an output + * length of 32 bytes. Keyed hashing and variable-length digests are + * not supported. + */ +public class Blake2sMessageDigest extends MessageDigest implements Destroyable { + + private int[] h; + private byte[] block; + private int[] m; + private int[] v; + private long length; + private int posn; + + /** + * Constructs a new BLAKE2s message digest object. + */ + public Blake2sMessageDigest() { + super("BLAKE2S-256"); + h = new int[8]; + block = new byte[64]; + m = new int[16]; + v = new int[16]; + engineReset(); + } + + @Override + protected byte[] engineDigest() { + byte[] digest = new byte[32]; + try { + engineDigest(digest, 0, 32); + } catch (DigestException e) { + // Shouldn't happen, but just in case. + Arrays.fill(digest, (byte) 0); + } + return digest; + } + + @Override + protected int engineDigest(byte[] buf, int offset, int len) throws DigestException { + if (len < 32) + throw new DigestException("Invalid digest length for BLAKE2s"); + Arrays.fill(block, posn, 64, (byte) 0); + transform(-1); + for (int index = 0; index < 8; ++index) { + int value = h[index]; + buf[offset++] = (byte) value; + buf[offset++] = (byte) (value >> 8); + buf[offset++] = (byte) (value >> 16); + buf[offset++] = (byte) (value >> 24); + } + return 32; + } + + @Override + protected int engineGetDigestLength() { + return 32; + } + + @Override + protected void engineReset() { + h[0] = 0x6A09E667 ^ 0x01010020; + h[1] = 0xBB67AE85; + h[2] = 0x3C6EF372; + h[3] = 0xA54FF53A; + h[4] = 0x510E527F; + h[5] = 0x9B05688C; + h[6] = 0x1F83D9AB; + h[7] = 0x5BE0CD19; + length = 0; + posn = 0; + } + + @Override + protected void engineUpdate(byte input) { + if (posn >= 64) { + transform(0); + posn = 0; + } + block[posn++] = input; + ++length; + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + while (len > 0) { + if (posn >= 64) { + transform(0); + posn = 0; + } + int temp = (64 - posn); + if (temp > len) + temp = len; + System.arraycopy(input, offset, block, posn, temp); + posn += temp; + length += temp; + offset += temp; + len -= temp; + } + } + + // Permutation on the message input state for BLAKE2s. + static final byte[][] sigma = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 } }; + + private void transform(int f0) { + int index; + int offset; + + // Unpack the input block from little-endian into host-endian. + for (index = 0, offset = 0; index < 16; ++index, offset += 4) { + m[index] = (block[offset] & 0xFF) | ((block[offset + 1] & 0xFF) << 8) | ((block[offset + 2] & 0xFF) << 16) + | ((block[offset + 3] & 0xFF) << 24); + } + + // Format the block to be hashed. + for (index = 0; index < 8; ++index) + v[index] = h[index]; + v[8] = 0x6A09E667; + v[9] = 0xBB67AE85; + v[10] = 0x3C6EF372; + v[11] = 0xA54FF53A; + v[12] = 0x510E527F ^ (int) length; + v[13] = 0x9B05688C ^ (int) (length >> 32); + v[14] = 0x1F83D9AB ^ f0; + v[15] = 0x5BE0CD19; + + // Perform the 10 BLAKE2s rounds. + for (index = 0; index < 10; ++index) { + // Column round. + quarterRound(0, 4, 8, 12, 0, index); + quarterRound(1, 5, 9, 13, 1, index); + quarterRound(2, 6, 10, 14, 2, index); + quarterRound(3, 7, 11, 15, 3, index); + + // Diagonal round. + quarterRound(0, 5, 10, 15, 4, index); + quarterRound(1, 6, 11, 12, 5, index); + quarterRound(2, 7, 8, 13, 6, index); + quarterRound(3, 4, 9, 14, 7, index); + } + + // Combine the new and old hash values. + for (index = 0; index < 8; ++index) + h[index] ^= (v[index] ^ v[index + 8]); + } + + private static int rightRotate16(int v) { + return v << 16 | (v >>> 16); + } + + private static int rightRotate12(int v) { + return v << 20 | (v >>> 12); + } + + private static int rightRotate8(int v) { + return v << 24 | (v >>> 8); + } + + private static int rightRotate7(int v) { + return v << 25 | (v >>> 7); + } + + private void quarterRound(int a, int b, int c, int d, int i, int row) { + v[a] += v[b] + m[sigma[row][2 * i]]; + v[d] = rightRotate16(v[d] ^ v[a]); + v[c] += v[d]; + v[b] = rightRotate12(v[b] ^ v[c]); + v[a] += v[b] + m[sigma[row][2 * i + 1]]; + v[d] = rightRotate8(v[d] ^ v[a]); + v[c] += v[d]; + v[b] = rightRotate7(v[b] ^ v[c]); + } + + @Override + public void destroy() { + Arrays.fill(h, (int) 0); + Arrays.fill(block, (byte) 0); + Arrays.fill(m, (int) 0); + Arrays.fill(v, (int) 0); + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/ChaChaCore.java b/src/main/java/com/southernstorm/noise/crypto/ChaChaCore.java new file mode 100644 index 0000000..b7d73f9 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/ChaChaCore.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +/** + * Implementation of the ChaCha20 core hash transformation. + */ +public final class ChaChaCore { + + 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); + + // 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]; + } + + 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 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); + } + + /** + * 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 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); + } + + private static int leftRotate12(int v) { + return v << 12 | (v >>> 20); + } + + private static int leftRotate8(int v) { + return v << 8 | (v >>> 24); + } + + private static int leftRotate7(int v) { + return v << 7 | (v >>> 25); + } + + 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]); + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/Curve25519.java b/src/main/java/com/southernstorm/noise/crypto/Curve25519.java new file mode 100644 index 0000000..41cd96e --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/Curve25519.java @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.util.Arrays; + +/** + * Implementation of the Curve25519 elliptic curve algorithm. + * + * This implementation is based on that from arduinolibs: + * https://github.com/rweather/arduinolibs + * + * Differences in this version are due to using 26-bit limbs for the + * representation instead of the 8/16/32-bit limbs in the original. + * + * References: http://cr.yp.to/ecdh.html, RFC 7748 + */ +public final class Curve25519 { + + // Numbers modulo 2^255 - 19 are broken up into ten 26-bit words. + private static final int NUM_LIMBS_255BIT = 10; + private static final int NUM_LIMBS_510BIT = 20; + private int[] x_1; + private int[] x_2; + private int[] x_3; + private int[] z_2; + private int[] z_3; + private int[] A; + private int[] B; + private int[] C; + private int[] D; + private int[] E; + private int[] AA; + private int[] BB; + private int[] DA; + private int[] CB; + private long[] t1; + private int[] t2; + + /** + * Constructs the temporary state holder for Curve25519 evaluation. + */ + private Curve25519() { + // Allocate memory for all of the temporary variables we will need. + x_1 = new int[NUM_LIMBS_255BIT]; + x_2 = new int[NUM_LIMBS_255BIT]; + x_3 = new int[NUM_LIMBS_255BIT]; + z_2 = new int[NUM_LIMBS_255BIT]; + z_3 = new int[NUM_LIMBS_255BIT]; + A = new int[NUM_LIMBS_255BIT]; + B = new int[NUM_LIMBS_255BIT]; + C = new int[NUM_LIMBS_255BIT]; + D = new int[NUM_LIMBS_255BIT]; + E = new int[NUM_LIMBS_255BIT]; + AA = new int[NUM_LIMBS_255BIT]; + BB = new int[NUM_LIMBS_255BIT]; + DA = new int[NUM_LIMBS_255BIT]; + CB = new int[NUM_LIMBS_255BIT]; + t1 = new long[NUM_LIMBS_510BIT]; + t2 = new int[NUM_LIMBS_510BIT]; + } + + /** + * Destroy all sensitive data in this object. + */ + private void destroy() { + // Destroy all temporary variables. + Arrays.fill(x_1, 0); + Arrays.fill(x_2, 0); + Arrays.fill(x_3, 0); + Arrays.fill(z_2, 0); + Arrays.fill(z_3, 0); + Arrays.fill(A, 0); + Arrays.fill(B, 0); + Arrays.fill(C, 0); + Arrays.fill(D, 0); + Arrays.fill(E, 0); + Arrays.fill(AA, 0); + Arrays.fill(BB, 0); + Arrays.fill(DA, 0); + Arrays.fill(CB, 0); + Arrays.fill(t1, 0L); + Arrays.fill(t2, 0); + } + + /** + * Reduces a number modulo 2^255 - 19 where it is known that the + * number can be reduced with only 1 trial subtraction. + * + * @param x The number to reduce, and the result. + */ + private void reduceQuick(int[] x) { + int index, carry; + + // Perform a trial subtraction of (2^255 - 19) from "x" which is + // equivalent to adding 19 and subtracting 2^255. We add 19 here; + // the subtraction of 2^255 occurs in the next step. + carry = 19; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + t2[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + + // If there was a borrow, then the original "x" is the correct answer. + // If there was no borrow, then "t2" is the correct answer. Select the + // correct answer but do it in a way that instruction timing will not + // reveal which value was selected. Borrow will occur if bit 21 of + // "t2" is zero. Turn the bit into a selection mask. + int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01); + int nmask = ~mask; + t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) + x[index] = (x[index] & nmask) | (t2[index] & mask); + } + + /** + * Reduce a number modulo 2^255 - 19. + * + * @param result The result. + * @param x The value to be reduced. This array will be + * modified during the reduction. + * @param size The number of limbs in the high order half of x. + */ + private void reduce(int[] result, int[] x, int size) { + int index, limb, carry; + + // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will + // either produce the answer we want or it will produce a + // value of the form "answer + j * (2^255 - 19)". There are + // 5 left-over bits in the top-most limb of the bottom half. + carry = 0; + limb = x[NUM_LIMBS_255BIT - 1] >> 21; + x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (index = 0; index < size; ++index) { + limb += x[NUM_LIMBS_255BIT + index] << 5; + carry += (limb & 0x03FFFFFF) * 19 + x[index]; + x[index] = carry & 0x03FFFFFF; + limb >>= 26; + carry >>= 26; + } + if (size < NUM_LIMBS_255BIT) { + // The high order half of the number is short; e.g. for mulA24(). + // Propagate the carry through the rest of the low order part. + for (index = size; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + x[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + } + + // The "j" value may still be too large due to the final carry-out. + // We must repeat the reduction. If we already have the answer, + // then this won't do any harm but we must still do the calculation + // to preserve the overall timing. The "j" value will be between + // 0 and 19, which means that the carry we care about is in the + // top 5 bits of the highest limb of the bottom half. + carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19; + x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + result[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + + // At this point "x" will either be the answer or it will be the + // answer plus (2^255 - 19). Perform a trial subtraction to + // complete the reduction process. + reduceQuick(result); + } + + /** + * Multiplies two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to multiply. + * @param y The second number to multiply. + */ + private void mul(int[] result, int[] x, int[] y) { + int i, j; + + // Multiply the two numbers to create the intermediate result. + long v = x[0]; + for (i = 0; i < NUM_LIMBS_255BIT; ++i) { + t1[i] = v * y[i]; + } + for (i = 1; i < NUM_LIMBS_255BIT; ++i) { + v = x[i]; + for (j = 0; j < (NUM_LIMBS_255BIT - 1); ++j) { + t1[i + j] += v * y[j]; + } + t1[i + NUM_LIMBS_255BIT - 1] = v * y[NUM_LIMBS_255BIT - 1]; + } + + // Propagate carries and convert back into 26-bit words. + v = t1[0]; + t2[0] = ((int) v) & 0x03FFFFFF; + for (i = 1; i < NUM_LIMBS_510BIT; ++i) { + v = (v >> 26) + t1[i]; + t2[i] = ((int) v) & 0x03FFFFFF; + } + + // Reduce the result modulo 2^255 - 19. + reduce(result, t2, NUM_LIMBS_255BIT); + } + + /** + * Squares a number modulo 2^255 - 19. + * + * @param result The result. + * @param x The number to square. + */ + private void square(int[] result, int[] x) { + mul(result, x, x); + } + + /** + * Multiplies a number by the a24 constant, modulo 2^255 - 19. + * + * @param result The result. + * @param x The number to multiply by a24. + */ + private void mulA24(int[] result, int[] x) { + long a24 = 121665; + long carry = 0; + int index; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += a24 * x[index]; + t2[index] = ((int) carry) & 0x03FFFFFF; + carry >>= 26; + } + t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF; + reduce(result, t2, 1); + } + + /** + * Adds two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to add. + * @param y The second number to add. + */ + private void add(int[] result, int[] x, int[] y) { + int index, carry; + carry = x[0] + y[0]; + result[0] = carry & 0x03FFFFFF; + for (index = 1; index < NUM_LIMBS_255BIT; ++index) { + carry = (carry >> 26) + x[index] + y[index]; + result[index] = carry & 0x03FFFFFF; + } + reduceQuick(result); + } + + /** + * Subtracts two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to subtract. + * @param y The second number to subtract. + */ + private void sub(int[] result, int[] x, int[] y) { + int index, borrow; + + // Subtract y from x to generate the intermediate result. + borrow = 0; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + borrow = x[index] - y[index] - ((borrow >> 26) & 0x01); + result[index] = borrow & 0x03FFFFFF; + } + + // If we had a borrow, then the result has gone negative and we + // have to add 2^255 - 19 to the result to make it positive again. + // The top bits of "borrow" will be all 1's if there is a borrow + // or it will be all 0's if there was no borrow. Easiest is to + // conditionally subtract 19 and then mask off the high bits. + borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19); + result[0] = borrow & 0x03FFFFFF; + for (index = 1; index < NUM_LIMBS_255BIT; ++index) { + borrow = result[index] - ((borrow >> 26) & 0x01); + result[index] = borrow & 0x03FFFFFF; + } + result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + } + + /** + * Conditional swap of two values. + * + * @param select Set to 1 to swap, 0 to leave as-is. + * @param x The first value. + * @param y The second value. + */ + private static void cswap(int select, int[] x, int[] y) { + int dummy; + select = -select; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { + dummy = select & (x[index] ^ y[index]); + x[index] ^= dummy; + y[index] ^= dummy; + } + } + + /** + * Raise x to the power of (2^250 - 1). + * + * @param result The result. Must not overlap with x. + * @param x The argument. + */ + private void pow250(int[] result, int[] x) { + int i, j; + + // The big-endian hexadecimal expansion of (2^250 - 1) is: + // 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF + // + // The naive implementation needs to do 2 multiplications per 1 bit and + // 1 multiplication per 0 bit. We can improve upon this by creating a + // pattern 0000000001 ... 0000000001. If we square and multiply the + // pattern by itself we can turn the pattern into the partial results + // 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc. + // This averages out to about 1.1 multiplications per 1 bit instead of 2. + + // Build a pattern of 250 bits in length of repeated copies of 0000000001. + square(A, x); + for (j = 0; j < 9; ++j) + square(A, A); + mul(result, A, x); + for (i = 0; i < 23; ++i) { + for (j = 0; j < 10; ++j) + square(A, A); + mul(result, result, A); + } + + // Multiply bit-shifted versions of the 0000000001 pattern into + // the result to "fill in" the gaps in the pattern. + square(A, result); + mul(result, result, A); + for (j = 0; j < 8; ++j) { + square(A, A); + mul(result, result, A); + } + } + + /** + * Computes the reciprocal of a number modulo 2^255 - 19. + * + * @param result The result. Must not overlap with x. + * @param x The argument. + */ + private void recip(int[] result, int[] x) { + // The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19. + // The big-endian hexadecimal expansion of (p - 2) is: + // 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB + // Start with the 250 upper bits of the expansion of (p - 2). + pow250(result, x); + + // Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest. + square(result, result); + square(result, result); + mul(result, result, x); + square(result, result); + square(result, result); + mul(result, result, x); + square(result, result); + mul(result, result, x); + } + + /** + * Evaluates the curve for every bit in a secret key. + * + * @param s The 32-byte secret key. + */ + private void evalCurve(byte[] s) { + int sposn = 31; + int sbit = 6; + int svalue = s[sposn] | 0x40; + int swap = 0; + int select; + + // Iterate over all 255 bits of "s" from the highest to the lowest. + // We ignore the high bit of the 256-bit representation of "s". + for (;;) { + // Conditional swaps on entry to this bit but only if we + // didn't swap on the previous bit. + select = (svalue >> sbit) & 0x01; + swap ^= select; + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + swap = select; + + // Evaluate the curve. + add(A, x_2, z_2); // A = x_2 + z_2 + square(AA, A); // AA = A^2 + sub(B, x_2, z_2); // B = x_2 - z_2 + square(BB, B); // BB = B^2 + sub(E, AA, BB); // E = AA - BB + add(C, x_3, z_3); // C = x_3 + z_3 + sub(D, x_3, z_3); // D = x_3 - z_3 + mul(DA, D, A); // DA = D * A + mul(CB, C, B); // CB = C * B + add(x_3, DA, CB); // x_3 = (DA + CB)^2 + square(x_3, x_3); + sub(z_3, DA, CB); // z_3 = x_1 * (DA - CB)^2 + square(z_3, z_3); + mul(z_3, z_3, x_1); + mul(x_2, AA, BB); // x_2 = AA * BB + mulA24(z_2, E); // z_2 = E * (AA + a24 * E) + add(z_2, z_2, AA); + mul(z_2, z_2, E); + + // Move onto the next lower bit of "s". + if (sbit > 0) { + --sbit; + } else if (sposn == 0) { + break; + } else if (sposn == 1) { + --sposn; + svalue = s[sposn] & 0xF8; + sbit = 7; + } else { + --sposn; + svalue = s[sposn]; + sbit = 7; + } + } + + // Final conditional swaps. + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + } + + /** + * Evaluates the Curve25519 curve. + * + * @param result Buffer to place the result of the evaluation into. + * @param offset Offset into the result buffer. + * @param privateKey The private key to use in the evaluation. + * @param publicKey The public key to use in the evaluation, or null + * if the base point of the curve should be used. + */ + public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey) { + Curve25519 state = new Curve25519(); + try { + // Unpack the public key value. If null, use 9 as the base point. + Arrays.fill(state.x_1, 0); + if (publicKey != null) { + // Convert the input value from little-endian into 26-bit limbs. + for (int index = 0; index < 32; ++index) { + int bit = (index * 8) % 26; + int word = (index * 8) / 26; + int value = publicKey[index] & 0xFF; + if (bit <= (26 - 8)) { + state.x_1[word] |= value << bit; + } else { + state.x_1[word] |= value << bit; + state.x_1[word] &= 0x03FFFFFF; + state.x_1[word + 1] |= value >> (26 - bit); + } + } + + // Just in case, we reduce the number modulo 2^255 - 19 to + // make sure that it is in range of the field before we start. + // This eliminates values between 2^255 - 19 and 2^256 - 1. + state.reduceQuick(state.x_1); + state.reduceQuick(state.x_1); + } else { + state.x_1[0] = 9; + } + + // Initialize the other temporary variables. + Arrays.fill(state.x_2, 0); // x_2 = 1 + state.x_2[0] = 1; + Arrays.fill(state.z_2, 0); // z_2 = 0 + System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1 + Arrays.fill(state.z_3, 0); // z_3 = 1 + state.z_3[0] = 1; + + // Evaluate the curve for every bit of the private key. + state.evalCurve(privateKey); + + // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19. + state.recip(state.z_3, state.z_2); + state.mul(state.x_2, state.x_2, state.z_3); + + // Convert x_2 into little-endian in the result buffer. + for (int index = 0; index < 32; ++index) { + int bit = (index * 8) % 26; + int word = (index * 8) / 26; + if (bit <= (26 - 8)) + result[offset + index] = (byte) (state.x_2[word] >> bit); + else + result[offset + index] = (byte) ((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit))); + } + } finally { + // Clean up all temporary state before we exit. + state.destroy(); + } + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/Curve448.java b/src/main/java/com/southernstorm/noise/crypto/Curve448.java new file mode 100644 index 0000000..a0b7750 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/Curve448.java @@ -0,0 +1,613 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + +Portions of this code were extracted from the p448/arch_32 field +arithmetic implementation in Ed448-Goldilocks and converted from +C into Java. The LICENSE.txt file for the imported code follows: + +---- +The MIT License (MIT) + +Copyright (c) 2011 Stanford University. +Copyright (c) 2014 Cryptography Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +---- + +*/ + +package com.southernstorm.noise.crypto; + +import java.util.Arrays; + +/** + * Implementation of the Curve448 elliptic curve algorithm. + * + * Reference: RFC 7748 + */ +public final class Curve448 { + + // Numbers modulo 2^448 - 2^224 - 1 are broken up into sixteen 28-bit words. + private int[] x_1; + private int[] x_2; + private int[] x_3; + private int[] z_2; + private int[] z_3; + private int[] A; + private int[] B; + private int[] C; + private int[] D; + private int[] E; + private int[] AA; + private int[] BB; + private int[] DA; + private int[] CB; + private int[] aa; + private int[] bb; + + /** + * Constructs the temporary state holder for Curve448 evaluation. + */ + private Curve448() { + // Allocate memory for all of the temporary variables we will need. + x_1 = new int[16]; + x_2 = new int[16]; + x_3 = new int[16]; + z_2 = new int[16]; + z_3 = new int[16]; + A = new int[16]; + B = new int[16]; + C = new int[16]; + D = new int[16]; + E = new int[16]; + AA = new int[16]; + BB = new int[16]; + DA = new int[16]; + CB = new int[16]; + aa = new int[8]; + bb = new int[8]; + } + + /** + * Destroy all sensitive data in this object. + */ + private void destroy() { + // Destroy all temporary variables. + Arrays.fill(x_1, 0); + Arrays.fill(x_2, 0); + Arrays.fill(x_3, 0); + Arrays.fill(z_2, 0); + Arrays.fill(z_3, 0); + Arrays.fill(A, 0); + Arrays.fill(B, 0); + Arrays.fill(C, 0); + Arrays.fill(D, 0); + Arrays.fill(E, 0); + Arrays.fill(AA, 0); + Arrays.fill(BB, 0); + Arrays.fill(DA, 0); + Arrays.fill(CB, 0); + Arrays.fill(aa, 0); + Arrays.fill(bb, 0); + } + + /* Beginning of code imported from Ed448-Goldilocks */ + + private static long widemul_32(int a, int b) { + return ((long) a) * b; + } + + // p448_mul() + private void mul(int[] c, int[] a, int[] b) { + long accum0 = 0, accum1 = 0, accum2 = 0; + int mask = (1 << 28) - 1; + + int i, j; + for (i = 0; i < 8; i++) { + aa[i] = a[i] + a[i + 8]; + bb[i] = b[i] + b[i + 8]; + } + + for (j = 0; j < 8; j++) { + accum2 = 0; + + for (i = 0; i <= j; i++) { + accum2 += widemul_32(a[j - i], b[i]); + accum1 += widemul_32(aa[j - i], bb[i]); + accum0 += widemul_32(a[8 + j - i], b[8 + i]); + } + + accum1 -= accum2; + accum0 += accum2; + accum2 = 0; + + for (; i < 8; i++) { + accum0 -= widemul_32(a[8 + j - i], b[i]); + accum2 += widemul_32(aa[8 + j - i], bb[i]); + accum1 += widemul_32(a[16 + j - i], b[8 + i]); + } + + accum1 += accum2; + accum0 += accum2; + + c[j] = ((int) (accum0)) & mask; + c[j + 8] = ((int) (accum1)) & mask; + + accum0 >>>= 28; + accum1 >>>= 28; + } + + accum0 += accum1; + accum0 += c[8]; + accum1 += c[0]; + c[8] = ((int) (accum0)) & mask; + c[0] = ((int) (accum1)) & mask; + + accum0 >>>= 28; + accum1 >>>= 28; + c[9] += ((int) (accum0)); + c[1] += ((int) (accum1)); + } + + // p448_mulw() + private static void mulw(int[] c, int[] a, long b) { + int bhi = (int) (b >> 28), blo = ((int) b) & ((1 << 28) - 1); + + long accum0, accum8; + int mask = (1 << 28) - 1; + + int i; + + accum0 = widemul_32(blo, a[0]); + accum8 = widemul_32(blo, a[8]); + accum0 += widemul_32(bhi, a[15]); + accum8 += widemul_32(bhi, a[15] + a[7]); + + c[0] = ((int) accum0) & mask; + accum0 >>>= 28; + c[8] = ((int) accum8) & mask; + accum8 >>>= 28; + + for (i = 1; i < 8; i++) { + accum0 += widemul_32(blo, a[i]); + accum8 += widemul_32(blo, a[i + 8]); + + accum0 += widemul_32(bhi, a[i - 1]); + accum8 += widemul_32(bhi, a[i + 7]); + + c[i] = ((int) accum0) & mask; + accum0 >>>= 28; + c[i + 8] = ((int) accum8) & mask; + accum8 >>>= 28; + } + + accum0 += accum8 + c[8]; + c[8] = ((int) accum0) & mask; + c[9] += accum0 >>> 28; + + accum8 += c[0]; + c[0] = ((int) accum8) & mask; + c[1] += accum8 >>> 28; + } + + // p448_weak_reduce + private static void weak_reduce(int[] a) { + int mask = (1 << 28) - 1; + int tmp = a[15] >>> 28; + int i; + a[8] += tmp; + for (i = 15; i > 0; i--) { + a[i] = (a[i] & mask) + (a[i - 1] >>> 28); + } + a[0] = (a[0] & mask) + tmp; + } + + // p448_strong_reduce + private static void strong_reduce(int[] a) { + int mask = (1 << 28) - 1; + + /* first, clear high */ + a[8] += a[15] >>> 28; + a[0] += a[15] >>> 28; + a[15] &= mask; + + /* now the total is less than 2^448 - 2^(448-56) + 2^(448-56+8) < 2p */ + + /* compute total_value - p. No need to reduce mod p. */ + + long scarry = 0; + int i; + for (i = 0; i < 16; i++) { + scarry = scarry + (a[i] & 0xFFFFFFFFL) - ((i == 8) ? mask - 1 : mask); + a[i] = (int) (scarry & mask); + scarry >>= 28; + } + + /* + * uncommon case: it was >= p, so now scarry = 0 and this = x + * common case: it was < p, so now scarry = -1 and this = x - p + 2^448 + * so let's add back in p. will carry back off the top for 2^448. + */ + + int scarry_mask = (int) (scarry & mask); + long carry = 0; + + /* add it back */ + for (i = 0; i < 16; i++) { + carry = carry + (a[i] & 0xFFFFFFFFL) + ((i == 8) ? (scarry_mask & ~1) : scarry_mask); + a[i] = (int) (carry & mask); + carry >>>= 28; + } + } + + // field_add() + private static void add(int[] out, int[] a, int[] b) { + for (int i = 0; i < 16; ++i) + out[i] = a[i] + b[i]; + weak_reduce(out); + } + + // field_sub() + private static void sub(int[] out, int[] a, int[] b) { + int i; + + // p448_sub_RAW(out, a, b) + for (i = 0; i < 16; ++i) + out[i] = a[i] - b[i]; + + // p448_bias(out, 2) + int co1 = ((1 << 28) - 1) * 2; + int co2 = co1 - 2; + for (i = 0; i < 16; ++i) { + if (i != 8) + out[i] += co1; + else + out[i] += co2; + } + + weak_reduce(out); + } + + // p448_serialize() + private static void serialize(byte[] serial, int offset, int[] x) { + int i, j; + for (i = 0; i < 8; i++) { + long limb = x[2 * i] + (((long) x[2 * i + 1]) << 28); + for (j = 0; j < 7; j++) { + serial[offset + 7 * i + j] = (byte) limb; + limb >>= 8; + } + } + } + + private static int is_zero(int x) { + long xx = x & 0xFFFFFFFFL; + xx--; + return (int) (xx >> 32); + } + + // p448_deserialize() + private static int deserialize(int[] x, byte[] serial, int offset) { + int i, j; + for (i = 0; i < 8; i++) { + long out = 0; + for (j = 0; j < 7; j++) { + out |= (serial[offset + 7 * i + j] & 0xFFL) << (8 * j); + } + x[2 * i] = ((int) out) & ((1 << 28) - 1); + x[2 * i + 1] = (int) (out >>> 28); + } + + /* + * Check for reduction. + * + * The idea is to create a variable ge which is all ones (rather, 56 ones) + * if and only if the low $i$ words of $x$ are >= those of p. + * + * Remember p = little_endian(1111,1111,1111,1111,1110,1111,1111,1111) + */ + int ge = -1, mask = (1 << 28) - 1; + for (i = 0; i < 8; i++) { + ge &= x[i]; + } + + /* At this point, ge = 1111 iff bottom are all 1111. Now propagate if 1110, or set if 1111 */ + ge = (ge & (x[8] + 1)) | is_zero(x[8] ^ mask); + + /* Propagate the rest */ + for (i = 9; i < 16; i++) { + ge &= x[i]; + } + + return ~is_zero(ge ^ mask); + } + + /* End of code imported from Ed448-Goldilocks */ + + /** + * Squares a number modulo 2^448 - 2^224 - 1. + * + * @param result The result. + * @param x The number to square. + */ + private void square(int[] result, int[] x) { + mul(result, x, x); + } + + /** + * Conditional swap of two values. + * + * @param select Set to 1 to swap, 0 to leave as-is. + * @param x The first value. + * @param y The second value. + */ + private static void cswap(int select, int[] x, int[] y) { + int dummy; + select = -select; + for (int index = 0; index < 16; ++index) { + dummy = select & (x[index] ^ y[index]); + x[index] ^= dummy; + y[index] ^= dummy; + } + } + + /** + * Computes the reciprocal of a number modulo 2^448 - 2^224 - 1. + * + * @param result The result. Must not overlap with z_2. + * @param z_2 The argument. + */ + private void recip(int[] result, int[] z_2) { + int posn; + + /* + * Compute z_2 ^ (p - 2) + * + * The value p - 2 is: FF...FEFF...FD, which from highest to lowest is + * 223 one bits, followed by a zero bit, followed by 222 one bits, + * followed by another zero bit, and a final one bit. + * + * The naive implementation that squares for every bit and multiplies + * for every 1 bit requires 893 multiplications. The following can + * do the same operation in 483 multiplications. The basic idea is to + * create bit patterns and then "shift" them into position. We start + * with a 4 bit pattern 1111, which we can square 4 times to get + * 11110000 and then multiply by the 1111 pattern to get 11111111. + * We then repeat that to turn 11111111 into 1111111111111111, etc. + */ + square(B, z_2); /* Set A to a 4 bit pattern */ + mul(A, B, z_2); + square(B, A); + mul(A, B, z_2); + square(B, A); + mul(A, B, z_2); + square(B, A); /* Set C to a 6 bit pattern */ + mul(C, B, z_2); + square(B, C); + mul(C, B, z_2); + square(B, C); /* Set A to a 8 bit pattern */ + mul(A, B, z_2); + square(B, A); + mul(A, B, z_2); + square(E, A); /* Set E to a 16 bit pattern */ + square(B, E); + for (posn = 1; posn < 4; ++posn) { + square(E, B); + square(B, E); + } + mul(E, B, A); + square(AA, E); /* Set AA to a 32 bit pattern */ + square(B, AA); + for (posn = 1; posn < 8; ++posn) { + square(AA, B); + square(B, AA); + } + mul(AA, B, E); + square(BB, AA); /* Set BB to a 64 bit pattern */ + square(B, BB); + for (posn = 1; posn < 16; ++posn) { + square(BB, B); + square(B, BB); + } + mul(BB, B, AA); + square(DA, BB); /* Set DA to a 128 bit pattern */ + square(B, DA); + for (posn = 1; posn < 32; ++posn) { + square(DA, B); + square(B, DA); + } + mul(DA, B, BB); + square(CB, DA); /* Set CB to a 192 bit pattern */ + square(B, CB); /* 192 = 128 + 64 */ + for (posn = 1; posn < 32; ++posn) { + square(CB, B); + square(B, CB); + } + mul(CB, B, BB); + square(DA, CB); /* Set DA to a 208 bit pattern */ + square(B, DA); /* 208 = 128 + 64 + 16 */ + for (posn = 1; posn < 8; ++posn) { + square(DA, B); + square(B, DA); + } + mul(DA, B, E); + square(CB, DA); /* Set CB to a 216 bit pattern */ + square(B, CB); /* 216 = 128 + 64 + 16 + 8 */ + for (posn = 1; posn < 4; ++posn) { + square(CB, B); + square(B, CB); + } + mul(CB, B, A); + square(DA, CB); /* Set DA to a 222 bit pattern */ + square(B, DA); /* 222 = 128 + 64 + 16 + 8 + 6 */ + for (posn = 1; posn < 3; ++posn) { + square(DA, B); + square(B, DA); + } + mul(DA, B, C); + square(CB, DA); /* Set CB to a 224 bit pattern */ + mul(B, CB, z_2); /* CB = DA|1|0 */ + square(CB, B); + square(BB, CB); /* Set BB to a 446 bit pattern */ + square(B, BB); /* BB = DA|1|0|DA */ + for (posn = 1; posn < 111; ++posn) { + square(BB, B); + square(B, BB); + } + mul(BB, B, DA); + square(B, BB); /* Set result to a 448 bit pattern */ + square(BB, B); /* result = DA|1|0|DA|01 */ + mul(result, BB, z_2); + } + + /** + * Evaluates the curve for every bit in a secret key. + * + * @param s The 56-byte secret key. + */ + private void evalCurve(byte[] s) { + int sposn = 55; + int sbit = 7; + int svalue = s[sposn] | 0x80; + int swap = 0; + int select; + + // Iterate over all 448 bits of "s" from the highest to the lowest. + for (;;) { + // Conditional swaps on entry to this bit but only if we + // didn't swap on the previous bit. + select = (svalue >> sbit) & 0x01; + swap ^= select; + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + swap = select; + + // Evaluate the curve. + add(A, x_2, z_2); // A = x_2 + z_2 + square(AA, A); // AA = A^2 + sub(B, x_2, z_2); // B = x_2 - z_2 + square(BB, B); // BB = B^2 + sub(E, AA, BB); // E = AA - BB + add(C, x_3, z_3); // C = x_3 + z_3 + sub(D, x_3, z_3); // D = x_3 - z_3 + mul(DA, D, A); // DA = D * A + mul(CB, C, B); // CB = C * B + add(z_2, DA, CB); // x_3 = (DA + CB)^2 + square(x_3, z_2); + sub(z_2, DA, CB); // z_3 = x_1 * (DA - CB)^2 + square(x_2, z_2); + mul(z_3, x_1, x_2); + mul(x_2, AA, BB); // x_2 = AA * BB + mulw(z_2, E, 39081); // z_2 = E * (AA + a24 * E) + add(A, AA, z_2); + mul(z_2, E, A); + + // Move onto the next lower bit of "s". + if (sbit > 0) { + --sbit; + } else if (sposn == 0) { + break; + } else if (sposn == 1) { + --sposn; + svalue = s[sposn] & 0xFC; + sbit = 7; + } else { + --sposn; + svalue = s[sposn]; + sbit = 7; + } + } + + // Final conditional swaps. + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + } + + /** + * Evaluates the Curve448 curve. + * + * @param result Buffer to place the result of the evaluation into. + * @param offset Offset into the result buffer. + * @param privateKey The private key to use in the evaluation. + * @param publicKey The public key to use in the evaluation, or null + * if the base point of the curve should be used. + * @return Returns true if the curve evaluation was successful, + * false if the publicKey value is out of range. + */ + public static boolean eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey) { + Curve448 state = new Curve448(); + int success = -1; + try { + // Unpack the public key value. If null, use 5 as the base point. + Arrays.fill(state.x_1, 0); + if (publicKey != null) { + // Convert the input value from little-endian into 28-bit limbs. + // It is possible that the public key is out of range. If so, + // delay reporting that state until the function completes. + success = deserialize(state.x_1, publicKey, 0); + } else { + state.x_1[0] = 5; + } + + // Initialize the other temporary variables. + Arrays.fill(state.x_2, 0); // x_2 = 1 + state.x_2[0] = 1; + Arrays.fill(state.z_2, 0); // z_2 = 0 + System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1 + Arrays.fill(state.z_3, 0); // z_3 = 1 + state.z_3[0] = 1; + + // Evaluate the curve for every bit of the private key. + state.evalCurve(privateKey); + + // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^448 - 2^224 - 1. + state.recip(state.z_3, state.z_2); + state.mul(state.x_1, state.x_2, state.z_3); + + // Convert x_2 into little-endian in the result buffer. + strong_reduce(state.x_1); + serialize(result, offset, state.x_1); + } finally { + // Clean up all temporary state before we exit. + state.destroy(); + } + return (success & 0x01) != 0; + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/GHASH.java b/src/main/java/com/southernstorm/noise/crypto/GHASH.java new file mode 100644 index 0000000..9b7ce82 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/GHASH.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.util.Arrays; + +import com.southernstorm.noise.protocol.Destroyable; + +/** + * Implementation of the GHASH primitive for GCM. + */ +public final class GHASH implements Destroyable { + + private long[] H; + private byte[] Y; + int posn; + + /** + * Constructs a new GHASH object. + */ + public GHASH() { + H = new long[2]; + Y = new byte[16]; + posn = 0; + } + + /** + * Resets this GHASH object with a new key. + * + * @param key The key, which must contain at least 16 bytes. + * @param offset The offset of the first key byte. + */ + public void reset(byte[] key, int offset) { + H[0] = readBigEndian(key, offset); + H[1] = readBigEndian(key, offset + 8); + Arrays.fill(Y, (byte) 0); + posn = 0; + } + + /** + * Resets the GHASH object but retains the previous key. + */ + public void reset() { + Arrays.fill(Y, (byte) 0); + posn = 0; + } + + /** + * Updates this GHASH object with more data. + * + * @param data Buffer containing the data. + * @param offset Offset of the first data byte in the buffer. + * @param length The number of bytes from the buffer to hash. + */ + public void update(byte[] data, int offset, int length) { + while (length > 0) { + int size = 16 - posn; + if (size > length) + size = length; + for (int index = 0; index < size; ++index) + Y[posn + index] ^= data[offset + index]; + posn += size; + length -= size; + offset += size; + if (posn == 16) { + GF128_mul(Y, H); + posn = 0; + } + } + } + + /** + * Finishes the GHASH process and returns the tag. + * + * @param tag Buffer to receive the tag. + * @param offset Offset of the first byte of the tag. + * @param length The length of the tag, which must be less + * than or equal to 16. + */ + public void finish(byte[] tag, int offset, int length) { + pad(); + System.arraycopy(Y, 0, tag, offset, length); + } + + /** + * Pads the input to a 16-byte boundary. + */ + public void pad() { + if (posn != 0) { + // Padding involves XOR'ing the rest of state->Y with zeroes, + // which does nothing. Immediately process the next chunk. + GF128_mul(Y, H); + posn = 0; + } + } + + /** + * Pads the input to a 16-byte boundary and then adds a block + * containing the AD and data lengths. + * + * @param adLen Length of the associated data in bytes. + * @param dataLen Length of the data in bytes. + */ + public void pad(long adLen, long dataLen) { + byte[] temp = new byte[16]; + try { + pad(); + writeBigEndian(temp, 0, adLen * 8); + writeBigEndian(temp, 8, dataLen * 8); + update(temp, 0, 16); + } finally { + Arrays.fill(temp, (byte) 0); + } + } + + @Override + public void destroy() { + Arrays.fill(H, 0L); + Arrays.fill(Y, (byte) 0); + } + + private static long readBigEndian(byte[] buf, int offset) { + return ((buf[offset] & 0xFFL) << 56) | ((buf[offset + 1] & 0xFFL) << 48) | ((buf[offset + 2] & 0xFFL) << 40) + | ((buf[offset + 3] & 0xFFL) << 32) | ((buf[offset + 4] & 0xFFL) << 24) + | ((buf[offset + 5] & 0xFFL) << 16) | ((buf[offset + 6] & 0xFFL) << 8) | (buf[offset + 7] & 0xFFL); + } + + private static void writeBigEndian(byte[] buf, int offset, long value) { + buf[offset] = (byte) (value >> 56); + buf[offset + 1] = (byte) (value >> 48); + buf[offset + 2] = (byte) (value >> 40); + buf[offset + 3] = (byte) (value >> 32); + buf[offset + 4] = (byte) (value >> 24); + buf[offset + 5] = (byte) (value >> 16); + buf[offset + 6] = (byte) (value >> 8); + buf[offset + 7] = (byte) value; + } + + private static void GF128_mul(byte[] Y, long[] H) { + long Z0 = 0; // Z = 0 + long Z1 = 0; + long V0 = H[0]; // V = H + long V1 = H[1]; + + // Multiply Z by V for the set bits in Y, starting at the top. + // This is a very simple bit by bit version that may not be very + // fast but it should be resistant to cache timing attacks. + for (int posn = 0; posn < 16; ++posn) { + int value = Y[posn] & 0xFF; + for (int bit = 7; bit >= 0; --bit) { + // Extract the high bit of "value" and turn it into a mask. + long mask = -((long) ((value >> bit) & 0x01)); + + // XOR V with Z if the bit is 1. + Z0 ^= (V0 & mask); + Z1 ^= (V1 & mask); + + // Rotate V right by 1 bit. + mask = ((~(V1 & 0x01)) + 1) & 0xE100000000000000L; + V1 = (V1 >>> 1) | (V0 << 63); + V0 = (V0 >>> 1) ^ mask; + } + } + + // We have finished the block so copy Z into Y and byte-swap. + writeBigEndian(Y, 0, Z0); + writeBigEndian(Y, 8, Z1); + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/NewHope.java b/src/main/java/com/southernstorm/noise/crypto/NewHope.java new file mode 100644 index 0000000..9c4c318 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/NewHope.java @@ -0,0 +1,1492 @@ +/* + * Based on the public domain C reference code for New Hope. + * This Java version is also placed into the public domain. + * + * Original authors: Erdem Alkim, Léo Ducas, Thomas Pöppelmann, Peter Schwabe + * Java port: Rhys Weatherley + */ + +package com.southernstorm.noise.crypto; + +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * NewHope key exchange algorithm. + * + * This class implements the standard "ref" version of the New Hope + * algorithm. + * + * @see NewHopeTor + */ +public class NewHope { + + // -------------- params.h -------------- + + static final int PARAM_N = 1024; + static final int PARAM_Q = 12289; + static final int POLY_BYTES = 1792; + static final int SEEDBYTES = 32; + static final int RECBYTES = 256; + + /** + * Number of bytes in the public key value sent by Alice. + */ + public static final int SENDABYTES = POLY_BYTES + SEEDBYTES; + + /** + * Number of bytes in the public key value sent by Bob. + */ + public static final int SENDBBYTES = POLY_BYTES + RECBYTES; + + /** + * Number of bytes in shared secret values computed by shareda() and sharedb(). + */ + public static final int SHAREDBYTES = 32; + + // -------------- newhope.c -------------- + + private Poly sk; + + /** + * Constructs a NewHope object. + */ + public NewHope() { + sk = null; + } + + @Override + protected void finalize() { + destroy(); + } + + /** + * Destroys sensitive material in this object. + * + * This function should be called once the application has finished + * with the private key contained in this object. This function + * will also be called when the object is finalized, but the point + * of finalization is unpredictable. This function provides a more + * predictable place where the sensitive data is destroyed. + */ + public void destroy() { + if (sk != null) { + sk.destroy(); + sk = null; + } + } + + /** + * Generates the keypair for Alice. + * + * @param send Buffer to place the public key for Alice in, to be sent to Bob. + * @param sendOffset Offset of the first byte in the send buffer to populate. + * + * The send buffer must have space for at least NewHope.SENDABYTES bytes + * starting at sendOffset. + * + * @see #sharedb(byte[], int, byte[], int, byte[], int) + * @see #shareda(byte[], int, byte[], int) + */ + public void keygen(byte[] send, int sendOffset) { + Poly a = new Poly(); + Poly e = new Poly(); + Poly r = new Poly(); + Poly pk = new Poly(); + byte[] seed = new byte[SEEDBYTES + 32]; + byte[] noiseseed = new byte[32]; + + try { + randombytes(seed); + sha3256(seed, 0, seed, 0, SEEDBYTES); /* Don't send output of system RNG */ + System.arraycopy(seed, SEEDBYTES, noiseseed, 0, 32); + + uniform(a.coeffs, seed); + + if (sk == null) + sk = new Poly(); + sk.getnoise(noiseseed, (byte) 0); + sk.ntt(); + + e.getnoise(noiseseed, (byte) 1); + e.ntt(); + + r.pointwise(sk, a); + pk.add(e, r); + + encode_a(send, sendOffset, pk, seed); + } finally { + a.destroy(); + e.destroy(); + r.destroy(); + pk.destroy(); + Arrays.fill(seed, (byte) 0); + Arrays.fill(noiseseed, (byte) 0); + } + } + + /** + * Generates the public key and shared secret for Bob. + * + * @param sharedkey Buffer to place the shared secret for Bob in. + * @param sharedkeyOffset Offset of the first byte in the sharedkey buffer to populate. + * @param send Buffer to place the public key for Bob in to be sent to Alice. + * @param sendOffset Offset of the first byte in the send buffer to populate. + * @param received Buffer containing the public key value received from Alice. + * @param receivedOffset Offset of the first byte of the value received from Alice. + * + * The sharedkey buffer must have space for at least NewHope.SHAREDBYTES + * bytes starting at sharedkeyOffset. + * + * The send buffer must have space for at least NewHope.SENDBBYTES bytes + * starting at sendOffset. + * + * The received buffer must have space for at least NewHope.SENDABYTES + * bytes starting at receivedOffset. + * + * @see #shareda(byte[], int, byte[], int) + * @see #keygen(byte[], int) + */ + public void sharedb(byte[] sharedkey, int sharedkeyOffset, byte[] send, int sendOffset, byte[] received, + int receivedOffset) { + Poly sp = new Poly(); + Poly ep = new Poly(); + Poly v = new Poly(); + Poly a = new Poly(); + Poly pka = new Poly(); + Poly c = new Poly(); + Poly epp = new Poly(); + Poly bp = new Poly(); + byte[] seed = new byte[SEEDBYTES]; + byte[] noiseseed = new byte[32]; + byte[] skey = new byte[32]; + + try { + randombytes(noiseseed); + + decode_a(pka, seed, received, receivedOffset); + uniform(a.coeffs, seed); + + sp.getnoise(noiseseed, (byte) 0); + sp.ntt(); + ep.getnoise(noiseseed, (byte) 1); + ep.ntt(); + + bp.pointwise(a, sp); + bp.add(bp, ep); + + v.pointwise(pka, sp); + v.invntt(); + + epp.getnoise(noiseseed, (byte) 2); + v.add(v, epp); + + helprec(c, v, noiseseed, (byte) 3); + + encode_b(send, sendOffset, bp, c); + + rec(skey, v, c); + + sha3256(sharedkey, sharedkeyOffset, skey, 0, 32); + } finally { + sp.destroy(); + ep.destroy(); + v.destroy(); + a.destroy(); + pka.destroy(); + c.destroy(); + epp.destroy(); + bp.destroy(); + Arrays.fill(seed, (byte) 0); + Arrays.fill(noiseseed, (byte) 0); + Arrays.fill(skey, (byte) 0); + } + } + + /** + * Generates the shared secret for Alice. + * + * @param sharedkey Buffer to place the shared secret for Alice in. + * @param sharedkeyOffset Offset of the first byte in the sharedkey buffer to populate. + * @param received Buffer containing the public key value received from Bob. + * @param receivedOffset Offset of the first byte of the value received from Bob. + * + * The sharedkey buffer must have space for at least NewHope.SHAREDBYTES + * bytes starting at sharedkeyOffset. + * + * The received buffer must have space for at least NewHope.SENDBBYTES bytes + * starting at receivedOffset. + * + * @see #shareda(byte[], int, byte[], int) + * @see #keygen(byte[], int) + */ + public void shareda(byte[] sharedkey, int sharedkeyOffset, byte[] received, int receivedOffset) { + Poly v = new Poly(); + Poly bp = new Poly(); + Poly c = new Poly(); + byte[] skey = new byte[32]; + + try { + decode_b(bp, c, received, receivedOffset); + + v.pointwise(sk, bp); + v.invntt(); + + rec(skey, v, c); + + sha3256(sharedkey, sharedkeyOffset, skey, 0, 32); + } finally { + v.destroy(); + bp.destroy(); + c.destroy(); + Arrays.fill(skey, (byte) 0); + } + } + + /** + * Generates random bytes for use in the NewHope implementation. + * + * @param buffer The buffer to fill with random bytes. + * + * This function may be overridden in subclasses to provide a better + * random number generator or to provide static data for test vectors. + */ + protected void randombytes(byte[] buffer) { + SecureRandom random = new SecureRandom(); + random.nextBytes(buffer); + } + + private static void encode_a(byte[] r, int roffset, Poly pk, byte[] seed) { + int i; + pk.tobytes(r, roffset); + for (i = 0; i < SEEDBYTES; i++) + r[POLY_BYTES + roffset + i] = seed[i]; + } + + private static void decode_a(Poly pk, byte[] seed, byte[] r, int roffset) { + int i; + pk.frombytes(r, roffset); + for (i = 0; i < SEEDBYTES; i++) + seed[i] = r[POLY_BYTES + roffset + i]; + } + + private static void encode_b(byte[] r, int roffset, Poly b, Poly c) { + int i; + b.tobytes(r, roffset); + for (i = 0; i < PARAM_N / 4; i++) + r[POLY_BYTES + roffset + i] = (byte) (c.coeffs[4 * i] | (c.coeffs[4 * i + 1] << 2) + | (c.coeffs[4 * i + 2] << 4) | (c.coeffs[4 * i + 3] << 6)); + } + + private static void decode_b(Poly b, Poly c, byte[] r, int roffset) { + int i; + b.frombytes(r, roffset); + for (i = 0; i < PARAM_N / 4; i++) { + c.coeffs[4 * i + 0] = (char) (r[POLY_BYTES + roffset + i] & 0x03); + c.coeffs[4 * i + 1] = (char) ((r[POLY_BYTES + roffset + i] >> 2) & 0x03); + c.coeffs[4 * i + 2] = (char) ((r[POLY_BYTES + roffset + i] >> 4) & 0x03); + c.coeffs[4 * i + 3] = (char) (((r[POLY_BYTES + roffset + i] & 0xff) >> 6)); + } + } + + // -------------- poly.c -------------- + + private class Poly { + public char[] coeffs; + + public Poly() { + coeffs = new char[PARAM_N]; + } + + protected void finalize() { + destroy(); + } + + public void destroy() { + Arrays.fill(coeffs, (char) 0); + } + + public void frombytes(byte[] a, int offset) { + int i; + for (i = 0; i < PARAM_N / 4; i++) { + coeffs[4 * i + 0] = (char) ((a[offset + 7 * i + 0] & 0xff) | ((a[offset + 7 * i + 1] & 0x3f) << 8)); + coeffs[4 * i + 1] = (char) (((a[offset + 7 * i + 1] & 0xc0) >> 6) + | ((a[offset + 7 * i + 2] & 0xff) << 2) | ((a[offset + 7 * i + 3] & 0x0f) << 10)); + coeffs[4 * i + 2] = (char) (((a[offset + 7 * i + 3] & 0xf0) >> 4) + | ((a[offset + 7 * i + 4] & 0xff) << 4) | ((a[offset + 7 * i + 5] & 0x03) << 12)); + coeffs[4 * i + + 3] = (char) (((a[offset + 7 * i + 5] & 0xfc) >> 2) | ((a[offset + 7 * i + 6] & 0xff) << 6)); + } + } + + public void tobytes(byte[] r, int offset) { + int i; + int t0, t1, t2, t3, m; + int c; + for (i = 0; i < PARAM_N / 4; i++) { + t0 = barrett_reduce(coeffs[4 * i + 0]); // Make sure that coefficients have only 14 bits + t1 = barrett_reduce(coeffs[4 * i + 1]); + t2 = barrett_reduce(coeffs[4 * i + 2]); + t3 = barrett_reduce(coeffs[4 * i + 3]); + + m = t0 - PARAM_Q; + c = m; + c >>= 15; + t0 = m ^ ((t0 ^ m) & c); // >= 15; + t1 = m ^ ((t1 ^ m) & c); // >= 15; + t2 = m ^ ((t2 ^ m) & c); // >= 15; + t3 = m ^ ((t3 ^ m) & c); // > 8) | (t1 << 6)); + r[offset + 7 * i + 2] = (byte) (t1 >> 2); + r[offset + 7 * i + 3] = (byte) ((t1 >> 10) | (t2 << 4)); + r[offset + 7 * i + 4] = (byte) (t2 >> 4); + r[offset + 7 * i + 5] = (byte) ((t2 >> 12) | (t3 << 2)); + r[offset + 7 * i + 6] = (byte) (t3 >> 6); + } + } + + public void getnoise(byte[] seed, byte nonce) { + byte[] buf = new byte[4 * PARAM_N]; + int /* t, d, */ a, b; + int i/* ,j */; + + try { + crypto_stream_chacha20(buf, 0, 4 * PARAM_N, nonce, seed); + + for (i = 0; i < PARAM_N; i++) { + /* + * The original C reference code: + * + * t = (buf[4*i] & 0xff) | (((buf[4*i+1]) & 0xff) << 8) | (((buf[4*i+2]) & 0xff) << 16) | + * (((buf[4*i+3]) & 0xff) << 24); + * d = 0; + * for(j=0;j<8;j++) + * d += (t >>> j) & 0x01010101; + * a = ((d >>> 8) & 0xff) + (d & 0xff); + * b = (d >>> 24) + ((d >>> 16) & 0xff); + * + * What the above is doing is reading 32-bit words from buf and then + * setting a and b to the number of 1 bits in the low and high 16 bits. + * We instead use the following technique from "Bit Twiddling Hacks", + * modified for 16-bit quantities: + * + * https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + */ + a = (buf[4 * i] & 0xff) | (((buf[4 * i + 1]) & 0xff) << 8); + a = a - ((a >> 1) & 0x5555); + a = (a & 0x3333) + ((a >> 2) & 0x3333); + a = ((a >> 4) + a) & 0x0F0F; + a = ((a >> 8) + a) & 0x00FF; + + b = (buf[4 * i + 2] & 0xff) | (((buf[4 * i + 3]) & 0xff) << 8); + b = b - ((b >> 1) & 0x5555); + b = (b & 0x3333) + ((b >> 2) & 0x3333); + b = ((b >> 4) + b) & 0x0F0F; + b = ((b >> 8) + b) & 0x00FF; + + coeffs[i] = (char) (a + PARAM_Q - b); + } + } finally { + Arrays.fill(buf, (byte) 0); + } + } + + public void pointwise(Poly a, Poly b) { + int i; + int t; + for (i = 0; i < PARAM_N; i++) { + t = montgomery_reduce(3186 * b.coeffs[i]); /* t is now in Montgomery domain */ + coeffs[i] = (char) montgomery_reduce(a.coeffs[i] * t); /* coeffs[i] is back in normal domain */ + } + } + + public void add(Poly a, Poly b) { + int i; + for (i = 0; i < PARAM_N; i++) + coeffs[i] = (char) barrett_reduce(a.coeffs[i] + b.coeffs[i]); + } + + public void ntt() { + mul_coefficients(coeffs, psis_bitrev_montgomery); + ntt_global(coeffs, omegas_montgomery); + } + + public void invntt() { + bitrev_vector(coeffs); + ntt_global(coeffs, omegas_inv_montgomery); + mul_coefficients(coeffs, psis_inv_montgomery); + } + } + + /** + * Derives the public "a" value from a 32-byte seed. + * + * @param coeffs The 1024 16-bit coefficients of "a" on exit. + * @param seed The 32-byte seed to use to generate "a". + * + * The base class implementation is not constant-time but usually + * this doesn't matter for the public "a" value. However, as + * described in the New Hope paper, non constant-time generation + * of "a" can be a problem in anonymity networks like Tor. + * + * This function can be overridden in subclasses to provide a + * different method for generating "a". The NewHopeTor class + * provides such an example. + * + * Reference: https://cryptojedi.org/papers/newhope-20160803.pdf + */ + protected void uniform(char[] coeffs, byte[] seed) { + int pos = 0, ctr = 0; + int val; + long[] state = new long[25]; + int nblocks = 14; + byte[] buf = new byte[SHAKE128_RATE * nblocks]; + + try { + shake128_absorb(state, seed, 0, SEEDBYTES); + + shake128_squeezeblocks(buf, 0, nblocks, state); + + while (ctr < PARAM_N) { + val = ((buf[pos] & 0xff) | ((buf[pos + 1] & 0xff) << 8)); + if (val < 5 * PARAM_Q) + coeffs[ctr++] = (char) val; + pos += 2; + if (pos > SHAKE128_RATE * nblocks - 2) { + nblocks = 1; + shake128_squeezeblocks(buf, 0, nblocks, state); + pos = 0; + } + } + } finally { + Arrays.fill(state, 0); + Arrays.fill(buf, (byte) 0); + } + } + + // -------------- reduce.c -------------- + + private static final int qinv = 12287; // -inverse_mod(p,2^18) + private static final int rlog = 18; + + private static int montgomery_reduce(int a) { + int u; + + u = (a * qinv); + u &= ((1 << rlog) - 1); + u *= PARAM_Q; + a = a + u; + return a >>> 18; + } + + private static int barrett_reduce(int a) { + int u; + a &= 0xffff; + u = (a * 5) >> 16; + u *= PARAM_Q; + a -= u; + return a & 0xffff; + } + + // -------------- error_correction.c -------------- + + private static int abs(int v) { + int mask = v >> 31; + return (v ^ mask) - mask; + } + + private static int f(int[] v0, int v0offset, int[] v1, int v1offset, int x) { + int xit, t, r, b; + + // Next 6 lines compute t = x/PARAM_Q; + b = x * 2730; + t = b >> 25; + b = x - t * 12289; + b = 12288 - b; + b >>= 31; + t -= b; + + r = t & 1; + xit = (t >> 1); + v0[v0offset] = xit + r; // v0 = round(x/(2*PARAM_Q)) + + t -= 1; + r = t & 1; + v1[v1offset] = (t >> 1) + r; + + return abs(x - ((v0[v0offset]) * 2 * PARAM_Q)); + } + + private static int g(int x) { + int t, c, b; + + // Next 6 lines compute t = x/(4*PARAM_Q); + b = x * 2730; + t = b >> 27; + b = x - t * 49156; + b = 49155 - b; + b >>= 31; + t -= b; + + c = t & 1; + t = (t >> 1) + c; // t = round(x/(8*PARAM_Q)) + + t *= 8 * PARAM_Q; + + return abs(t - x); + } + + private static int LDDecode(int xi0, int xi1, int xi2, int xi3) { + int t; + + t = g(xi0); + t += g(xi1); + t += g(xi2); + t += g(xi3); + + t -= 8 * PARAM_Q; + t >>= 31; + return t & 1; + } + + private static void helprec(Poly c, Poly v, byte[] seed, byte nonce) { + int[] v0 = new int[8]; + int v_tmp0, v_tmp1, v_tmp2, v_tmp3; + int k; + int rbit; + byte[] rand = new byte[32]; + int i; + + try { + crypto_stream_chacha20(rand, 0, 32, ((long) nonce) << 56, seed); + + for (i = 0; i < 256; i++) { + rbit = (rand[i >> 3] >> (i & 7)) & 1; + + k = f(v0, 0, v0, 4, 8 * v.coeffs[0 + i] + 4 * rbit); + k += f(v0, 1, v0, 5, 8 * v.coeffs[256 + i] + 4 * rbit); + k += f(v0, 2, v0, 6, 8 * v.coeffs[512 + i] + 4 * rbit); + k += f(v0, 3, v0, 7, 8 * v.coeffs[768 + i] + 4 * rbit); + + k = (2 * PARAM_Q - 1 - k) >> 31; + + v_tmp0 = ((~k) & v0[0]) ^ (k & v0[4]); + v_tmp1 = ((~k) & v0[1]) ^ (k & v0[5]); + v_tmp2 = ((~k) & v0[2]) ^ (k & v0[6]); + v_tmp3 = ((~k) & v0[3]) ^ (k & v0[7]); + + c.coeffs[0 + i] = (char) ((v_tmp0 - v_tmp3) & 3); + c.coeffs[256 + i] = (char) ((v_tmp1 - v_tmp3) & 3); + c.coeffs[512 + i] = (char) ((v_tmp2 - v_tmp3) & 3); + c.coeffs[768 + i] = (char) ((-k + 2 * v_tmp3) & 3); + } + } finally { + Arrays.fill(v0, 0); + Arrays.fill(rand, (byte) 0); + } + } + + private static void rec(byte[] key, Poly v, Poly c) { + int i; + int tmp0, tmp1, tmp2, tmp3; + + for (i = 0; i < 32; i++) + key[i] = 0; + + for (i = 0; i < 256; i++) { + char c768 = c.coeffs[768 + i]; + tmp0 = 16 * PARAM_Q + 8 * (int) v.coeffs[0 + i] - PARAM_Q * (2 * c.coeffs[0 + i] + c768); + tmp1 = 16 * PARAM_Q + 8 * (int) v.coeffs[256 + i] - PARAM_Q * (2 * c.coeffs[256 + i] + c768); + tmp2 = 16 * PARAM_Q + 8 * (int) v.coeffs[512 + i] - PARAM_Q * (2 * c.coeffs[512 + i] + c768); + tmp3 = 16 * PARAM_Q + 8 * (int) v.coeffs[768 + i] - PARAM_Q * (c768); + + key[i >> 3] |= LDDecode(tmp0, tmp1, tmp2, tmp3) << (i & 7); + } + } + + // -------------- ntt.c -------------- + + private static final int bitrev_table_combined[/* 496 */] = { 524289, 262146, 786435, 131076, 655365, 393222, + 917511, 65544, 589833, 327690, 851979, 196620, 720909, 458766, 983055, 32784, 557073, 294930, 819219, + 163860, 688149, 426006, 950295, 98328, 622617, 360474, 884763, 229404, 753693, 491550, 1015839, 540705, + 278562, 802851, 147492, 671781, 409638, 933927, 81960, 606249, 344106, 868395, 213036, 737325, 475182, + 999471, 573489, 311346, 835635, 180276, 704565, 442422, 966711, 114744, 639033, 376890, 901179, 245820, + 770109, 507966, 1032255, 532545, 270402, 794691, 139332, 663621, 401478, 925767, 598089, 335946, 860235, + 204876, 729165, 467022, 991311, 565329, 303186, 827475, 172116, 696405, 434262, 958551, 106584, 630873, + 368730, 893019, 237660, 761949, 499806, 1024095, 548961, 286818, 811107, 155748, 680037, 417894, 942183, + 614505, 352362, 876651, 221292, 745581, 483438, 1007727, 581745, 319602, 843891, 188532, 712821, 450678, + 974967, 647289, 385146, 909435, 254076, 778365, 516222, 1040511, 528513, 266370, 790659, 659589, 397446, + 921735, 594057, 331914, 856203, 200844, 725133, 462990, 987279, 561297, 299154, 823443, 168084, 692373, + 430230, 954519, 626841, 364698, 888987, 233628, 757917, 495774, 1020063, 544929, 282786, 807075, 676005, + 413862, 938151, 610473, 348330, 872619, 217260, 741549, 479406, 1003695, 577713, 315570, 839859, 708789, + 446646, 970935, 643257, 381114, 905403, 250044, 774333, 512190, 1036479, 536769, 274626, 798915, 667845, + 405702, 929991, 602313, 340170, 864459, 733389, 471246, 995535, 569553, 307410, 831699, 700629, 438486, + 962775, 635097, 372954, 897243, 241884, 766173, 504030, 1028319, 553185, 291042, 815331, 684261, 422118, + 946407, 618729, 356586, 880875, 749805, 487662, 1011951, 585969, 323826, 848115, 717045, 454902, 979191, + 651513, 389370, 913659, 782589, 520446, 1044735, 526593, 788739, 657669, 395526, 919815, 592137, 329994, + 854283, 723213, 461070, 985359, 559377, 297234, 821523, 690453, 428310, 952599, 624921, 362778, 887067, + 755997, 493854, 1018143, 543009, 805155, 674085, 411942, 936231, 608553, 346410, 870699, 739629, 477486, + 1001775, 575793, 837939, 706869, 444726, 969015, 641337, 379194, 903483, 772413, 510270, 1034559, 534849, + 796995, 665925, 403782, 928071, 600393, 862539, 731469, 469326, 993615, 567633, 829779, 698709, 436566, + 960855, 633177, 371034, 895323, 764253, 502110, 1026399, 551265, 813411, 682341, 420198, 944487, 616809, + 878955, 747885, 485742, 1010031, 584049, 846195, 715125, 452982, 977271, 649593, 911739, 780669, 518526, + 1042815, 530817, 792963, 661893, 924039, 596361, 858507, 727437, 465294, 989583, 563601, 825747, 694677, + 432534, 956823, 629145, 891291, 760221, 498078, 1022367, 547233, 809379, 678309, 940455, 612777, 874923, + 743853, 481710, 1005999, 580017, 842163, 711093, 973239, 645561, 907707, 776637, 514494, 1038783, 539073, + 801219, 670149, 932295, 604617, 866763, 735693, 997839, 571857, 834003, 702933, 965079, 637401, 899547, + 768477, 506334, 1030623, 555489, 817635, 686565, 948711, 621033, 883179, 752109, 1014255, 588273, 850419, + 719349, 981495, 653817, 915963, 784893, 1047039, 787971, 656901, 919047, 591369, 853515, 722445, 984591, + 558609, 820755, 689685, 951831, 624153, 886299, 755229, 1017375, 804387, 673317, 935463, 607785, 869931, + 738861, 1001007, 837171, 706101, 968247, 640569, 902715, 771645, 1033791, 796227, 665157, 927303, 861771, + 730701, 992847, 829011, 697941, 960087, 632409, 894555, 763485, 1025631, 812643, 681573, 943719, 878187, + 747117, 1009263, 845427, 714357, 976503, 910971, 779901, 1042047, 792195, 923271, 857739, 726669, 988815, + 824979, 693909, 956055, 890523, 759453, 1021599, 808611, 939687, 874155, 743085, 1005231, 841395, 972471, + 906939, 775869, 1038015, 800451, 931527, 865995, 997071, 833235, 964311, 898779, 767709, 1029855, 816867, + 947943, 882411, 1013487, 849651, 980727, 915195, 1046271, 921351, 855819, 986895, 823059, 954135, 888603, + 1019679, 937767, 872235, 1003311, 970551, 905019, 1036095, 929607, 995151, 962391, 896859, 1027935, 946023, + 1011567, 978807, 1044351, 991119, 958359, 1023903, 1007535, 1040319, 1032159 }; + + // Modified version of bitrev_vector() from the C reference code + // that reduces the number of array bounds checks on the bitrev_table + // from 1024 to 496. The values in the combined table are encoded + // as (i + (r * PARAM_N)) where i and r are the indices to swap. + // The pseudo-code to generate this combined table is: + // p = 0; + // for (i = 0; i < PARAM_N; i++) { + // r = bitrev_table[i]; + // if (i < r) + // bitrev_table_combined[p++] = i + (r * PARAM_N); + // } + private static void bitrev_vector(char[] poly) { + int i, r, p; + char tmp; + + for (p = 0; p < 496; ++p) { + int indices = bitrev_table_combined[p]; + i = indices & 0x03FF; + r = indices >> 10; + tmp = poly[i]; + poly[i] = poly[r]; + poly[r] = tmp; + } + } + + private static void mul_coefficients(char[] poly, char[] factors) { + int i; + + for (i = 0; i < PARAM_N; i++) + poly[i] = (char) montgomery_reduce((poly[i] * factors[i])); + } + + /* GS_bo_to_no; omegas need to be in Montgomery domain */ + private static void ntt_global(char[] a, char[] omega) { + int i, start, j, jTwiddle, distance; + char temp, W; + + for (i = 0; i < 10; i += 2) { + // Even level + distance = (1 << i); + for (start = 0; start < distance; start++) { + jTwiddle = 0; + for (j = start; j < PARAM_N - 1; j += 2 * distance) { + W = omega[jTwiddle++]; + temp = a[j]; + a[j] = (char) (temp + a[j + distance]); // Omit reduction (be lazy) + a[j + distance] = (char) montgomery_reduce((W * ((int) temp + 3 * PARAM_Q - a[j + distance]))); + } + } + + // Odd level + distance <<= 1; + for (start = 0; start < distance; start++) { + jTwiddle = 0; + for (j = start; j < PARAM_N - 1; j += 2 * distance) { + W = omega[jTwiddle++]; + temp = a[j]; + a[j] = (char) barrett_reduce((temp + a[j + distance])); + a[j + distance] = (char) montgomery_reduce((W * ((int) temp + 3 * PARAM_Q - a[j + distance]))); + } + } + } + } + + // -------------- fips202.c -------------- + + /* + * Based on the public domain implementation in + * crypto_hash/keccakc512/simple/ from http://bench.cr.yp.to/supercop.html + * by Ronny Van Keer + * and the public domain "TweetFips202" implementation + * from https://twitter.com/tweetfips202 + * by Gilles Van Assche, Daniel J. Bernstein, and Peter Schwabe + */ + + private static long ROL(long a, int offset) { + return (a << offset) ^ (a >>> (64 - offset)); + } + + private static long load64(byte[] x, int offset) { + long r = 0; + + for (int i = 0; i < 8; ++i) { + r |= ((long) (x[offset + i] & 0xff)) << (8 * i); + } + return r; + } + + private static void store64(byte[] x, int offset, long u) { + int i; + + for (i = 0; i < 8; ++i) { + x[offset + i] = (byte) u; + u >>= 8; + } + } + + private static final long[] KeccakF_RoundConstants = { 0x0000000000000001L, 0x0000000000008082L, + 0x800000000000808aL, 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L, + 0x8000000000008009L, 0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL, + 0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L, 0x8000000000008002L, + 0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, 0x8000000000008080L, + 0x0000000080000001L, 0x8000000080008008L }; + + private static void KeccakF1600_StatePermute(long[] state) { + int round; + + long Aba, Abe, Abi, Abo, Abu; + long Aga, Age, Agi, Ago, Agu; + long Aka, Ake, Aki, Ako, Aku; + long Ama, Ame, Ami, Amo, Amu; + long Asa, Ase, Asi, Aso, Asu; + long BCa, BCe, BCi, BCo, BCu; + long Da, De, Di, Do, Du; + long Eba, Ebe, Ebi, Ebo, Ebu; + long Ega, Ege, Egi, Ego, Egu; + long Eka, Eke, Eki, Eko, Eku; + long Ema, Eme, Emi, Emo, Emu; + long Esa, Ese, Esi, Eso, Esu; + + // copyFromState(A, state) + Aba = state[0]; + Abe = state[1]; + Abi = state[2]; + Abo = state[3]; + Abu = state[4]; + Aga = state[5]; + Age = state[6]; + Agi = state[7]; + Ago = state[8]; + Agu = state[9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for (round = 0; round < 24; round += 2) { + // prepareTheta + BCa = Aba ^ Aga ^ Aka ^ Ama ^ Asa; + BCe = Abe ^ Age ^ Ake ^ Ame ^ Ase; + BCi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; + BCo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; + BCu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; + + // thetaRhoPiChiIotaPrepareTheta(round , A, E) + Da = BCu ^ ROL(BCe, 1); + De = BCa ^ ROL(BCi, 1); + Di = BCe ^ ROL(BCo, 1); + Do = BCi ^ ROL(BCu, 1); + Du = BCo ^ ROL(BCa, 1); + + Aba ^= Da; + BCa = Aba; + Age ^= De; + BCe = ROL(Age, 44); + Aki ^= Di; + BCi = ROL(Aki, 43); + Amo ^= Do; + BCo = ROL(Amo, 21); + Asu ^= Du; + BCu = ROL(Asu, 14); + Eba = BCa ^ ((~BCe) & BCi); + Eba ^= KeccakF_RoundConstants[round]; + Ebe = BCe ^ ((~BCi) & BCo); + Ebi = BCi ^ ((~BCo) & BCu); + Ebo = BCo ^ ((~BCu) & BCa); + Ebu = BCu ^ ((~BCa) & BCe); + + Abo ^= Do; + BCa = ROL(Abo, 28); + Agu ^= Du; + BCe = ROL(Agu, 20); + Aka ^= Da; + BCi = ROL(Aka, 3); + Ame ^= De; + BCo = ROL(Ame, 45); + Asi ^= Di; + BCu = ROL(Asi, 61); + Ega = BCa ^ ((~BCe) & BCi); + Ege = BCe ^ ((~BCi) & BCo); + Egi = BCi ^ ((~BCo) & BCu); + Ego = BCo ^ ((~BCu) & BCa); + Egu = BCu ^ ((~BCa) & BCe); + + Abe ^= De; + BCa = ROL(Abe, 1); + Agi ^= Di; + BCe = ROL(Agi, 6); + Ako ^= Do; + BCi = ROL(Ako, 25); + Amu ^= Du; + BCo = ROL(Amu, 8); + Asa ^= Da; + BCu = ROL(Asa, 18); + Eka = BCa ^ ((~BCe) & BCi); + Eke = BCe ^ ((~BCi) & BCo); + Eki = BCi ^ ((~BCo) & BCu); + Eko = BCo ^ ((~BCu) & BCa); + Eku = BCu ^ ((~BCa) & BCe); + + Abu ^= Du; + BCa = ROL(Abu, 27); + Aga ^= Da; + BCe = ROL(Aga, 36); + Ake ^= De; + BCi = ROL(Ake, 10); + Ami ^= Di; + BCo = ROL(Ami, 15); + Aso ^= Do; + BCu = ROL(Aso, 56); + Ema = BCa ^ ((~BCe) & BCi); + Eme = BCe ^ ((~BCi) & BCo); + Emi = BCi ^ ((~BCo) & BCu); + Emo = BCo ^ ((~BCu) & BCa); + Emu = BCu ^ ((~BCa) & BCe); + + Abi ^= Di; + BCa = ROL(Abi, 62); + Ago ^= Do; + BCe = ROL(Ago, 55); + Aku ^= Du; + BCi = ROL(Aku, 39); + Ama ^= Da; + BCo = ROL(Ama, 41); + Ase ^= De; + BCu = ROL(Ase, 2); + Esa = BCa ^ ((~BCe) & BCi); + Ese = BCe ^ ((~BCi) & BCo); + Esi = BCi ^ ((~BCo) & BCu); + Eso = BCo ^ ((~BCu) & BCa); + Esu = BCu ^ ((~BCa) & BCe); + + // prepareTheta + BCa = Eba ^ Ega ^ Eka ^ Ema ^ Esa; + BCe = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; + BCi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; + BCo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; + BCu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; + + // thetaRhoPiChiIotaPrepareTheta(round+1, E, A) + Da = BCu ^ ROL(BCe, 1); + De = BCa ^ ROL(BCi, 1); + Di = BCe ^ ROL(BCo, 1); + Do = BCi ^ ROL(BCu, 1); + Du = BCo ^ ROL(BCa, 1); + + Eba ^= Da; + BCa = Eba; + Ege ^= De; + BCe = ROL(Ege, 44); + Eki ^= Di; + BCi = ROL(Eki, 43); + Emo ^= Do; + BCo = ROL(Emo, 21); + Esu ^= Du; + BCu = ROL(Esu, 14); + Aba = BCa ^ ((~BCe) & BCi); + Aba ^= KeccakF_RoundConstants[round + 1]; + Abe = BCe ^ ((~BCi) & BCo); + Abi = BCi ^ ((~BCo) & BCu); + Abo = BCo ^ ((~BCu) & BCa); + Abu = BCu ^ ((~BCa) & BCe); + + Ebo ^= Do; + BCa = ROL(Ebo, 28); + Egu ^= Du; + BCe = ROL(Egu, 20); + Eka ^= Da; + BCi = ROL(Eka, 3); + Eme ^= De; + BCo = ROL(Eme, 45); + Esi ^= Di; + BCu = ROL(Esi, 61); + Aga = BCa ^ ((~BCe) & BCi); + Age = BCe ^ ((~BCi) & BCo); + Agi = BCi ^ ((~BCo) & BCu); + Ago = BCo ^ ((~BCu) & BCa); + Agu = BCu ^ ((~BCa) & BCe); + + Ebe ^= De; + BCa = ROL(Ebe, 1); + Egi ^= Di; + BCe = ROL(Egi, 6); + Eko ^= Do; + BCi = ROL(Eko, 25); + Emu ^= Du; + BCo = ROL(Emu, 8); + Esa ^= Da; + BCu = ROL(Esa, 18); + Aka = BCa ^ ((~BCe) & BCi); + Ake = BCe ^ ((~BCi) & BCo); + Aki = BCi ^ ((~BCo) & BCu); + Ako = BCo ^ ((~BCu) & BCa); + Aku = BCu ^ ((~BCa) & BCe); + + Ebu ^= Du; + BCa = ROL(Ebu, 27); + Ega ^= Da; + BCe = ROL(Ega, 36); + Eke ^= De; + BCi = ROL(Eke, 10); + Emi ^= Di; + BCo = ROL(Emi, 15); + Eso ^= Do; + BCu = ROL(Eso, 56); + Ama = BCa ^ ((~BCe) & BCi); + Ame = BCe ^ ((~BCi) & BCo); + Ami = BCi ^ ((~BCo) & BCu); + Amo = BCo ^ ((~BCu) & BCa); + Amu = BCu ^ ((~BCa) & BCe); + + Ebi ^= Di; + BCa = ROL(Ebi, 62); + Ego ^= Do; + BCe = ROL(Ego, 55); + Eku ^= Du; + BCi = ROL(Eku, 39); + Ema ^= Da; + BCo = ROL(Ema, 41); + Ese ^= De; + BCu = ROL(Ese, 2); + Asa = BCa ^ ((~BCe) & BCi); + Ase = BCe ^ ((~BCi) & BCo); + Asi = BCi ^ ((~BCo) & BCu); + Aso = BCo ^ ((~BCu) & BCa); + Asu = BCu ^ ((~BCa) & BCe); + } + + // copyToState(state, A) + state[0] = Aba; + state[1] = Abe; + state[2] = Abi; + state[3] = Abo; + state[4] = Abu; + state[5] = Aga; + state[6] = Age; + state[7] = Agi; + state[8] = Ago; + state[9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; + } + + private static void keccak_absorb(long[] s, int r, byte[] m, int offset, int mlen, byte p) { + int i; + byte[] t = new byte[200]; + + try { + for (i = 0; i < 25; ++i) + s[i] = 0; + + while (mlen >= r) { + for (i = 0; i < r / 8; ++i) + s[i] ^= load64(m, offset + 8 * i); + + KeccakF1600_StatePermute(s); + mlen -= r; + offset += r; + } + + for (i = 0; i < r; ++i) + t[i] = 0; + for (i = 0; i < mlen; ++i) + t[i] = m[offset + i]; + t[i] = p; + t[r - 1] |= 128; + for (i = 0; i < r / 8; ++i) + s[i] ^= load64(t, 8 * i); + } finally { + Arrays.fill(t, (byte) 0); + } + } + + private static void keccak_squeezeblocks(byte[] h, int offset, int nblocks, long[] s, int r) { + int i; + while (nblocks > 0) { + KeccakF1600_StatePermute(s); + for (i = 0; i < (r >> 3); i++) { + store64(h, offset + 8 * i, s[i]); + } + offset += r; + nblocks--; + } + } + + static final int SHAKE128_RATE = 168; + + static void shake128_absorb(long[] s, byte[] input, int inputOffset, int inputByteLen) { + keccak_absorb(s, SHAKE128_RATE, input, inputOffset, inputByteLen, (byte) 0x1F); + } + + static void shake128_squeezeblocks(byte[] output, int outputOffset, int nblocks, long[] s) { + keccak_squeezeblocks(output, outputOffset, nblocks, s, SHAKE128_RATE); + } + + private static final int SHA3_256_RATE = 136; + + private static void sha3256(byte[] output, int outputOffset, byte[] input, int inputOffset, int inputByteLen) { + long[] s = new long[25]; + byte[] t = new byte[SHA3_256_RATE]; + int i; + + try { + keccak_absorb(s, SHA3_256_RATE, input, inputOffset, inputByteLen, (byte) 0x06); + keccak_squeezeblocks(t, 0, 1, s, SHA3_256_RATE); + for (i = 0; i < 32; i++) + output[i] = t[i]; + } finally { + Arrays.fill(s, 0); + Arrays.fill(t, (byte) 0); + } + } + + // -------------- crypto_stream_chacha20.c -------------- + + /* + * Based on the public domain implemntation in + * crypto_stream/chacha20/e/ref from http://bench.cr.yp.to/supercop.html + * by Daniel J. Bernstein + */ + + private static int load_littleendian(byte[] x, int offset) { + return (int) (x[offset + 0] & 0xff) | (((int) (x[offset + 1] & 0xff)) << 8) + | (((int) (x[offset + 2] & 0xff)) << 16) | (((int) (x[offset + 3] & 0xff)) << 24); + } + + private static void store_littleendian(byte[] x, int offset, int u) { + x[offset + 0] = (byte) u; + u >>= 8; + x[offset + 1] = (byte) u; + u >>= 8; + x[offset + 2] = (byte) u; + u >>= 8; + x[offset + 3] = (byte) u; + } + + // Note: This version is limited to a maximum of 2^32 blocks or 2^38 bytes + // because the block number counter is 32-bit instead of 64-bit. This isn't + // a problem for New Hope because the maximum required output is 4096 bytes. + private static void crypto_core_chacha20(byte[] out, int outOffset, long nonce, int blknum, byte[] k) { + int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + int j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + int i; + + j0 = x0 = 0x61707865; // "expa" + j1 = x1 = 0x3320646e; // "nd 3" + j2 = x2 = 0x79622d32; // "2-by" + j3 = x3 = 0x6b206574; // "te k" + j4 = x4 = load_littleendian(k, 0); + j5 = x5 = load_littleendian(k, 4); + j6 = x6 = load_littleendian(k, 8); + j7 = x7 = load_littleendian(k, 12); + j8 = x8 = load_littleendian(k, 16); + j9 = x9 = load_littleendian(k, 20); + j10 = x10 = load_littleendian(k, 24); + j11 = x11 = load_littleendian(k, 28); + j12 = x12 = blknum; + j13 = x13 = 0; + j14 = x14 = (int) nonce; + j15 = x15 = (int) (nonce >>> 32); + + for (i = 20; i > 0; i -= 2) { + x0 += x4; + x12 ^= x0; + x12 = (x12 << 16) | (x12 >>> 16); + x8 += x12; + x4 ^= x8; + x4 = (x4 << 12) | (x4 >>> 20); + x0 += x4; + x12 ^= x0; + x12 = (x12 << 8) | (x12 >>> 24); + x8 += x12; + x4 ^= x8; + x4 = (x4 << 7) | (x4 >>> 25); + x1 += x5; + x13 ^= x1; + x13 = (x13 << 16) | (x13 >>> 16); + x9 += x13; + x5 ^= x9; + x5 = (x5 << 12) | (x5 >>> 20); + x1 += x5; + x13 ^= x1; + x13 = (x13 << 8) | (x13 >>> 24); + x9 += x13; + x5 ^= x9; + x5 = (x5 << 7) | (x5 >>> 25); + x2 += x6; + x14 ^= x2; + x14 = (x14 << 16) | (x14 >>> 16); + x10 += x14; + x6 ^= x10; + x6 = (x6 << 12) | (x6 >>> 20); + x2 += x6; + x14 ^= x2; + x14 = (x14 << 8) | (x14 >>> 24); + x10 += x14; + x6 ^= x10; + x6 = (x6 << 7) | (x6 >>> 25); + x3 += x7; + x15 ^= x3; + x15 = (x15 << 16) | (x15 >>> 16); + x11 += x15; + x7 ^= x11; + x7 = (x7 << 12) | (x7 >>> 20); + x3 += x7; + x15 ^= x3; + x15 = (x15 << 8) | (x15 >>> 24); + x11 += x15; + x7 ^= x11; + x7 = (x7 << 7) | (x7 >>> 25); + x0 += x5; + x15 ^= x0; + x15 = (x15 << 16) | (x15 >>> 16); + x10 += x15; + x5 ^= x10; + x5 = (x5 << 12) | (x5 >>> 20); + x0 += x5; + x15 ^= x0; + x15 = (x15 << 8) | (x15 >>> 24); + x10 += x15; + x5 ^= x10; + x5 = (x5 << 7) | (x5 >>> 25); + x1 += x6; + x12 ^= x1; + x12 = (x12 << 16) | (x12 >>> 16); + x11 += x12; + x6 ^= x11; + x6 = (x6 << 12) | (x6 >>> 20); + x1 += x6; + x12 ^= x1; + x12 = (x12 << 8) | (x12 >>> 24); + x11 += x12; + x6 ^= x11; + x6 = (x6 << 7) | (x6 >>> 25); + x2 += x7; + x13 ^= x2; + x13 = (x13 << 16) | (x13 >>> 16); + x8 += x13; + x7 ^= x8; + x7 = (x7 << 12) | (x7 >>> 20); + x2 += x7; + x13 ^= x2; + x13 = (x13 << 8) | (x13 >>> 24); + x8 += x13; + x7 ^= x8; + x7 = (x7 << 7) | (x7 >>> 25); + x3 += x4; + x14 ^= x3; + x14 = (x14 << 16) | (x14 >>> 16); + x9 += x14; + x4 ^= x9; + x4 = (x4 << 12) | (x4 >>> 20); + x3 += x4; + x14 ^= x3; + x14 = (x14 << 8) | (x14 >>> 24); + x9 += x14; + x4 ^= x9; + x4 = (x4 << 7) | (x4 >>> 25); + } + + x0 += j0; + x1 += j1; + x2 += j2; + x3 += j3; + x4 += j4; + x5 += j5; + x6 += j6; + x7 += j7; + x8 += j8; + x9 += j9; + x10 += j10; + x11 += j11; + x12 += j12; + x13 += j13; + x14 += j14; + x15 += j15; + + store_littleendian(out, outOffset + 0, x0); + store_littleendian(out, outOffset + 4, x1); + store_littleendian(out, outOffset + 8, x2); + store_littleendian(out, outOffset + 12, x3); + store_littleendian(out, outOffset + 16, x4); + store_littleendian(out, outOffset + 20, x5); + store_littleendian(out, outOffset + 24, x6); + store_littleendian(out, outOffset + 28, x7); + store_littleendian(out, outOffset + 32, x8); + store_littleendian(out, outOffset + 36, x9); + store_littleendian(out, outOffset + 40, x10); + store_littleendian(out, outOffset + 44, x11); + store_littleendian(out, outOffset + 48, x12); + store_littleendian(out, outOffset + 52, x13); + store_littleendian(out, outOffset + 56, x14); + store_littleendian(out, outOffset + 60, x15); + } + + private static void crypto_stream_chacha20(byte[] c, int coffset, int clen, long n, byte[] k) { + int blknum = 0; + + if (clen <= 0) + return; + + while (clen >= 64) { + crypto_core_chacha20(c, coffset, n, blknum, k); + ++blknum; + clen -= 64; + coffset += 64; + } + + if (clen != 0) { + byte[] block = new byte[64]; + try { + crypto_core_chacha20(block, 0, n, blknum, k); + for (int i = 0; i < clen; ++i) + c[coffset + i] = block[i]; + } finally { + Arrays.fill(block, (byte) 0); + } + } + } + + // -------------- precomp.c -------------- + + private static final char[/* PARAM_N/2 */] omegas_montgomery = { 4075, 6974, 7373, 7965, 3262, 5079, 522, 2169, + 6364, 1018, 1041, 8775, 2344, 11011, 5574, 1973, 4536, 1050, 6844, 3860, 3818, 6118, 2683, 1190, 4789, 7822, + 7540, 6752, 5456, 4449, 3789, 12142, 11973, 382, 3988, 468, 6843, 5339, 6196, 3710, 11316, 1254, 5435, + 10930, 3998, 10256, 10367, 3879, 11889, 1728, 6137, 4948, 5862, 6136, 3643, 6874, 8724, 654, 10302, 1702, + 7083, 6760, 56, 3199, 9987, 605, 11785, 8076, 5594, 9260, 6403, 4782, 6212, 4624, 9026, 8689, 4080, 11868, + 6221, 3602, 975, 8077, 8851, 9445, 5681, 3477, 1105, 142, 241, 12231, 1003, 3532, 5009, 1956, 6008, 11404, + 7377, 2049, 10968, 12097, 7591, 5057, 3445, 4780, 2920, 7048, 3127, 8120, 11279, 6821, 11502, 8807, 12138, + 2127, 2839, 3957, 431, 1579, 6383, 9784, 5874, 677, 3336, 6234, 2766, 1323, 9115, 12237, 2031, 6956, 6413, + 2281, 3969, 3991, 12133, 9522, 4737, 10996, 4774, 5429, 11871, 3772, 453, 5908, 2882, 1805, 2051, 1954, + 11713, 3963, 2447, 6142, 8174, 3030, 1843, 2361, 12071, 2908, 3529, 3434, 3202, 7796, 2057, 5369, 11939, + 1512, 6906, 10474, 11026, 49, 10806, 5915, 1489, 9789, 5942, 10706, 10431, 7535, 426, 8974, 3757, 10314, + 9364, 347, 5868, 9551, 9634, 6554, 10596, 9280, 11566, 174, 2948, 2503, 6507, 10723, 11606, 2459, 64, 3656, + 8455, 5257, 5919, 7856, 1747, 9166, 5486, 9235, 6065, 835, 3570, 4240, 11580, 4046, 10970, 9139, 1058, 8210, + 11848, 922, 7967, 1958, 10211, 1112, 3728, 4049, 11130, 5990, 1404, 325, 948, 11143, 6190, 295, 11637, 5766, + 8212, 8273, 2919, 8527, 6119, 6992, 8333, 1360, 2555, 6167, 1200, 7105, 7991, 3329, 9597, 12121, 5106, 5961, + 10695, 10327, 3051, 9923, 4896, 9326, 81, 3091, 1000, 7969, 4611, 726, 1853, 12149, 4255, 11112, 2768, + 10654, 1062, 2294, 3553, 4805, 2747, 4846, 8577, 9154, 1170, 2319, 790, 11334, 9275, 9088, 1326, 5086, 9094, + 6429, 11077, 10643, 3504, 3542, 8668, 9744, 1479, 1, 8246, 7143, 11567, 10984, 4134, 5736, 4978, 10938, + 5777, 8961, 4591, 5728, 6461, 5023, 9650, 7468, 949, 9664, 2975, 11726, 2744, 9283, 10092, 5067, 12171, + 2476, 3748, 11336, 6522, 827, 9452, 5374, 12159, 7935, 3296, 3949, 9893, 4452, 10908, 2525, 3584, 8112, + 8011, 10616, 4989, 6958, 11809, 9447, 12280, 1022, 11950, 9821, 11745, 5791, 5092, 2089, 9005, 2881, 3289, + 2013, 9048, 729, 7901, 1260, 5755, 4632, 11955, 2426, 10593, 1428, 4890, 5911, 3932, 9558, 8830, 3637, 5542, + 145, 5179, 8595, 3707, 10530, 355, 3382, 4231, 9741, 1207, 9041, 7012, 1168, 10146, 11224, 4645, 11885, + 10911, 10377, 435, 7952, 4096, 493, 9908, 6845, 6039, 2422, 2187, 9723, 8643, 9852, 9302, 6022, 7278, 1002, + 4284, 5088, 1607, 7313, 875, 8509, 9430, 1045, 2481, 5012, 7428, 354, 6591, 9377, 11847, 2401, 1067, 7188, + 11516, 390, 8511, 8456, 7270, 545, 8585, 9611, 12047, 1537, 4143, 4714, 4885, 1017, 5084, 1632, 3066, 27, + 1440, 8526, 9273, 12046, 11618, 9289, 3400, 9890, 3136, 7098, 8758, 11813, 7384, 3985, 11869, 6730, 10745, + 10111, 2249, 4048, 2884, 11136, 2126, 1630, 9103, 5407, 2686, 9042, 2969, 8311, 9424, 9919, 8779, 5332, + 10626, 1777, 4654, 10863, 7351, 3636, 9585, 5291, 8374, 2166, 4919, 12176, 9140, 12129, 7852, 12286, 4895, + 10805, 2780, 5195, 2305, 7247, 9644, 4053, 10600, 3364, 3271, 4057, 4414, 9442, 7917, 2174 }; + + private static final char[/* PARAM_N/2 */] omegas_inv_montgomery = { 4075, 5315, 4324, 4916, 10120, 11767, 7210, + 9027, 10316, 6715, 1278, 9945, 3514, 11248, 11271, 5925, 147, 8500, 7840, 6833, 5537, 4749, 4467, 7500, + 11099, 9606, 6171, 8471, 8429, 5445, 11239, 7753, 9090, 12233, 5529, 5206, 10587, 1987, 11635, 3565, 5415, + 8646, 6153, 6427, 7341, 6152, 10561, 400, 8410, 1922, 2033, 8291, 1359, 6854, 11035, 973, 8579, 6093, 6950, + 5446, 11821, 8301, 11907, 316, 52, 3174, 10966, 9523, 6055, 8953, 11612, 6415, 2505, 5906, 10710, 11858, + 8332, 9450, 10162, 151, 3482, 787, 5468, 1010, 4169, 9162, 5241, 9369, 7509, 8844, 7232, 4698, 192, 1321, + 10240, 4912, 885, 6281, 10333, 7280, 8757, 11286, 58, 12048, 12147, 11184, 8812, 6608, 2844, 3438, 4212, + 11314, 8687, 6068, 421, 8209, 3600, 3263, 7665, 6077, 7507, 5886, 3029, 6695, 4213, 504, 11684, 2302, 1962, + 1594, 6328, 7183, 168, 2692, 8960, 4298, 5184, 11089, 6122, 9734, 10929, 3956, 5297, 6170, 3762, 9370, 4016, + 4077, 6523, 652, 11994, 6099, 1146, 11341, 11964, 10885, 6299, 1159, 8240, 8561, 11177, 2078, 10331, 4322, + 11367, 441, 4079, 11231, 3150, 1319, 8243, 709, 8049, 8719, 11454, 6224, 3054, 6803, 3123, 10542, 4433, + 6370, 7032, 3834, 8633, 12225, 9830, 683, 1566, 5782, 9786, 9341, 12115, 723, 3009, 1693, 5735, 2655, 2738, + 6421, 11942, 2925, 1975, 8532, 3315, 11863, 4754, 1858, 1583, 6347, 2500, 10800, 6374, 1483, 12240, 1263, + 1815, 5383, 10777, 350, 6920, 10232, 4493, 9087, 8855, 8760, 9381, 218, 9928, 10446, 9259, 4115, 6147, 9842, + 8326, 576, 10335, 10238, 10484, 9407, 6381, 11836, 8517, 418, 6860, 7515, 1293, 7552, 2767, 156, 8298, 8320, + 10008, 5876, 5333, 10258, 10115, 4372, 2847, 7875, 8232, 9018, 8925, 1689, 8236, 2645, 5042, 9984, 7094, + 9509, 1484, 7394, 3, 4437, 160, 3149, 113, 7370, 10123, 3915, 6998, 2704, 8653, 4938, 1426, 7635, 10512, + 1663, 6957, 3510, 2370, 2865, 3978, 9320, 3247, 9603, 6882, 3186, 10659, 10163, 1153, 9405, 8241, 10040, + 2178, 1544, 5559, 420, 8304, 4905, 476, 3531, 5191, 9153, 2399, 8889, 3000, 671, 243, 3016, 3763, 10849, + 12262, 9223, 10657, 7205, 11272, 7404, 7575, 8146, 10752, 242, 2678, 3704, 11744, 5019, 3833, 3778, 11899, + 773, 5101, 11222, 9888, 442, 2912, 5698, 11935, 4861, 7277, 9808, 11244, 2859, 3780, 11414, 4976, 10682, + 7201, 8005, 11287, 5011, 6267, 2987, 2437, 3646, 2566, 10102, 9867, 6250, 5444, 2381, 11796, 8193, 4337, + 11854, 1912, 1378, 404, 7644, 1065, 2143, 11121, 5277, 3248, 11082, 2548, 8058, 8907, 11934, 1759, 8582, + 3694, 7110, 12144, 6747, 8652, 3459, 2731, 8357, 6378, 7399, 10861, 1696, 9863, 334, 7657, 6534, 11029, + 4388, 11560, 3241, 10276, 9000, 9408, 3284, 10200, 7197, 6498, 544, 2468, 339, 11267, 9, 2842, 480, 5331, + 7300, 1673, 4278, 4177, 8705, 9764, 1381, 7837, 2396, 8340, 8993, 4354, 130, 6915, 2837, 11462, 5767, 953, + 8541, 9813, 118, 7222, 2197, 3006, 9545, 563, 9314, 2625, 11340, 4821, 2639, 7266, 5828, 6561, 7698, 3328, + 6512, 1351, 7311, 6553, 8155, 1305, 722, 5146, 4043, 12288, 10810, 2545, 3621, 8747, 8785, 1646, 1212, 5860, + 3195, 7203, 10963, 3201, 3014, 955, 11499, 9970, 11119, 3135, 3712, 7443, 9542, 7484, 8736, 9995, 11227, + 1635, 9521, 1177, 8034, 140, 10436, 11563, 7678, 4320, 11289, 9198, 12208, 2963, 7393, 2366, 9238 }; + + private static final char[/* PARAM_N */] psis_bitrev_montgomery = { 4075, 6974, 7373, 7965, 3262, 5079, 522, 2169, + 6364, 1018, 1041, 8775, 2344, 11011, 5574, 1973, 4536, 1050, 6844, 3860, 3818, 6118, 2683, 1190, 4789, 7822, + 7540, 6752, 5456, 4449, 3789, 12142, 11973, 382, 3988, 468, 6843, 5339, 6196, 3710, 11316, 1254, 5435, + 10930, 3998, 10256, 10367, 3879, 11889, 1728, 6137, 4948, 5862, 6136, 3643, 6874, 8724, 654, 10302, 1702, + 7083, 6760, 56, 3199, 9987, 605, 11785, 8076, 5594, 9260, 6403, 4782, 6212, 4624, 9026, 8689, 4080, 11868, + 6221, 3602, 975, 8077, 8851, 9445, 5681, 3477, 1105, 142, 241, 12231, 1003, 3532, 5009, 1956, 6008, 11404, + 7377, 2049, 10968, 12097, 7591, 5057, 3445, 4780, 2920, 7048, 3127, 8120, 11279, 6821, 11502, 8807, 12138, + 2127, 2839, 3957, 431, 1579, 6383, 9784, 5874, 677, 3336, 6234, 2766, 1323, 9115, 12237, 2031, 6956, 6413, + 2281, 3969, 3991, 12133, 9522, 4737, 10996, 4774, 5429, 11871, 3772, 453, 5908, 2882, 1805, 2051, 1954, + 11713, 3963, 2447, 6142, 8174, 3030, 1843, 2361, 12071, 2908, 3529, 3434, 3202, 7796, 2057, 5369, 11939, + 1512, 6906, 10474, 11026, 49, 10806, 5915, 1489, 9789, 5942, 10706, 10431, 7535, 426, 8974, 3757, 10314, + 9364, 347, 5868, 9551, 9634, 6554, 10596, 9280, 11566, 174, 2948, 2503, 6507, 10723, 11606, 2459, 64, 3656, + 8455, 5257, 5919, 7856, 1747, 9166, 5486, 9235, 6065, 835, 3570, 4240, 11580, 4046, 10970, 9139, 1058, 8210, + 11848, 922, 7967, 1958, 10211, 1112, 3728, 4049, 11130, 5990, 1404, 325, 948, 11143, 6190, 295, 11637, 5766, + 8212, 8273, 2919, 8527, 6119, 6992, 8333, 1360, 2555, 6167, 1200, 7105, 7991, 3329, 9597, 12121, 5106, 5961, + 10695, 10327, 3051, 9923, 4896, 9326, 81, 3091, 1000, 7969, 4611, 726, 1853, 12149, 4255, 11112, 2768, + 10654, 1062, 2294, 3553, 4805, 2747, 4846, 8577, 9154, 1170, 2319, 790, 11334, 9275, 9088, 1326, 5086, 9094, + 6429, 11077, 10643, 3504, 3542, 8668, 9744, 1479, 1, 8246, 7143, 11567, 10984, 4134, 5736, 4978, 10938, + 5777, 8961, 4591, 5728, 6461, 5023, 9650, 7468, 949, 9664, 2975, 11726, 2744, 9283, 10092, 5067, 12171, + 2476, 3748, 11336, 6522, 827, 9452, 5374, 12159, 7935, 3296, 3949, 9893, 4452, 10908, 2525, 3584, 8112, + 8011, 10616, 4989, 6958, 11809, 9447, 12280, 1022, 11950, 9821, 11745, 5791, 5092, 2089, 9005, 2881, 3289, + 2013, 9048, 729, 7901, 1260, 5755, 4632, 11955, 2426, 10593, 1428, 4890, 5911, 3932, 9558, 8830, 3637, 5542, + 145, 5179, 8595, 3707, 10530, 355, 3382, 4231, 9741, 1207, 9041, 7012, 1168, 10146, 11224, 4645, 11885, + 10911, 10377, 435, 7952, 4096, 493, 9908, 6845, 6039, 2422, 2187, 9723, 8643, 9852, 9302, 6022, 7278, 1002, + 4284, 5088, 1607, 7313, 875, 8509, 9430, 1045, 2481, 5012, 7428, 354, 6591, 9377, 11847, 2401, 1067, 7188, + 11516, 390, 8511, 8456, 7270, 545, 8585, 9611, 12047, 1537, 4143, 4714, 4885, 1017, 5084, 1632, 3066, 27, + 1440, 8526, 9273, 12046, 11618, 9289, 3400, 9890, 3136, 7098, 8758, 11813, 7384, 3985, 11869, 6730, 10745, + 10111, 2249, 4048, 2884, 11136, 2126, 1630, 9103, 5407, 2686, 9042, 2969, 8311, 9424, 9919, 8779, 5332, + 10626, 1777, 4654, 10863, 7351, 3636, 9585, 5291, 8374, 2166, 4919, 12176, 9140, 12129, 7852, 12286, 4895, + 10805, 2780, 5195, 2305, 7247, 9644, 4053, 10600, 3364, 3271, 4057, 4414, 9442, 7917, 2174, 3947, 11951, + 2455, 6599, 10545, 10975, 3654, 2894, 7681, 7126, 7287, 12269, 4119, 3343, 2151, 1522, 7174, 7350, 11041, + 2442, 2148, 5959, 6492, 8330, 8945, 5598, 3624, 10397, 1325, 6565, 1945, 11260, 10077, 2674, 3338, 3276, + 11034, 506, 6505, 1392, 5478, 8778, 1178, 2776, 3408, 10347, 11124, 2575, 9489, 12096, 6092, 10058, 4167, + 6085, 923, 11251, 11912, 4578, 10669, 11914, 425, 10453, 392, 10104, 8464, 4235, 8761, 7376, 2291, 3375, + 7954, 8896, 6617, 7790, 1737, 11667, 3982, 9342, 6680, 636, 6825, 7383, 512, 4670, 2900, 12050, 7735, 994, + 1687, 11883, 7021, 146, 10485, 1403, 5189, 6094, 2483, 2054, 3042, 10945, 3981, 10821, 11826, 8882, 8151, + 180, 9600, 7684, 5219, 10880, 6780, 204, 11232, 2600, 7584, 3121, 3017, 11053, 7814, 7043, 4251, 4739, + 11063, 6771, 7073, 9261, 2360, 11925, 1928, 11825, 8024, 3678, 3205, 3359, 11197, 5209, 8581, 3238, 8840, + 1136, 9363, 1826, 3171, 4489, 7885, 346, 2068, 1389, 8257, 3163, 4840, 6127, 8062, 8921, 612, 4238, 10763, + 8067, 125, 11749, 10125, 5416, 2110, 716, 9839, 10584, 11475, 11873, 3448, 343, 1908, 4538, 10423, 7078, + 4727, 1208, 11572, 3589, 2982, 1373, 1721, 10753, 4103, 2429, 4209, 5412, 5993, 9011, 438, 3515, 7228, 1218, + 8347, 5232, 8682, 1327, 7508, 4924, 448, 1014, 10029, 12221, 4566, 5836, 12229, 2717, 1535, 3200, 5588, + 5845, 412, 5102, 7326, 3744, 3056, 2528, 7406, 8314, 9202, 6454, 6613, 1417, 10032, 7784, 1518, 3765, 4176, + 5063, 9828, 2275, 6636, 4267, 6463, 2065, 7725, 3495, 8328, 8755, 8144, 10533, 5966, 12077, 9175, 9520, + 5596, 6302, 8400, 579, 6781, 11014, 5734, 11113, 11164, 4860, 1131, 10844, 9068, 8016, 9694, 3837, 567, + 9348, 7000, 6627, 7699, 5082, 682, 11309, 5207, 4050, 7087, 844, 7434, 3769, 293, 9057, 6940, 9344, 10883, + 2633, 8190, 3944, 5530, 5604, 3480, 2171, 9282, 11024, 2213, 8136, 3805, 767, 12239, 216, 11520, 6763, + 10353, 7, 8566, 845, 7235, 3154, 4360, 3285, 10268, 2832, 3572, 1282, 7559, 3229, 8360, 10583, 6105, 3120, + 6643, 6203, 8536, 8348, 6919, 3536, 9199, 10891, 11463, 5043, 1658, 5618, 8787, 5789, 4719, 751, 11379, + 6389, 10783, 3065, 7806, 6586, 2622, 5386, 510, 7628, 6921, 578, 10345, 11839, 8929, 4684, 12226, 7154, + 9916, 7302, 8481, 3670, 11066, 2334, 1590, 7878, 10734, 1802, 1891, 5103, 6151, 8820, 3418, 7846, 9951, + 4693, 417, 9996, 9652, 4510, 2946, 5461, 365, 881, 1927, 1015, 11675, 11009, 1371, 12265, 2485, 11385, 5039, + 6742, 8449, 1842, 12217, 8176, 9577, 4834, 7937, 9461, 2643, 11194, 3045, 6508, 4094, 3451, 7911, 11048, + 5406, 4665, 3020, 6616, 11345, 7519, 3669, 5287, 1790, 7014, 5410, 11038, 11249, 2035, 6125, 10407, 4565, + 7315, 5078, 10506, 2840, 2478, 9270, 4194, 9195, 4518, 7469, 1160, 6878, 2730, 10421, 10036, 1734, 3815, + 10939, 5832, 10595, 10759, 4423, 8420, 9617, 7119, 11010, 11424, 9173, 189, 10080, 10526, 3466, 10588, 7592, + 3578, 11511, 7785, 9663, 530, 12150, 8957, 2532, 3317, 9349, 10243, 1481, 9332, 3454, 3758, 7899, 4218, + 2593, 11410, 2276, 982, 6513, 1849, 8494, 9021, 4523, 7988, 8, 457, 648, 150, 8000, 2307, 2301, 874, 5650, + 170, 9462, 2873, 9855, 11498, 2535, 11169, 5808, 12268, 9687, 1901, 7171, 11787, 3846, 1573, 6063, 3793, + 466, 11259, 10608, 3821, 6320, 4649, 6263, 2929 }; + + private static final char[/* PARAM_N */] psis_inv_montgomery = { 256, 10570, 1510, 7238, 1034, 7170, 6291, 7921, + 11665, 3422, 4000, 2327, 2088, 5565, 795, 10647, 1521, 5484, 2539, 7385, 1055, 7173, 8047, 11683, 1669, + 1994, 3796, 5809, 4341, 9398, 11876, 12230, 10525, 12037, 12253, 3506, 4012, 9351, 4847, 2448, 7372, 9831, + 3160, 2207, 5582, 2553, 7387, 6322, 9681, 1383, 10731, 1533, 219, 5298, 4268, 7632, 6357, 9686, 8406, 4712, + 9451, 10128, 4958, 5975, 11387, 8649, 11769, 6948, 11526, 12180, 1740, 10782, 6807, 2728, 7412, 4570, 4164, + 4106, 11120, 12122, 8754, 11784, 3439, 5758, 11356, 6889, 9762, 11928, 1704, 1999, 10819, 12079, 12259, + 7018, 11536, 1648, 1991, 2040, 2047, 2048, 10826, 12080, 8748, 8272, 8204, 1172, 1923, 7297, 2798, 7422, + 6327, 4415, 7653, 6360, 11442, 12168, 7005, 8023, 9924, 8440, 8228, 2931, 7441, 1063, 3663, 5790, 9605, + 10150, 1450, 8985, 11817, 10466, 10273, 12001, 3470, 7518, 1074, 1909, 7295, 9820, 4914, 702, 5367, 7789, + 8135, 9940, 1420, 3714, 11064, 12114, 12264, 1752, 5517, 9566, 11900, 1700, 3754, 5803, 829, 1874, 7290, + 2797, 10933, 5073, 7747, 8129, 6428, 6185, 11417, 1631, 233, 5300, 9535, 10140, 11982, 8734, 8270, 2937, + 10953, 8587, 8249, 2934, 9197, 4825, 5956, 4362, 9401, 1343, 3703, 529, 10609, 12049, 6988, 6265, 895, 3639, + 4031, 4087, 4095, 585, 10617, 8539, 4731, 4187, 9376, 3095, 9220, 10095, 10220, 1460, 10742, 12068, 1724, + 5513, 11321, 6884, 2739, 5658, 6075, 4379, 11159, 10372, 8504, 4726, 9453, 3106, 7466, 11600, 10435, 8513, + 9994, 8450, 9985, 3182, 10988, 8592, 2983, 9204, 4826, 2445, 5616, 6069, 867, 3635, 5786, 11360, 5134, 2489, + 10889, 12089, 1727, 7269, 2794, 9177, 1311, 5454, 9557, 6632, 2703, 9164, 10087, 1441, 3717, 531, 3587, + 2268, 324, 5313, 759, 1864, 5533, 2546, 7386, 9833, 8427, 4715, 11207, 1601, 7251, 4547, 11183, 12131, 1733, + 10781, 10318, 1474, 10744, 5046, 4232, 11138, 10369, 6748, 964, 7160, 4534, 7670, 8118, 8182, 4680, 11202, + 6867, 981, 8918, 1274, 182, 26, 7026, 8026, 11680, 12202, 10521, 1503, 7237, 4545, 5916, 9623, 8397, 11733, + 10454, 3249, 9242, 6587, 941, 1890, 270, 10572, 6777, 9746, 6659, 6218, 6155, 6146, 878, 1881, 7291, 11575, + 12187, 1741, 7271, 8061, 11685, 6936, 4502, 9421, 4857, 4205, 7623, 1089, 10689, 1527, 8996, 10063, 11971, + 10488, 6765, 2722, 3900, 9335, 11867, 6962, 11528, 5158, 4248, 4118, 5855, 2592, 5637, 6072, 2623, 7397, + 8079, 9932, 4930, 5971, 853, 3633, 519, 8852, 11798, 3441, 11025, 1575, 225, 8810, 11792, 12218, 3501, 9278, + 3081, 9218, 4828, 7712, 8124, 11694, 12204, 3499, 4011, 573, 3593, 5780, 7848, 9899, 10192, 1456, 208, 7052, + 2763, 7417, 11593, 10434, 12024, 8740, 11782, 10461, 3250, 5731, 7841, 9898, 1414, 202, 3540, 7528, 2831, + 2160, 10842, 5060, 4234, 4116, 588, 84, 12, 7024, 2759, 9172, 6577, 11473, 1639, 9012, 3043, 7457, 6332, + 11438, 1634, 1989, 9062, 11828, 8712, 11778, 12216, 10523, 6770, 9745, 10170, 4964, 9487, 6622, 946, 8913, + 6540, 6201, 4397, 9406, 8366, 9973, 8447, 8229, 11709, 8695, 10020, 3187, 5722, 2573, 10901, 6824, 4486, + 4152, 9371, 8361, 2950, 2177, 311, 1800, 9035, 8313, 11721, 3430, 490, 70, 10, 1757, 251, 3547, 7529, 11609, + 3414, 7510, 4584, 4166, 9373, 1339, 5458, 7802, 11648, 1664, 7260, 9815, 10180, 6721, 9738, 10169, 8475, + 8233, 9954, 1422, 8981, 1283, 5450, 11312, 1616, 3742, 11068, 10359, 4991, 713, 3613, 9294, 8350, 4704, 672, + 96, 7036, 9783, 11931, 3460, 5761, 823, 10651, 12055, 10500, 1500, 5481, 783, 3623, 11051, 8601, 8251, 8201, + 11705, 10450, 5004, 4226, 7626, 2845, 2162, 3820, 7568, 9859, 3164, 452, 10598, 1514, 5483, 6050, 6131, + 4387, 7649, 8115, 6426, 918, 8909, 8295, 1185, 5436, 11310, 8638, 1234, 5443, 11311, 5127, 2488, 2111, + 10835, 5059, 7745, 2862, 3920, 560, 80, 1767, 2008, 3798, 11076, 6849, 2734, 10924, 12094, 8750, 1250, + 10712, 6797, 971, 7161, 1023, 8924, 4786, 7706, 4612, 4170, 7618, 6355, 4419, 5898, 11376, 10403, 10264, + 6733, 4473, 639, 5358, 2521, 9138, 3061, 5704, 4326, 618, 5355, 765, 5376, 768, 7132, 4530, 9425, 3102, + 9221, 6584, 11474, 10417, 10266, 12000, 6981, 6264, 4406, 2385, 7363, 4563, 4163, 7617, 9866, 3165, 9230, + 11852, 10471, 5007, 5982, 11388, 5138, 734, 3616, 11050, 12112, 6997, 11533, 12181, 10518, 12036, 3475, + 2252, 7344, 9827, 4915, 9480, 6621, 4457, 7659, 9872, 6677, 4465, 4149, 7615, 4599, 657, 3605, 515, 10607, + 6782, 4480, 640, 1847, 3775, 5806, 2585, 5636, 9583, 1369, 10729, 8555, 10000, 11962, 5220, 7768, 8132, + 8184, 9947, 1421, 203, 29, 8782, 11788, 1684, 10774, 10317, 4985, 9490, 8378, 4708, 11206, 5112, 5997, 7879, + 11659, 12199, 8765, 10030, 4944, 5973, 6120, 6141, 6144, 7900, 11662, 1666, 238, 34, 3516, 5769, 9602, 8394, + 9977, 6692, 956, 10670, 6791, 9748, 11926, 8726, 11780, 5194, 742, 106, 8793, 10034, 3189, 10989, 5081, + 4237, 5872, 4350, 2377, 10873, 6820, 6241, 11425, 10410, 10265, 3222, 5727, 9596, 4882, 2453, 2106, 3812, + 11078, 12116, 5242, 4260, 11142, 8614, 11764, 12214, 5256, 4262, 4120, 11122, 5100, 11262, 5120, 2487, 5622, + 9581, 8391, 8221, 2930, 10952, 12098, 6995, 6266, 9673, 4893, 699, 3611, 4027, 5842, 11368, 1624, 232, 8811, + 8281, 1183, 169, 8802, 3013, 2186, 5579, 797, 3625, 4029, 11109, 1587, 7249, 11569, 8675, 6506, 2685, 10917, + 12093, 12261, 12285, 1755, 7273, 1039, 1904, 272, 3550, 9285, 3082, 5707, 6082, 4380, 7648, 11626, 5172, + 4250, 9385, 8363, 8217, 4685, 5936, 848, 8899, 6538, 934, 1889, 3781, 9318, 10109, 10222, 6727, 961, 5404, + 772, 5377, 9546, 8386, 1198, 8949, 3034, 2189, 7335, 4559, 5918, 2601, 10905, 5069, 9502, 3113, 7467, 8089, + 11689, 5181, 9518, 8382, 2953, 3933, 4073, 4093, 7607, 8109, 2914, 5683, 4323, 11151, 1593, 10761, 6804, + 972, 3650, 2277, 5592, 4310, 7638, 9869, 4921, 703, 1856, 9043, 4803, 9464, 1352, 8971, 11815, 5199, 7765, + 6376, 4422, 7654, 2849, 407, 8836, 6529, 7955, 2892, 9191, 1313, 10721, 12065, 12257, 1751, 9028, 8312, + 2943, 2176, 3822, 546, 78, 8789, 11789, 10462, 12028, 6985, 4509, 9422, 1346, 5459, 4291, 613, 10621, 6784, + 9747, 3148, 7472, 2823, 5670, 810, 7138, 8042, 4660, 7688, 6365, 6176, 6149, 2634, 5643, 9584, 10147, 11983, + 5223, 9524, 11894, 10477, 8519, 1217, 3685, 2282, 326, 10580, 3267, 7489, 4581, 2410, 5611, 11335, 6886, + 8006, 8166, 11700, 3427, 11023, 8597, 10006, 3185, 455, 65, 5276, 7776, 4622, 5927, 7869, 9902, 11948, 5218, + 2501, 5624, 2559, 10899, 1557, 1978, 10816, 10323, 8497, 4725, 675, 1852, 10798, 12076, 10503, 3256, 9243, + 3076, 2195, 10847, 12083, 10504, 12034, 10497 }; +} diff --git a/src/main/java/com/southernstorm/noise/crypto/NewHopeTor.java b/src/main/java/com/southernstorm/noise/crypto/NewHopeTor.java new file mode 100644 index 0000000..d25e95b --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/NewHopeTor.java @@ -0,0 +1,3690 @@ +/* + * Based on the public domain C reference code for New Hope. + * This Java version is also placed into the public domain. + * + * Original authors: Erdem Alkim, Léo Ducas, Thomas Pöppelmann, Peter Schwabe + * Java port: Rhys Weatherley + */ + +package com.southernstorm.noise.crypto; + +import java.util.Arrays; + +/** + * New Hope key exchange algorithm, "torref" variant. + * + * This version of New Hope implements the alternative constant-time + * method for generating the public "a" value for anonymity networks + * like Tor. It is not binary-compatible with the standard New Hope + * implementation in the NewHope class. + * + * Reference: https://cryptojedi.org/papers/newhope-20160803.pdf + * + * @see NewHope + */ +public class NewHopeTor extends NewHope { + + public NewHopeTor() { + } + + @Override + protected void uniform(char[] coeffs, byte[] seed) { + long[] state = new long[25]; + int nblocks = 16; + byte[] buf = new byte[SHAKE128_RATE * nblocks]; + char[] x = new char[buf.length / 2]; + + try { + shake128_absorb(state, seed, 0, SEEDBYTES); + do { + shake128_squeezeblocks(buf, 0, nblocks, state); + for (int i = buf.length - 2; i >= 0; i -= 2) { + x[i / 2] = (char) ((buf[i] & 0xff) | ((buf[i + 1] & 0xff) << 8)); + } + } while (discardtopoly(coeffs, x)); + } finally { + Arrays.fill(state, 0); + Arrays.fill(buf, (byte) 0); + Arrays.fill(x, (char) 0); + } + } + + private static boolean discardtopoly(char[] coeffs, char[] x) { + int i, r = 0; + + for (i = 0; i < 16; i++) + batcher84(x, i); + + // Check whether we're safe: + for (i = 1008; i < 1024; i++) + r |= 61444 - x[i]; + if ((r >>= 31) != 0) + return true; + + // If we are, copy coefficients to polynomial: + for (i = 0; i < PARAM_N; i++) + coeffs[i] = x[i]; + + return false; + } + + private static void batcher84(char[] x, int offset) { + int c, t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 16]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 16] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 48]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 48] ^= t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 32]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 48]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 48] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 80]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 112]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 112] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 96]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 112]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 112] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 64]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 96]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 80]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 112]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 112] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 144]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 176]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 160]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 176]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 208]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 240]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 240] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 224]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 240]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 240] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 192]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 224]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 208]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 240]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 240] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 128]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 192]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 160]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 224]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 144]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 208]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 176]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 240]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 240] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 272]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 304]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 288]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 304]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 336]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 368]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 352]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 368]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 320]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 352]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 336]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 368]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 400]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 432]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 416]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 432]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 464]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 496]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 496] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 480]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 496]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 496] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 448]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 480]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 464]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 496]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 496] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 384]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 448]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 416]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 480]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 400]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 464]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 432]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 496]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 496] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 256]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 384]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 320]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 448]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 288]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 416]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 352]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 480]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 272]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 400]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 336]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 464]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 304]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 432]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 368]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 496]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 496] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 512]; + t = (x[offset + 512] ^ x[offset + 528]) & (c >> 31); + x[offset + 512] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 560]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 512]; + t = (x[offset + 512] ^ x[offset + 544]) & (c >> 31); + x[offset + 512] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 560]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 592]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 624]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 608]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 624]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 512]; + t = (x[offset + 512] ^ x[offset + 576]) & (c >> 31); + x[offset + 512] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 608]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 592]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 624]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 656]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 688]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 672]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 688]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 720]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 752]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 736]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 752]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 704]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 736]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 720]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 752]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 512]; + t = (x[offset + 512] ^ x[offset + 640]) & (c >> 31); + x[offset + 512] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 704]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 672]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 736]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 656]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 720]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 688]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 752]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 768]; + t = (x[offset + 768] ^ x[offset + 784]) & (c >> 31); + x[offset + 768] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 816]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 768]; + t = (x[offset + 768] ^ x[offset + 800]) & (c >> 31); + x[offset + 768] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 816]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 848]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 880]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 864]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 880]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 768]; + t = (x[offset + 768] ^ x[offset + 832]) & (c >> 31); + x[offset + 768] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 864]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 848]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 880]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 896]; + t = (x[offset + 896] ^ x[offset + 912]) & (c >> 31); + x[offset + 896] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 944]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 896]; + t = (x[offset + 896] ^ x[offset + 928]) & (c >> 31); + x[offset + 896] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 944]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 960]; + t = (x[offset + 960] ^ x[offset + 976]) & (c >> 31); + x[offset + 960] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 992]; + t = (x[offset + 992] ^ x[offset + 1008]) & (c >> 31); + x[offset + 992] ^= t; + x[offset + 1008] ^= t; + c = 61444 - x[offset + 960]; + t = (x[offset + 960] ^ x[offset + 992]) & (c >> 31); + x[offset + 960] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 1008]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 1008] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 896]; + t = (x[offset + 896] ^ x[offset + 960]) & (c >> 31); + x[offset + 896] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 992]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 976]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 1008]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 1008] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 768]; + t = (x[offset + 768] ^ x[offset + 896]) & (c >> 31); + x[offset + 768] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 960]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 928]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 992]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 912]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 976]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 944]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 1008]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 1008] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 512]; + t = (x[offset + 512] ^ x[offset + 768]) & (c >> 31); + x[offset + 512] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 896]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 832]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 960]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 800]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 928]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 864]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 992]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 784]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 912]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 848]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 976]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 816]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 944]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 880]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 1008]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 1008] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 512]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 768]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 512]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 640]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 896]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 640]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 512]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 576]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 832]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 576]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 704]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 960]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 704]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 576]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 512]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 544]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 800]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 544]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 672]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 928]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 672]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 544]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 608]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 864]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 608]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 736]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 992]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 736]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 608]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 544]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 512]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 528]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 784]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 528]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 656]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 912]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 656]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 528]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 592]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 848]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 592]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 720]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 976]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 720]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 592]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 528]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 560]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 816]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 560]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 688]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 944]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 688]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 560]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 624]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 880]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 624]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 752]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 1008]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 1008] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 752]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 624]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 560]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 528]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 512]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 1024]; + t = (x[offset + 1024] ^ x[offset + 1040]) & (c >> 31); + x[offset + 1024] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1072]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 1024]; + t = (x[offset + 1024] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1024] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1072]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1136]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1136]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1024]; + t = (x[offset + 1024] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1024] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1136]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1152]; + t = (x[offset + 1152] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1152] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1152]; + t = (x[offset + 1152] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1152] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1216]; + t = (x[offset + 1216] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1216] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1264]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 1216]; + t = (x[offset + 1216] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1216] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1264]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1152]; + t = (x[offset + 1152] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1152] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1264]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1024]; + t = (x[offset + 1024] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1024] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1264]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1280]; + t = (x[offset + 1280] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1280] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1312]; + t = (x[offset + 1312] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1312] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1280]; + t = (x[offset + 1280] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1280] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1024]; + t = (x[offset + 1024] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1024] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1152]; + t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1152] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1216]; + t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1216] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1152]; + t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1152] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1216]; + t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1216] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 0]; + t = (x[offset + 0] ^ x[offset + 1024]) & (c >> 31); + x[offset + 0] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 512]; + t = (x[offset + 512] ^ x[offset + 1024]) & (c >> 31); + x[offset + 512] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 1280]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 768]; + t = (x[offset + 768] ^ x[offset + 1280]) & (c >> 31); + x[offset + 768] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 256]; + t = (x[offset + 256] ^ x[offset + 512]) & (c >> 31); + x[offset + 256] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 768]; + t = (x[offset + 768] ^ x[offset + 1024]) & (c >> 31); + x[offset + 768] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 1152]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 1152]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 640]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 896]; + t = (x[offset + 896] ^ x[offset + 1152]) & (c >> 31); + x[offset + 896] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 128]; + t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); + x[offset + 128] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 384]; + t = (x[offset + 384] ^ x[offset + 512]) & (c >> 31); + x[offset + 384] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 640]; + t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); + x[offset + 640] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 896]; + t = (x[offset + 896] ^ x[offset + 1024]) & (c >> 31); + x[offset + 896] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 1152]; + t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1152] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 1088]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 1088]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 576]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 1088]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 1216]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 1216]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 704]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 960]; + t = (x[offset + 960] ^ x[offset + 1216]) & (c >> 31); + x[offset + 960] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 576]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 960]; + t = (x[offset + 960] ^ x[offset + 1088]) & (c >> 31); + x[offset + 960] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 64]; + t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); + x[offset + 64] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 192]; + t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); + x[offset + 192] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 320]; + t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); + x[offset + 320] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 448]; + t = (x[offset + 448] ^ x[offset + 512]) & (c >> 31); + x[offset + 448] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 576]; + t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); + x[offset + 576] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 704]; + t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); + x[offset + 704] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 832]; + t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); + x[offset + 832] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 960]; + t = (x[offset + 960] ^ x[offset + 1024]) & (c >> 31); + x[offset + 960] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 1088]; + t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1088] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1216]; + t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1216] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 1056]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 1056]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 1312]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 1312]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 544]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 1056]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 1184]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 1184]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 672]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 1184]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 544]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 1056]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 1120]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 1120]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 608]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 1120]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 1248]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 1248]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 736]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 992]; + t = (x[offset + 992] ^ x[offset + 1248]) & (c >> 31); + x[offset + 992] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 608]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 992]; + t = (x[offset + 992] ^ x[offset + 1120]) & (c >> 31); + x[offset + 992] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 544]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 992]; + t = (x[offset + 992] ^ x[offset + 1056]) & (c >> 31); + x[offset + 992] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1312] ^= t; + c = 61444 - x[offset + 32]; + t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); + x[offset + 32] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 96]; + t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); + x[offset + 96] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 160]; + t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); + x[offset + 160] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 224]; + t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); + x[offset + 224] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 288]; + t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); + x[offset + 288] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 352]; + t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); + x[offset + 352] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 416]; + t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); + x[offset + 416] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 480]; + t = (x[offset + 480] ^ x[offset + 512]) & (c >> 31); + x[offset + 480] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 544]; + t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); + x[offset + 544] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 608]; + t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); + x[offset + 608] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 672]; + t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); + x[offset + 672] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 736]; + t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); + x[offset + 736] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 800]; + t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); + x[offset + 800] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 864]; + t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); + x[offset + 864] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 928]; + t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); + x[offset + 928] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 992]; + t = (x[offset + 992] ^ x[offset + 1024]) & (c >> 31); + x[offset + 992] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 1056]; + t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1056] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1120]; + t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1120] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1184]; + t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1184] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1248]; + t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1248] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 1040]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 1040]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 1296]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 1296]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 528]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 1040]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 1168]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 1168]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 656]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 1168]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 528]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 1040]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 1104]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 1104]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 592]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 1104]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 1232]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 1232]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 720]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 1232]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 592]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 1104]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 528]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 1040]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 1072]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 1072]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 1328]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 1328]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 560]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 1072]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 1200]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 1200]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 688]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 1200]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 560]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 1072]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 1136]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 1136]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 624]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 1136]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 1264]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 1264]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 752]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 752] ^= t; + c = 61444 - x[offset + 1008]; + t = (x[offset + 1008] ^ x[offset + 1264]) & (c >> 31); + x[offset + 1008] ^= t; + x[offset + 1264] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 368] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 624]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 624] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 880] ^= t; + c = 61444 - x[offset + 1008]; + t = (x[offset + 1008] ^ x[offset + 1136]) & (c >> 31); + x[offset + 1008] ^= t; + x[offset + 1136] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 176] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 304] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 432] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 560]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 560] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 688] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 816] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 944] ^= t; + c = 61444 - x[offset + 1008]; + t = (x[offset + 1008] ^ x[offset + 1072]) & (c >> 31); + x[offset + 1008] ^= t; + x[offset + 1072] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1200] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1328] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 80] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 144] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 208] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 272] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 336] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 400] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 464] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 528]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 528] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 592] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 656] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 720] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 784] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 848] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 912] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 976] ^= t; + c = 61444 - x[offset + 1008]; + t = (x[offset + 1008] ^ x[offset + 1040]) & (c >> 31); + x[offset + 1008] ^= t; + x[offset + 1040] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1104] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1168] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1232] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1296] ^= t; + c = 61444 - x[offset + 16]; + t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); + x[offset + 16] ^= t; + x[offset + 32] ^= t; + c = 61444 - x[offset + 48]; + t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); + x[offset + 48] ^= t; + x[offset + 64] ^= t; + c = 61444 - x[offset + 80]; + t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); + x[offset + 80] ^= t; + x[offset + 96] ^= t; + c = 61444 - x[offset + 112]; + t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); + x[offset + 112] ^= t; + x[offset + 128] ^= t; + c = 61444 - x[offset + 144]; + t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); + x[offset + 144] ^= t; + x[offset + 160] ^= t; + c = 61444 - x[offset + 176]; + t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); + x[offset + 176] ^= t; + x[offset + 192] ^= t; + c = 61444 - x[offset + 208]; + t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); + x[offset + 208] ^= t; + x[offset + 224] ^= t; + c = 61444 - x[offset + 240]; + t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); + x[offset + 240] ^= t; + x[offset + 256] ^= t; + c = 61444 - x[offset + 272]; + t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); + x[offset + 272] ^= t; + x[offset + 288] ^= t; + c = 61444 - x[offset + 304]; + t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); + x[offset + 304] ^= t; + x[offset + 320] ^= t; + c = 61444 - x[offset + 336]; + t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); + x[offset + 336] ^= t; + x[offset + 352] ^= t; + c = 61444 - x[offset + 368]; + t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); + x[offset + 368] ^= t; + x[offset + 384] ^= t; + c = 61444 - x[offset + 400]; + t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); + x[offset + 400] ^= t; + x[offset + 416] ^= t; + c = 61444 - x[offset + 432]; + t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); + x[offset + 432] ^= t; + x[offset + 448] ^= t; + c = 61444 - x[offset + 464]; + t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); + x[offset + 464] ^= t; + x[offset + 480] ^= t; + c = 61444 - x[offset + 496]; + t = (x[offset + 496] ^ x[offset + 512]) & (c >> 31); + x[offset + 496] ^= t; + x[offset + 512] ^= t; + c = 61444 - x[offset + 528]; + t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); + x[offset + 528] ^= t; + x[offset + 544] ^= t; + c = 61444 - x[offset + 560]; + t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); + x[offset + 560] ^= t; + x[offset + 576] ^= t; + c = 61444 - x[offset + 592]; + t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); + x[offset + 592] ^= t; + x[offset + 608] ^= t; + c = 61444 - x[offset + 624]; + t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); + x[offset + 624] ^= t; + x[offset + 640] ^= t; + c = 61444 - x[offset + 656]; + t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); + x[offset + 656] ^= t; + x[offset + 672] ^= t; + c = 61444 - x[offset + 688]; + t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); + x[offset + 688] ^= t; + x[offset + 704] ^= t; + c = 61444 - x[offset + 720]; + t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); + x[offset + 720] ^= t; + x[offset + 736] ^= t; + c = 61444 - x[offset + 752]; + t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); + x[offset + 752] ^= t; + x[offset + 768] ^= t; + c = 61444 - x[offset + 784]; + t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); + x[offset + 784] ^= t; + x[offset + 800] ^= t; + c = 61444 - x[offset + 816]; + t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); + x[offset + 816] ^= t; + x[offset + 832] ^= t; + c = 61444 - x[offset + 848]; + t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); + x[offset + 848] ^= t; + x[offset + 864] ^= t; + c = 61444 - x[offset + 880]; + t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); + x[offset + 880] ^= t; + x[offset + 896] ^= t; + c = 61444 - x[offset + 912]; + t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); + x[offset + 912] ^= t; + x[offset + 928] ^= t; + c = 61444 - x[offset + 944]; + t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); + x[offset + 944] ^= t; + x[offset + 960] ^= t; + c = 61444 - x[offset + 976]; + t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); + x[offset + 976] ^= t; + x[offset + 992] ^= t; + c = 61444 - x[offset + 1008]; + t = (x[offset + 1008] ^ x[offset + 1024]) & (c >> 31); + x[offset + 1008] ^= t; + x[offset + 1024] ^= t; + c = 61444 - x[offset + 1040]; + t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); + x[offset + 1040] ^= t; + x[offset + 1056] ^= t; + c = 61444 - x[offset + 1072]; + t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); + x[offset + 1072] ^= t; + x[offset + 1088] ^= t; + c = 61444 - x[offset + 1104]; + t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); + x[offset + 1104] ^= t; + x[offset + 1120] ^= t; + c = 61444 - x[offset + 1136]; + t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); + x[offset + 1136] ^= t; + x[offset + 1152] ^= t; + c = 61444 - x[offset + 1168]; + t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); + x[offset + 1168] ^= t; + x[offset + 1184] ^= t; + c = 61444 - x[offset + 1200]; + t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); + x[offset + 1200] ^= t; + x[offset + 1216] ^= t; + c = 61444 - x[offset + 1232]; + t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); + x[offset + 1232] ^= t; + x[offset + 1248] ^= t; + c = 61444 - x[offset + 1264]; + t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); + x[offset + 1264] ^= t; + x[offset + 1280] ^= t; + c = 61444 - x[offset + 1296]; + t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); + x[offset + 1296] ^= t; + x[offset + 1312] ^= t; + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/Poly1305.java b/src/main/java/com/southernstorm/noise/crypto/Poly1305.java new file mode 100644 index 0000000..11fdc9b --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/Poly1305.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.util.Arrays; + +import com.southernstorm.noise.protocol.Destroyable; + +/** + * Simple implementation of the Poly1305 message authenticator. + */ +public final class Poly1305 implements Destroyable { + + // 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. + */ + 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); + } + + /** + * 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; + } + } + } + } + + /** + * 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. + + // 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); + + // 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]; + + // 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; + } + } + + /** + * 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; + } + + @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); + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/RijndaelAES.java b/src/main/java/com/southernstorm/noise/crypto/RijndaelAES.java new file mode 100644 index 0000000..76b46a0 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/RijndaelAES.java @@ -0,0 +1,618 @@ +// This implementation is a straight C-to-Java port of the +// public domain code from the original Rijndael authors: +// http://web.cs.ucdavis.edu/~rogaway/ocb/ocb-ref/ +// The original license declaration follows (all modifications +// are released under the same terms): + +/* + * rijndael-alg-fst.c + * + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.southernstorm.noise.crypto; + +import java.util.Arrays; + +/** + * Public domain fallback implementation of AES in ECB mode. + */ +public class RijndaelAES { + + private int[] rk; + private int Nr; + + /** + * Constructs a new key schedule object for AES encryption/decryption. + */ + public RijndaelAES() { + rk = new int[60]; + Nr = 14; + } + + /** + * Destroys the sensitive state in this key schedule. + */ + public void destroy() { + Arrays.fill(rk, 0); + } + + private static int GETU32(byte[] buf, int offset) { + return ((buf[offset] & 0xFF) << 24) | ((buf[offset + 1] & 0xFF) << 16) | ((buf[offset + 2] & 0xFF) << 8) + | (buf[offset + 3] & 0xFF); + } + + private static void PUTU32(byte[] buf, int offset, int value) { + buf[offset] = (byte) (value >> 24); + buf[offset + 1] = (byte) (value >> 16); + buf[offset + 2] = (byte) (value >> 8); + buf[offset + 3] = (byte) value; + } + + /** + * Expand the cipher key into the encryption key schedule. + * + * @return the number of rounds for the given cipher key size. + */ + public int setupEnc(byte[] cipherKey, int offset, int keyBits) { + int i = 0; + int temp; + + rk[0] = GETU32(cipherKey, offset); + rk[1] = GETU32(cipherKey, offset + 4); + rk[2] = GETU32(cipherKey, offset + 8); + rk[3] = GETU32(cipherKey, offset + 12); + int rkoffset = 0; + if (keyBits == 128) { + for (;;) { + temp = rk[rkoffset + 3]; + rk[rkoffset + 4] = rk[rkoffset] ^ (Te4[(temp >> 16) & 0xff] & 0xff000000) + ^ (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ (Te4[(temp) & 0xff] & 0x0000ff00) + ^ (Te4[(temp >> 24) & 0xff] & 0x000000ff) ^ rcon[i]; + rk[rkoffset + 5] = rk[rkoffset + 1] ^ rk[rkoffset + 4]; + rk[rkoffset + 6] = rk[rkoffset + 2] ^ rk[rkoffset + 5]; + rk[rkoffset + 7] = rk[rkoffset + 3] ^ rk[rkoffset + 6]; + if (++i == 10) { + Nr = 10; + return Nr; + } + rkoffset += 4; + } + } + rk[rkoffset + 4] = GETU32(cipherKey, offset + 16); + rk[rkoffset + 5] = GETU32(cipherKey, offset + 20); + if (keyBits == 192) { + for (;;) { + temp = rk[rkoffset + 5]; + rk[rkoffset + 6] = rk[rkoffset] ^ (Te4[(temp >> 16) & 0xff] & 0xff000000) + ^ (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ (Te4[(temp) & 0xff] & 0x0000ff00) + ^ (Te4[(temp >> 24) & 0xff] & 0x000000ff) ^ rcon[i]; + rk[rkoffset + 7] = rk[rkoffset + 1] ^ rk[rkoffset + 6]; + rk[rkoffset + 8] = rk[rkoffset + 2] ^ rk[rkoffset + 7]; + rk[rkoffset + 9] = rk[rkoffset + 3] ^ rk[rkoffset + 8]; + if (++i == 8) { + Nr = 12; + return Nr; + } + rk[rkoffset + 10] = rk[rkoffset + 4] ^ rk[rkoffset + 9]; + rk[rkoffset + 11] = rk[rkoffset + 5] ^ rk[rkoffset + 10]; + rkoffset += 6; + } + } + rk[rkoffset + 6] = GETU32(cipherKey, offset + 24); + rk[rkoffset + 7] = GETU32(cipherKey, offset + 28); + if (keyBits == 256) { + for (;;) { + temp = rk[rkoffset + 7]; + rk[rkoffset + 8] = rk[rkoffset + 0] ^ (Te4[(temp >> 16) & 0xff] & 0xff000000) + ^ (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ (Te4[(temp) & 0xff] & 0x0000ff00) + ^ (Te4[(temp >> 24) & 0xff] & 0x000000ff) ^ rcon[i]; + rk[rkoffset + 9] = rk[rkoffset + 1] ^ rk[rkoffset + 8]; + rk[rkoffset + 10] = rk[rkoffset + 2] ^ rk[rkoffset + 9]; + rk[rkoffset + 11] = rk[rkoffset + 3] ^ rk[rkoffset + 10]; + if (++i == 7) { + Nr = 14; + return Nr; + } + temp = rk[rkoffset + 11]; + rk[rkoffset + 12] = rk[rkoffset + 4] ^ (Te4[(temp >> 24) & 0xff] & 0xff000000) + ^ (Te4[(temp >> 16) & 0xff] & 0x00ff0000) ^ (Te4[(temp >> 8) & 0xff] & 0x0000ff00) + ^ (Te4[(temp) & 0xff] & 0x000000ff); + rk[rkoffset + 13] = rk[rkoffset + 5] ^ rk[rkoffset + 12]; + rk[rkoffset + 14] = rk[rkoffset + 6] ^ rk[rkoffset + 13]; + rk[rkoffset + 15] = rk[rkoffset + 7] ^ rk[rkoffset + 14]; + + rkoffset += 8; + } + } + return 0; + } + + /** + * Expand the cipher key into the decryption key schedule. + * + * @return the number of rounds for the given cipher key size. + */ + public int setupDec(byte[] cipherKey, int offset, int keyBits) { + int Nr, i, j; + int temp; + + /* expand the cipher key: */ + Nr = setupEnc(cipherKey, offset, keyBits); + /* invert the order of the round keys: */ + for (i = 0, j = 4 * Nr; i < j; i += 4, j -= 4) { + temp = rk[i]; + rk[i] = rk[j]; + rk[j] = temp; + temp = rk[i + 1]; + rk[i + 1] = rk[j + 1]; + rk[j + 1] = temp; + temp = rk[i + 2]; + rk[i + 2] = rk[j + 2]; + rk[j + 2] = temp; + temp = rk[i + 3]; + rk[i + 3] = rk[j + 3]; + rk[j + 3] = temp; + } + /* apply the inverse MixColumn transform to all round keys but the first and the last: */ + int rkoffset = 0; + for (i = 1; i < Nr; i++) { + rkoffset += 4; + rk[rkoffset + 0] = Td0[Te4[(rk[rkoffset] >> 24) & 0xff] & 0xff] + ^ Td1[Te4[(rk[rkoffset] >> 16) & 0xff] & 0xff] ^ Td2[Te4[(rk[rkoffset] >> 8) & 0xff] & 0xff] + ^ Td3[Te4[(rk[rkoffset]) & 0xff] & 0xff]; + rk[rkoffset + 1] = Td0[Te4[(rk[rkoffset + 1] >> 24) & 0xff] & 0xff] + ^ Td1[Te4[(rk[rkoffset + 1] >> 16) & 0xff] & 0xff] ^ Td2[Te4[(rk[rkoffset + 1] >> 8) & 0xff] & 0xff] + ^ Td3[Te4[(rk[rkoffset + 1]) & 0xff] & 0xff]; + rk[rkoffset + 2] = Td0[Te4[(rk[rkoffset + 2] >> 24) & 0xff] & 0xff] + ^ Td1[Te4[(rk[rkoffset + 2] >> 16) & 0xff] & 0xff] ^ Td2[Te4[(rk[rkoffset + 2] >> 8) & 0xff] & 0xff] + ^ Td3[Te4[(rk[rkoffset + 2]) & 0xff] & 0xff]; + rk[rkoffset + 3] = Td0[Te4[(rk[rkoffset + 3] >> 24) & 0xff] & 0xff] + ^ Td1[Te4[(rk[rkoffset + 3] >> 16) & 0xff] & 0xff] ^ Td2[Te4[(rk[rkoffset + 3] >> 8) & 0xff] & 0xff] + ^ Td3[Te4[(rk[rkoffset + 3]) & 0xff] & 0xff]; + } + this.Nr = Nr; + return Nr; + } + + public void encrypt(byte[] pt, int ptoffset, byte[] ct, int ctoffset) { + int s0, s1, s2, s3, t0, t1, t2, t3; + int r, rkoffset; + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(pt, ptoffset) ^ rk[0]; + s1 = GETU32(pt, ptoffset + 4) ^ rk[1]; + s2 = GETU32(pt, ptoffset + 8) ^ rk[2]; + s3 = GETU32(pt, ptoffset + 12) ^ rk[3]; + + /* + * Nr - 1 full rounds: + */ + r = Nr >> 1; + rkoffset = 0; + for (;;) { + t0 = Te0[(s0 >> 24) & 0xff] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[(s3) & 0xff] + ^ rk[rkoffset + 4]; + t1 = Te0[(s1 >> 24) & 0xff] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[(s0) & 0xff] + ^ rk[rkoffset + 5]; + t2 = Te0[(s2 >> 24) & 0xff] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[(s1) & 0xff] + ^ rk[rkoffset + 6]; + t3 = Te0[(s3 >> 24) & 0xff] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[(s2) & 0xff] + ^ rk[rkoffset + 7]; + + rkoffset += 8; + if (--r == 0) { + break; + } + + s0 = Te0[(t0 >> 24) & 0xff] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[(t3) & 0xff] + ^ rk[rkoffset]; + s1 = Te0[(t1 >> 24) & 0xff] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[(t0) & 0xff] + ^ rk[rkoffset + 1]; + s2 = Te0[(t2 >> 24) & 0xff] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[(t1) & 0xff] + ^ rk[rkoffset + 2]; + s3 = Te0[(t3 >> 24) & 0xff] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[(t2) & 0xff] + ^ rk[rkoffset + 3]; + } + + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = (Te4[(t0 >> 24) & 0xff] & 0xff000000) ^ (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) + ^ (Te4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ (Te4[(t3) & 0xff] & 0x000000ff) ^ rk[rkoffset]; + PUTU32(ct, ctoffset, s0); + s1 = (Te4[(t1 >> 24) & 0xff] & 0xff000000) ^ (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) + ^ (Te4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ (Te4[(t0) & 0xff] & 0x000000ff) ^ rk[rkoffset + 1]; + PUTU32(ct, ctoffset + 4, s1); + s2 = (Te4[(t2 >> 24) & 0xff] & 0xff000000) ^ (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) + ^ (Te4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ (Te4[(t1) & 0xff] & 0x000000ff) ^ rk[rkoffset + 2]; + PUTU32(ct, ctoffset + 8, s2); + s3 = (Te4[(t3 >> 24) & 0xff] & 0xff000000) ^ (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) + ^ (Te4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ (Te4[(t2) & 0xff] & 0x000000ff) ^ rk[rkoffset + 3]; + PUTU32(ct, ctoffset + 12, s3); + } + + public void decrypt(byte[] ct, int ctoffset, byte[] pt, int ptoffset) { + int s0, s1, s2, s3, t0, t1, t2, t3; + int r, rkoffset; + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(ct, ctoffset) ^ rk[0]; + s1 = GETU32(ct, ctoffset + 4) ^ rk[1]; + s2 = GETU32(ct, ctoffset + 8) ^ rk[2]; + s3 = GETU32(ct, ctoffset + 12) ^ rk[3]; + + /* + * Nr - 1 full rounds: + */ + r = Nr >> 1; + rkoffset = 0; + for (;;) { + t0 = Td0[(s0 >> 24) & 0xff] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[(s1) & 0xff] + ^ rk[rkoffset + 4]; + t1 = Td0[(s1 >> 24) & 0xff] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[(s2) & 0xff] + ^ rk[rkoffset + 5]; + t2 = Td0[(s2 >> 24) & 0xff] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[(s3) & 0xff] + ^ rk[rkoffset + 6]; + t3 = Td0[(s3 >> 24) & 0xff] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[(s0) & 0xff] + ^ rk[rkoffset + 7]; + + rkoffset += 8; + if (--r == 0) { + break; + } + + s0 = Td0[(t0 >> 24) & 0xff] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[(t1) & 0xff] + ^ rk[rkoffset]; + s1 = Td0[(t1 >> 24) & 0xff] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[(t2) & 0xff] + ^ rk[rkoffset + 1]; + s2 = Td0[(t2 >> 24) & 0xff] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[(t3) & 0xff] + ^ rk[rkoffset + 2]; + s3 = Td0[(t3 >> 24) & 0xff] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[(t0) & 0xff] + ^ rk[rkoffset + 3]; + } + + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = (Td4[(t0 >> 24) & 0xff] & 0xff000000) ^ (Td4[(t3 >> 16) & 0xff] & 0x00ff0000) + ^ (Td4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ (Td4[(t1) & 0xff] & 0x000000ff) ^ rk[rkoffset]; + PUTU32(pt, ptoffset, s0); + s1 = (Td4[(t1 >> 24) & 0xff] & 0xff000000) ^ (Td4[(t0 >> 16) & 0xff] & 0x00ff0000) + ^ (Td4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ (Td4[(t2) & 0xff] & 0x000000ff) ^ rk[rkoffset + 1]; + PUTU32(pt, ptoffset + 4, s1); + s2 = (Td4[(t2 >> 24) & 0xff] & 0xff000000) ^ (Td4[(t1 >> 16) & 0xff] & 0x00ff0000) + ^ (Td4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ (Td4[(t3) & 0xff] & 0x000000ff) ^ rk[rkoffset + 2]; + PUTU32(pt, ptoffset + 8, s2); + s3 = (Td4[(t3 >> 24) & 0xff] & 0xff000000) ^ (Td4[(t2 >> 16) & 0xff] & 0x00ff0000) + ^ (Td4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ (Td4[(t0) & 0xff] & 0x000000ff) ^ rk[rkoffset + 3]; + PUTU32(pt, ptoffset + 12, s3); + } + + private static final int[] Te0 = { 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, + 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, + 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, + 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, + 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, + 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, + 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, + 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, + 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, + 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, + 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, + 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, + 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, + 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, + 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, + 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, + 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, + 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, + 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, + 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, + 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, + 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, + 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, + 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, + 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, }; + private static final int[] Te1 = { 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, + 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, + 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, + 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, + 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, + 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, + 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, + 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, + 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, + 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, + 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, + 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, + 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, + 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, + 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, + 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, + 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, + 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, + 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, + 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, + 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, + 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, + 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, + 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, + 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, }; + private static final int[] Te2 = { 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, + 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, + 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, + 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, + 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, + 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, + 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, + 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, + 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, + 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, + 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, + 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, + 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, + 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, + 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, + 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, + 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, + 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, + 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, + 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, + 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, + 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, + 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, + 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, + 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, }; + private static final int[] Te3 = { + + 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, + 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, + 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, + 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, + 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, + 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, + 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, + 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, + 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, + 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, + 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, + 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, + 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, + 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, + 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, + 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, + 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, + 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, + 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, + 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, + 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, + 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, + 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, + 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, + 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, + 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, }; + private static final int[] Te4 = { 0x63636363, 0x7c7c7c7c, 0x77777777, 0x7b7b7b7b, 0xf2f2f2f2, 0x6b6b6b6b, + 0x6f6f6f6f, 0xc5c5c5c5, 0x30303030, 0x01010101, 0x67676767, 0x2b2b2b2b, 0xfefefefe, 0xd7d7d7d7, 0xabababab, + 0x76767676, 0xcacacaca, 0x82828282, 0xc9c9c9c9, 0x7d7d7d7d, 0xfafafafa, 0x59595959, 0x47474747, 0xf0f0f0f0, + 0xadadadad, 0xd4d4d4d4, 0xa2a2a2a2, 0xafafafaf, 0x9c9c9c9c, 0xa4a4a4a4, 0x72727272, 0xc0c0c0c0, 0xb7b7b7b7, + 0xfdfdfdfd, 0x93939393, 0x26262626, 0x36363636, 0x3f3f3f3f, 0xf7f7f7f7, 0xcccccccc, 0x34343434, 0xa5a5a5a5, + 0xe5e5e5e5, 0xf1f1f1f1, 0x71717171, 0xd8d8d8d8, 0x31313131, 0x15151515, 0x04040404, 0xc7c7c7c7, 0x23232323, + 0xc3c3c3c3, 0x18181818, 0x96969696, 0x05050505, 0x9a9a9a9a, 0x07070707, 0x12121212, 0x80808080, 0xe2e2e2e2, + 0xebebebeb, 0x27272727, 0xb2b2b2b2, 0x75757575, 0x09090909, 0x83838383, 0x2c2c2c2c, 0x1a1a1a1a, 0x1b1b1b1b, + 0x6e6e6e6e, 0x5a5a5a5a, 0xa0a0a0a0, 0x52525252, 0x3b3b3b3b, 0xd6d6d6d6, 0xb3b3b3b3, 0x29292929, 0xe3e3e3e3, + 0x2f2f2f2f, 0x84848484, 0x53535353, 0xd1d1d1d1, 0x00000000, 0xedededed, 0x20202020, 0xfcfcfcfc, 0xb1b1b1b1, + 0x5b5b5b5b, 0x6a6a6a6a, 0xcbcbcbcb, 0xbebebebe, 0x39393939, 0x4a4a4a4a, 0x4c4c4c4c, 0x58585858, 0xcfcfcfcf, + 0xd0d0d0d0, 0xefefefef, 0xaaaaaaaa, 0xfbfbfbfb, 0x43434343, 0x4d4d4d4d, 0x33333333, 0x85858585, 0x45454545, + 0xf9f9f9f9, 0x02020202, 0x7f7f7f7f, 0x50505050, 0x3c3c3c3c, 0x9f9f9f9f, 0xa8a8a8a8, 0x51515151, 0xa3a3a3a3, + 0x40404040, 0x8f8f8f8f, 0x92929292, 0x9d9d9d9d, 0x38383838, 0xf5f5f5f5, 0xbcbcbcbc, 0xb6b6b6b6, 0xdadadada, + 0x21212121, 0x10101010, 0xffffffff, 0xf3f3f3f3, 0xd2d2d2d2, 0xcdcdcdcd, 0x0c0c0c0c, 0x13131313, 0xecececec, + 0x5f5f5f5f, 0x97979797, 0x44444444, 0x17171717, 0xc4c4c4c4, 0xa7a7a7a7, 0x7e7e7e7e, 0x3d3d3d3d, 0x64646464, + 0x5d5d5d5d, 0x19191919, 0x73737373, 0x60606060, 0x81818181, 0x4f4f4f4f, 0xdcdcdcdc, 0x22222222, 0x2a2a2a2a, + 0x90909090, 0x88888888, 0x46464646, 0xeeeeeeee, 0xb8b8b8b8, 0x14141414, 0xdededede, 0x5e5e5e5e, 0x0b0b0b0b, + 0xdbdbdbdb, 0xe0e0e0e0, 0x32323232, 0x3a3a3a3a, 0x0a0a0a0a, 0x49494949, 0x06060606, 0x24242424, 0x5c5c5c5c, + 0xc2c2c2c2, 0xd3d3d3d3, 0xacacacac, 0x62626262, 0x91919191, 0x95959595, 0xe4e4e4e4, 0x79797979, 0xe7e7e7e7, + 0xc8c8c8c8, 0x37373737, 0x6d6d6d6d, 0x8d8d8d8d, 0xd5d5d5d5, 0x4e4e4e4e, 0xa9a9a9a9, 0x6c6c6c6c, 0x56565656, + 0xf4f4f4f4, 0xeaeaeaea, 0x65656565, 0x7a7a7a7a, 0xaeaeaeae, 0x08080808, 0xbabababa, 0x78787878, 0x25252525, + 0x2e2e2e2e, 0x1c1c1c1c, 0xa6a6a6a6, 0xb4b4b4b4, 0xc6c6c6c6, 0xe8e8e8e8, 0xdddddddd, 0x74747474, 0x1f1f1f1f, + 0x4b4b4b4b, 0xbdbdbdbd, 0x8b8b8b8b, 0x8a8a8a8a, 0x70707070, 0x3e3e3e3e, 0xb5b5b5b5, 0x66666666, 0x48484848, + 0x03030303, 0xf6f6f6f6, 0x0e0e0e0e, 0x61616161, 0x35353535, 0x57575757, 0xb9b9b9b9, 0x86868686, 0xc1c1c1c1, + 0x1d1d1d1d, 0x9e9e9e9e, 0xe1e1e1e1, 0xf8f8f8f8, 0x98989898, 0x11111111, 0x69696969, 0xd9d9d9d9, 0x8e8e8e8e, + 0x94949494, 0x9b9b9b9b, 0x1e1e1e1e, 0x87878787, 0xe9e9e9e9, 0xcececece, 0x55555555, 0x28282828, 0xdfdfdfdf, + 0x8c8c8c8c, 0xa1a1a1a1, 0x89898989, 0x0d0d0d0d, 0xbfbfbfbf, 0xe6e6e6e6, 0x42424242, 0x68686868, 0x41414141, + 0x99999999, 0x2d2d2d2d, 0x0f0f0f0f, 0xb0b0b0b0, 0x54545454, 0xbbbbbbbb, 0x16161616, }; + private static final int[] Td0 = { 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, + 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, + 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, + 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, + 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, + 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, + 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, + 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, + 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, + 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, + 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, + 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, + 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, + 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, + 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, + 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, + 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, + 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, + 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, + 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, + 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, + 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, + 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, + 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, + 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, }; + private static final int[] Td1 = { 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, + 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, + 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, + 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, + 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, + 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, + 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, + 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, + 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, + 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, + 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, + 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, + 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, + 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, + 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, + 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, + 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, + 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, + 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, + 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, + 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, + 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, + 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, + 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, + 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, }; + private static final int[] Td2 = { 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, + 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, + 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, + 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, + 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, + 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, + 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, + 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, + 0x9f065e71, 0x1051bd6e, + + 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, + 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, + 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, + 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, + 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, + 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, + 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, + 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, + 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, + 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, + 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, + 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, + 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, + 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, + 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, + 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, + 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, + 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, + 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, + 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, }; + private static final int[] Td3 = { 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, + 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, + 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, + 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, + 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, + 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, + 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, + 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, + 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, + 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, + 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, + 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, + 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, + 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, + 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, + 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, + 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, + 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, + 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, + 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, + 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, + 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, + 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, + 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, + 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, }; + private static final int[] Td4 = { 0x52525252, 0x09090909, 0x6a6a6a6a, 0xd5d5d5d5, 0x30303030, 0x36363636, + 0xa5a5a5a5, 0x38383838, 0xbfbfbfbf, 0x40404040, 0xa3a3a3a3, 0x9e9e9e9e, 0x81818181, 0xf3f3f3f3, 0xd7d7d7d7, + 0xfbfbfbfb, 0x7c7c7c7c, 0xe3e3e3e3, 0x39393939, 0x82828282, 0x9b9b9b9b, 0x2f2f2f2f, 0xffffffff, 0x87878787, + 0x34343434, 0x8e8e8e8e, 0x43434343, 0x44444444, 0xc4c4c4c4, 0xdededede, 0xe9e9e9e9, 0xcbcbcbcb, 0x54545454, + 0x7b7b7b7b, 0x94949494, 0x32323232, 0xa6a6a6a6, 0xc2c2c2c2, 0x23232323, 0x3d3d3d3d, 0xeeeeeeee, 0x4c4c4c4c, + 0x95959595, 0x0b0b0b0b, 0x42424242, 0xfafafafa, 0xc3c3c3c3, 0x4e4e4e4e, 0x08080808, 0x2e2e2e2e, 0xa1a1a1a1, + 0x66666666, 0x28282828, 0xd9d9d9d9, 0x24242424, 0xb2b2b2b2, 0x76767676, 0x5b5b5b5b, 0xa2a2a2a2, 0x49494949, + 0x6d6d6d6d, 0x8b8b8b8b, 0xd1d1d1d1, 0x25252525, 0x72727272, 0xf8f8f8f8, 0xf6f6f6f6, 0x64646464, 0x86868686, + 0x68686868, 0x98989898, 0x16161616, 0xd4d4d4d4, 0xa4a4a4a4, 0x5c5c5c5c, 0xcccccccc, 0x5d5d5d5d, 0x65656565, + 0xb6b6b6b6, 0x92929292, 0x6c6c6c6c, 0x70707070, 0x48484848, 0x50505050, 0xfdfdfdfd, 0xedededed, 0xb9b9b9b9, + 0xdadadada, 0x5e5e5e5e, 0x15151515, 0x46464646, 0x57575757, 0xa7a7a7a7, 0x8d8d8d8d, 0x9d9d9d9d, 0x84848484, + 0x90909090, 0xd8d8d8d8, 0xabababab, 0x00000000, 0x8c8c8c8c, 0xbcbcbcbc, 0xd3d3d3d3, 0x0a0a0a0a, 0xf7f7f7f7, + 0xe4e4e4e4, 0x58585858, 0x05050505, 0xb8b8b8b8, 0xb3b3b3b3, 0x45454545, 0x06060606, 0xd0d0d0d0, 0x2c2c2c2c, + 0x1e1e1e1e, 0x8f8f8f8f, 0xcacacaca, 0x3f3f3f3f, 0x0f0f0f0f, 0x02020202, 0xc1c1c1c1, 0xafafafaf, 0xbdbdbdbd, + 0x03030303, 0x01010101, 0x13131313, 0x8a8a8a8a, 0x6b6b6b6b, 0x3a3a3a3a, 0x91919191, 0x11111111, 0x41414141, + 0x4f4f4f4f, 0x67676767, 0xdcdcdcdc, 0xeaeaeaea, 0x97979797, 0xf2f2f2f2, 0xcfcfcfcf, 0xcececece, 0xf0f0f0f0, + 0xb4b4b4b4, 0xe6e6e6e6, 0x73737373, 0x96969696, 0xacacacac, 0x74747474, 0x22222222, 0xe7e7e7e7, 0xadadadad, + 0x35353535, 0x85858585, 0xe2e2e2e2, 0xf9f9f9f9, 0x37373737, 0xe8e8e8e8, 0x1c1c1c1c, 0x75757575, 0xdfdfdfdf, + 0x6e6e6e6e, 0x47474747, 0xf1f1f1f1, 0x1a1a1a1a, 0x71717171, 0x1d1d1d1d, 0x29292929, 0xc5c5c5c5, 0x89898989, + 0x6f6f6f6f, 0xb7b7b7b7, 0x62626262, 0x0e0e0e0e, 0xaaaaaaaa, 0x18181818, 0xbebebebe, 0x1b1b1b1b, 0xfcfcfcfc, + 0x56565656, 0x3e3e3e3e, 0x4b4b4b4b, 0xc6c6c6c6, 0xd2d2d2d2, 0x79797979, 0x20202020, 0x9a9a9a9a, 0xdbdbdbdb, + 0xc0c0c0c0, 0xfefefefe, 0x78787878, 0xcdcdcdcd, 0x5a5a5a5a, 0xf4f4f4f4, 0x1f1f1f1f, 0xdddddddd, 0xa8a8a8a8, + 0x33333333, 0x88888888, 0x07070707, 0xc7c7c7c7, 0x31313131, 0xb1b1b1b1, 0x12121212, 0x10101010, 0x59595959, + 0x27272727, 0x80808080, 0xecececec, 0x5f5f5f5f, 0x60606060, 0x51515151, 0x7f7f7f7f, 0xa9a9a9a9, 0x19191919, + 0xb5b5b5b5, 0x4a4a4a4a, 0x0d0d0d0d, 0x2d2d2d2d, 0xe5e5e5e5, 0x7a7a7a7a, 0x9f9f9f9f, 0x93939393, 0xc9c9c9c9, + 0x9c9c9c9c, 0xefefefef, 0xa0a0a0a0, 0xe0e0e0e0, 0x3b3b3b3b, 0x4d4d4d4d, 0xaeaeaeae, 0x2a2a2a2a, 0xf5f5f5f5, + 0xb0b0b0b0, 0xc8c8c8c8, 0xebebebeb, 0xbbbbbbbb, 0x3c3c3c3c, 0x83838383, 0x53535353, 0x99999999, 0x61616161, + 0x17171717, 0x2b2b2b2b, 0x04040404, 0x7e7e7e7e, 0xbabababa, 0x77777777, 0xd6d6d6d6, 0x26262626, 0xe1e1e1e1, + 0x69696969, 0x14141414, 0x63636363, 0x55555555, 0x21212121, 0x0c0c0c0c, 0x7d7d7d7d, }; + private static final int[] rcon = { 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, + 0x40000000, 0x80000000, 0x1B000000, + 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ + }; +} diff --git a/src/main/java/com/southernstorm/noise/crypto/SHA256MessageDigest.java b/src/main/java/com/southernstorm/noise/crypto/SHA256MessageDigest.java new file mode 100644 index 0000000..1f9d11a --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/SHA256MessageDigest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.southernstorm.noise.protocol.Destroyable; + +/** + * Fallback implementation of SHA256. + */ +public class SHA256MessageDigest extends MessageDigest implements Destroyable { + + private int[] h; + private byte[] block; + private int[] w; + private long length; + private int posn; + + /** + * Constructs a new SHA256 message digest object. + */ + public SHA256MessageDigest() { + super("SHA-256"); + h = new int[8]; + block = new byte[64]; + w = new int[64]; + engineReset(); + } + + @Override + public void destroy() { + Arrays.fill(h, (int) 0); + Arrays.fill(block, (byte) 0); + Arrays.fill(w, (int) 0); + } + + private static void writeBE32(byte[] buf, int offset, int value) { + buf[offset] = (byte) (value >> 24); + buf[offset + 1] = (byte) (value >> 16); + buf[offset + 2] = (byte) (value >> 8); + buf[offset + 3] = (byte) value; + } + + @Override + protected byte[] engineDigest() { + byte[] digest = new byte[32]; + try { + engineDigest(digest, 0, 32); + } catch (DigestException e) { + // Shouldn't happen, but just in case. + Arrays.fill(digest, (byte) 0); + } + return digest; + } + + @Override + protected int engineDigest(byte[] buf, int offset, int len) throws DigestException { + if (len < 32) + throw new DigestException("Invalid digest length for SHA256"); + if (posn <= (64 - 9)) { + block[posn] = (byte) 0x80; + Arrays.fill(block, posn + 1, 64 - 8, (byte) 0); + } else { + block[posn] = (byte) 0x80; + Arrays.fill(block, posn + 1, 64, (byte) 0); + transform(block, 0); + Arrays.fill(block, 0, 64 - 8, (byte) 0); + } + writeBE32(block, 64 - 8, (int) (length >> 32)); + writeBE32(block, 64 - 4, (int) length); + transform(block, 0); + posn = 0; + for (int index = 0; index < 8; ++index) + writeBE32(buf, offset + index * 4, h[index]); + return 32; + } + + @Override + protected int engineGetDigestLength() { + return 32; + } + + @Override + protected void engineReset() { + h[0] = 0x6A09E667; + h[1] = 0xBB67AE85; + h[2] = 0x3C6EF372; + h[3] = 0xA54FF53A; + h[4] = 0x510E527F; + h[5] = 0x9B05688C; + h[6] = 0x1F83D9AB; + h[7] = 0x5BE0CD19; + length = 0; + posn = 0; + } + + @Override + protected void engineUpdate(byte input) { + block[posn++] = input; + length += 8; + if (posn >= 64) { + transform(block, 0); + posn = 0; + } + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + while (len > 0) { + if (posn == 0 && len >= 64) { + transform(input, offset); + offset += 64; + len -= 64; + length += 64 * 8; + } else { + int temp = 64 - posn; + if (temp > len) + temp = len; + System.arraycopy(input, offset, block, posn, temp); + posn += temp; + length += temp * 8; + if (posn >= 64) { + transform(block, 0); + posn = 0; + } + offset += temp; + len -= temp; + } + } + } + + private static final int[] k = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, + 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, + 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, + 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, + 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, + 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; + + private static int rightRotate(int value, int n) { + return (value >>> n) | (value << (32 - n)); + } + + private void transform(byte[] m, int offset) { + int a, b, c, d, e, f, g, h; + int temp1, temp2; + int index; + + // Initialize working variables to the current hash value. + a = this.h[0]; + b = this.h[1]; + c = this.h[2]; + d = this.h[3]; + e = this.h[4]; + f = this.h[5]; + g = this.h[6]; + h = this.h[7]; + + // Convert the 16 input message words from big endian to host byte order. + for (index = 0; index < 16; ++index) { + w[index] = ((m[offset] & 0xFF) << 24) | ((m[offset + 1] & 0xFF) << 16) | ((m[offset + 2] & 0xFF) << 8) + | (m[offset + 3] & 0xFF); + offset += 4; + } + + // Extend the first 16 words to 64. + for (index = 16; index < 64; ++index) { + w[index] = w[index - 16] + w[index - 7] + + (rightRotate(w[index - 15], 7) ^ rightRotate(w[index - 15], 18) ^ (w[index - 15] >>> 3)) + + (rightRotate(w[index - 2], 17) ^ rightRotate(w[index - 2], 19) ^ (w[index - 2] >>> 10)); + } + + // Compression function main loop. + for (index = 0; index < 64; ++index) { + temp1 = (h) + k[index] + w[index] + (rightRotate((e), 6) ^ rightRotate((e), 11) ^ rightRotate((e), 25)) + + (((e) & (f)) ^ ((~(e)) & (g))); + temp2 = (rightRotate((a), 2) ^ rightRotate((a), 13) ^ rightRotate((a), 22)) + + (((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c))); + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + + // Add the compressed chunk to the current hash value. + this.h[0] += a; + this.h[1] += b; + this.h[2] += c; + this.h[3] += d; + this.h[4] += e; + this.h[5] += f; + this.h[6] += g; + this.h[7] += h; + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/SHA512MessageDigest.java b/src/main/java/com/southernstorm/noise/crypto/SHA512MessageDigest.java new file mode 100644 index 0000000..cf71af7 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/SHA512MessageDigest.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.southernstorm.noise.protocol.Destroyable; + +/** + * Fallback implementation of SHA512. + * + * Note: This implementation is limited to a maximum 2^56 - 1 bytes of input. + * That is, we don't bother trying to implement 128-bit length values. + */ +public class SHA512MessageDigest extends MessageDigest implements Destroyable { + + private long[] h; + private byte[] block; + private long[] w; + private long length; + private int posn; + + /** + * Constructs a new SHA512 message digest object. + */ + public SHA512MessageDigest() { + super("SHA-512"); + h = new long[8]; + block = new byte[128]; + w = new long[80]; + engineReset(); + } + + @Override + public void destroy() { + Arrays.fill(h, (long) 0); + Arrays.fill(block, (byte) 0); + Arrays.fill(w, (long) 0); + } + + private static void writeBE64(byte[] buf, int offset, long value) { + buf[offset] = (byte) (value >> 56); + buf[offset + 1] = (byte) (value >> 48); + buf[offset + 2] = (byte) (value >> 40); + buf[offset + 3] = (byte) (value >> 32); + buf[offset + 4] = (byte) (value >> 24); + buf[offset + 5] = (byte) (value >> 16); + buf[offset + 6] = (byte) (value >> 8); + buf[offset + 7] = (byte) value; + } + + @Override + protected byte[] engineDigest() { + byte[] digest = new byte[64]; + try { + engineDigest(digest, 0, 64); + } catch (DigestException e) { + // Shouldn't happen, but just in case. + Arrays.fill(digest, (byte) 0); + } + return digest; + } + + @Override + protected int engineDigest(byte[] buf, int offset, int len) throws DigestException { + if (len < 64) + throw new DigestException("Invalid digest length for SHA512"); + if (posn <= (128 - 17)) { + block[posn] = (byte) 0x80; + Arrays.fill(block, posn + 1, 128 - 8, (byte) 0); + } else { + block[posn] = (byte) 0x80; + Arrays.fill(block, posn + 1, 128, (byte) 0); + transform(block, 0); + Arrays.fill(block, 0, 128 - 8, (byte) 0); + } + writeBE64(block, 128 - 8, length); + transform(block, 0); + posn = 0; + for (int index = 0; index < 8; ++index) + writeBE64(buf, offset + index * 8, h[index]); + return 64; + } + + @Override + protected int engineGetDigestLength() { + return 64; + } + + @Override + protected void engineReset() { + h[0] = 0x6a09e667f3bcc908L; + h[1] = 0xbb67ae8584caa73bL; + h[2] = 0x3c6ef372fe94f82bL; + h[3] = 0xa54ff53a5f1d36f1L; + h[4] = 0x510e527fade682d1L; + h[5] = 0x9b05688c2b3e6c1fL; + h[6] = 0x1f83d9abfb41bd6bL; + h[7] = 0x5be0cd19137e2179L; + length = 0; + posn = 0; + } + + @Override + protected void engineUpdate(byte input) { + block[posn++] = input; + length += 8; + if (posn >= 128) { + transform(block, 0); + posn = 0; + } + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + while (len > 0) { + if (posn == 0 && len >= 128) { + transform(input, offset); + offset += 128; + len -= 128; + length += 128 * 8; + } else { + int temp = 128 - posn; + if (temp > len) + temp = len; + System.arraycopy(input, offset, block, posn, temp); + posn += temp; + length += temp * 8; + if (posn >= 128) { + transform(block, 0); + posn = 0; + } + offset += temp; + len -= temp; + } + } + } + + private 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 }; + + private static long rightRotate(long value, int n) { + return (value >>> n) | (value << (64 - n)); + } + + private void transform(byte[] m, int offset) { + long a, b, c, d, e, f, g, h; + long temp1, temp2; + int index; + + // Initialize working variables to the current hash value. + a = this.h[0]; + b = this.h[1]; + c = this.h[2]; + d = this.h[3]; + e = this.h[4]; + f = this.h[5]; + g = this.h[6]; + h = this.h[7]; + + // Convert the 16 input message words from big endian to host byte order. + for (index = 0; index < 16; ++index) { + w[index] = ((m[offset] & 0xFFL) << 56) | ((m[offset + 1] & 0xFFL) << 48) | ((m[offset + 2] & 0xFFL) << 40) + | ((m[offset + 3] & 0xFFL) << 32) | ((m[offset + 4] & 0xFFL) << 24) + | ((m[offset + 5] & 0xFFL) << 16) | ((m[offset + 6] & 0xFFL) << 8) | (m[offset + 7] & 0xFFL); + offset += 8; + } + + // Extend the first 16 words to 80. + for (index = 16; index < 80; ++index) { + w[index] = w[index - 16] + w[index - 7] + + (rightRotate(w[index - 15], 1) ^ rightRotate(w[index - 15], 8) ^ (w[index - 15] >>> 7)) + + (rightRotate(w[index - 2], 19) ^ rightRotate(w[index - 2], 61) ^ (w[index - 2] >>> 6)); + } + + // Compression function main loop. + for (index = 0; index < 80; ++index) { + temp1 = (h) + k[index] + w[index] + (rightRotate((e), 14) ^ rightRotate((e), 18) ^ rightRotate((e), 41)) + + (((e) & (f)) ^ ((~(e)) & (g))); + temp2 = (rightRotate((a), 28) ^ rightRotate((a), 34) ^ rightRotate((a), 39)) + + (((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c))); + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + + // Add the compressed chunk to the current hash value. + this.h[0] += a; + this.h[1] += b; + this.h[2] += c; + this.h[3] += d; + this.h[4] += e; + this.h[5] += f; + this.h[6] += g; + this.h[7] += h; + } +} diff --git a/src/main/java/com/southernstorm/noise/crypto/package-info.java b/src/main/java/com/southernstorm/noise/crypto/package-info.java new file mode 100644 index 0000000..8552009 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/crypto/package-info.java @@ -0,0 +1,12 @@ + +/** + * Fallback implementations of cryptographic primitives. + * + * This package provides plain Java implementations of the + * cryptographic primitives that Noise requires which do not + * normally come with standard JDK's. + * + * Applications that use Noise won't normally use these classes + * directly. + */ +package com.southernstorm.noise.crypto; diff --git a/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java b/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java new file mode 100644 index 0000000..c28a302 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +import com.southernstorm.noise.crypto.GHASH; +import com.southernstorm.noise.crypto.RijndaelAES; + +/** + * Fallback implementation of "AESGCM" on platforms where + * the JCA/JCE does not have a suitable GCM or CTR provider. + */ +class AESGCMFallbackCipherState implements CipherState { + + private RijndaelAES aes; + private long n; + private byte[] iv; + private byte[] enciv; + private byte[] hashKey; + private GHASH ghash; + private boolean haskey; + + /** + * Constructs a new cipher state for the "AESGCM" algorithm. + */ + public AESGCMFallbackCipherState() { + aes = new RijndaelAES(); + n = 0; + iv = new byte[16]; + enciv = new byte[16]; + hashKey = new byte[16]; + ghash = new GHASH(); + haskey = false; + } + + @Override + public void destroy() { + aes.destroy(); + ghash.destroy(); + Noise.destroy(hashKey); + Noise.destroy(iv); + Noise.destroy(enciv); + } + + @Override + public String getCipherName() { + return "AESGCM"; + } + + @Override + public int getKeyLength() { + return 32; + } + + @Override + public int getMACLength() { + return haskey ? 16 : 0; + } + + @Override + public void initializeKey(byte[] key, int offset) { + // Set up the AES key. + aes.setupEnc(key, offset, 256); + haskey = true; + + // Generate the hashing key by encrypting a block of zeroes. + Arrays.fill(hashKey, (byte) 0); + aes.encrypt(hashKey, 0, hashKey, 0); + ghash.reset(hashKey, 0); + + // Reset the nonce. + n = 0; + } + + @Override + public boolean hasKey() { + return haskey; + } + + /** + * Set up to encrypt or decrypt the next packet. + * + * @param ad The associated data for the packet. + */ + private void setup(byte[] ad) { + // Check for nonce wrap-around. + if (n == -1L) + throw new IllegalStateException("Nonce has wrapped around"); + + // Format the counter/IV block. + iv[0] = 0; + iv[1] = 0; + iv[2] = 0; + iv[3] = 0; + iv[4] = (byte) (n >> 56); + iv[5] = (byte) (n >> 48); + iv[6] = (byte) (n >> 40); + iv[7] = (byte) (n >> 32); + iv[8] = (byte) (n >> 24); + iv[9] = (byte) (n >> 16); + iv[10] = (byte) (n >> 8); + iv[11] = (byte) n; + iv[12] = 0; + iv[13] = 0; + iv[14] = 0; + iv[15] = 1; + ++n; + + // Encrypt a block of zeroes to generate the hash key to XOR + // the GHASH tag with at the end of the encrypt/decrypt operation. + Arrays.fill(hashKey, (byte) 0); + aes.encrypt(iv, 0, hashKey, 0); + + // Initialize the GHASH with the associated data value. + ghash.reset(); + if (ad != null) { + ghash.update(ad, 0, ad.length); + ghash.pad(); + } + } + + /** + * Encrypts a block in CTR mode. + * + * @param plaintext The plaintext to encrypt. + * @param plaintextOffset Offset of the first plaintext byte. + * @param ciphertext The resulting ciphertext. + * @param ciphertextOffset Offset of the first ciphertext byte. + * @param length The number of bytes to encrypt. + * + * This function can also be used to decrypt. + */ + private void encryptCTR(byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, + int length) { + while (length > 0) { + // Increment the IV and encrypt it to get the next keystream block. + if (++(iv[15]) == 0) + if (++(iv[14]) == 0) + if (++(iv[13]) == 0) + ++(iv[12]); + aes.encrypt(iv, 0, enciv, 0); + + // XOR the keystream block with the plaintext to create the ciphertext. + int temp = length; + if (temp > 16) + temp = 16; + for (int index = 0; index < temp; ++index) + ciphertext[ciphertextOffset + index] = (byte) (plaintext[plaintextOffset + index] ^ enciv[index]); + + // Advance to the next block. + plaintextOffset += temp; + ciphertextOffset += temp; + length -= temp; + } + } + + @Override + public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, + int length) throws ShortBufferException { + int space; + if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length) + throw new IllegalArgumentException(); + if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > plaintext.length + || (plaintext.length - plaintextOffset) < length) + throw new IllegalArgumentException(); + space = ciphertext.length - ciphertextOffset; + if (!haskey) { + // The key is not set yet - return the plaintext as-is. + if (length > space) + throw new ShortBufferException(); + if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) + System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); + return length; + } + if (space < 16 || length > (space - 16)) + throw new ShortBufferException(); + setup(ad); + encryptCTR(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); + ghash.update(ciphertext, ciphertextOffset, length); + ghash.pad(ad != null ? ad.length : 0, length); + ghash.finish(ciphertext, ciphertextOffset + length, 16); + for (int index = 0; index < 16; ++index) + ciphertext[ciphertextOffset + length + index] ^= hashKey[index]; + return length + 16; + } + + @Override + public int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException { + int space; + if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length) + throw new IllegalArgumentException(); + else + space = ciphertext.length - ciphertextOffset; + if (length > space) + throw new ShortBufferException(); + if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > ciphertext.length + || (ciphertext.length - ciphertextOffset) < length) + throw new IllegalArgumentException(); + space = plaintext.length - plaintextOffset; + if (!haskey) { + // The key is not set yet - return the ciphertext as-is. + if (length > space) + throw new ShortBufferException(); + if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) + System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + return length; + } + if (length < 16) + Noise.throwBadTagException(); + int dataLen = length - 16; + if (dataLen > space) + throw new ShortBufferException(); + setup(ad); + ghash.update(ciphertext, ciphertextOffset, dataLen); + ghash.pad(ad != null ? ad.length : 0, dataLen); + ghash.finish(enciv, 0, 16); + int temp = 0; + for (int index = 0; index < 16; ++index) + temp |= (hashKey[index] ^ enciv[index] ^ ciphertext[ciphertextOffset + dataLen + index]); + if ((temp & 0xFF) != 0) + Noise.throwBadTagException(); + encryptCTR(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen); + return dataLen; + } + + @Override + public CipherState fork(byte[] key, int offset) { + CipherState cipher; + cipher = new AESGCMFallbackCipherState(); + cipher.initializeKey(key, offset); + return cipher; + } + + @Override + public void setNonce(long nonce) { + n = nonce; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java b/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java new file mode 100644 index 0000000..ae6678b --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import com.southernstorm.noise.crypto.GHASH; + +/** + * Emulates the "AESGCM" cipher for Noise using the "AES/CTR/NoPadding" + * transformation from JCA/JCE. + * + * This class is used on platforms that don't have "AES/GCM/NoPadding", + * but which do have the older "AES/CTR/NoPadding". + */ +class AESGCMOnCtrCipherState implements CipherState { + + private Cipher cipher; + private SecretKeySpec keySpec; + private long n; + private byte[] iv; + private byte[] hashKey; + private GHASH ghash; + + /** + * Constructs a new cipher state for the "AESGCM" algorithm. + * + * @throws NoSuchAlgorithmException The system does not have a + * provider for this algorithm. + */ + public AESGCMOnCtrCipherState() throws NoSuchAlgorithmException { + try { + cipher = Cipher.getInstance("AES/CTR/NoPadding"); + } catch (NoSuchPaddingException e) { + // AES/CTR is available, but not the unpadded version? Huh? + throw new NoSuchAlgorithmException("AES/CTR/NoPadding not available", e); + } + keySpec = null; + n = 0; + iv = new byte[16]; + hashKey = new byte[16]; + ghash = new GHASH(); + + // Try to set a 256-bit key on the cipher. Some JCE's are + // configured to disallow 256-bit AES if an extra policy + // file has not been installed. + try { + SecretKeySpec spec = new SecretKeySpec(new byte[32], "AES"); + IvParameterSpec params = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, spec, params); + } catch (InvalidKeyException e) { + throw new NoSuchAlgorithmException("AES/CTR/NoPadding does not support 256-bit keys", e); + } catch (InvalidAlgorithmParameterException e) { + throw new NoSuchAlgorithmException("AES/CTR/NoPadding does not support 256-bit keys", e); + } + } + + @Override + public void destroy() { + // There doesn't seem to be a standard API to clean out a Cipher. + // So we instead set the key and IV to all-zeroes to hopefully + // destroy the sensitive data in the cipher instance. + ghash.destroy(); + Noise.destroy(hashKey); + Noise.destroy(iv); + keySpec = new SecretKeySpec(new byte[32], "AES"); + IvParameterSpec params = new IvParameterSpec(iv); + try { + cipher.init(Cipher.ENCRYPT_MODE, keySpec, params); + } catch (InvalidKeyException e) { + // Shouldn't happen. + } catch (InvalidAlgorithmParameterException e) { + // Shouldn't happen. + } + } + + @Override + public String getCipherName() { + return "AESGCM"; + } + + @Override + public int getKeyLength() { + return 32; + } + + @Override + public int getMACLength() { + return keySpec != null ? 16 : 0; + } + + @Override + public void initializeKey(byte[] key, int offset) { + // Set the encryption key. + keySpec = new SecretKeySpec(key, offset, 32, "AES"); + + // Generate the hashing key by encrypting a block of zeroes. + Arrays.fill(iv, (byte) 0); + Arrays.fill(hashKey, (byte) 0); + try { + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv)); + } catch (InvalidKeyException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (InvalidAlgorithmParameterException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } + try { + int result = cipher.update(hashKey, 0, 16, hashKey, 0); + cipher.doFinal(hashKey, result); + } catch (ShortBufferException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (IllegalBlockSizeException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (BadPaddingException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } + ghash.reset(hashKey, 0); + + // Reset the nonce. + n = 0; + } + + @Override + public boolean hasKey() { + return keySpec != null; + } + + /** + * Set up to encrypt or decrypt the next packet. + * + * @param ad The associated data for the packet. + */ + private void setup(byte[] ad) throws InvalidKeyException, InvalidAlgorithmParameterException { + // Check for nonce wrap-around. + if (n == -1L) { + throw new IllegalStateException("Nonce has wrapped around"); + } + + // Format the counter/IV block for AES/CTR/NoPadding. + iv[0] = 0; + iv[1] = 0; + iv[2] = 0; + iv[3] = 0; + iv[4] = (byte) (n >> 56); + iv[5] = (byte) (n >> 48); + iv[6] = (byte) (n >> 40); + iv[7] = (byte) (n >> 32); + iv[8] = (byte) (n >> 24); + iv[9] = (byte) (n >> 16); + iv[10] = (byte) (n >> 8); + iv[11] = (byte) n; + iv[12] = 0; + iv[13] = 0; + iv[14] = 0; + iv[15] = 1; + ++n; + + // Initialize the CTR mode cipher with the key and IV. + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv)); + + // Encrypt a block of zeroes to generate the hash key to XOR + // the GHASH tag with at the end of the encrypt/decrypt operation. + Arrays.fill(hashKey, (byte) 0); + try { + cipher.update(hashKey, 0, 16, hashKey, 0); + } catch (ShortBufferException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } + + // Initialize the GHASH with the associated data value. + ghash.reset(); + if (ad != null) { + ghash.update(ad, 0, ad.length); + ghash.pad(); + } + } + + @Override + public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, + int length) throws ShortBufferException { + int space; + if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length) { + throw new IllegalArgumentException(); + } + if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > plaintext.length + || (plaintext.length - plaintextOffset) < length) { + throw new IllegalArgumentException(); + } + space = ciphertext.length - ciphertextOffset; + if (keySpec == null) { + // The key is not set yet - return the plaintext as-is. + if (length > space) { + throw new ShortBufferException(); + } + if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) { + System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); + } + return length; + } + if (space < 16 || length > (space - 16)) { + throw new ShortBufferException(); + } + try { + setup(ad); + int result = cipher.update(plaintext, plaintextOffset, length, ciphertext, ciphertextOffset); + cipher.doFinal(ciphertext, ciphertextOffset + result); + } catch (InvalidKeyException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (InvalidAlgorithmParameterException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (IllegalBlockSizeException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (BadPaddingException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } + ghash.update(ciphertext, ciphertextOffset, length); + ghash.pad(ad != null ? ad.length : 0, length); + ghash.finish(ciphertext, ciphertextOffset + length, 16); + for (int index = 0; index < 16; ++index) { + ciphertext[ciphertextOffset + length + index] ^= hashKey[index]; + } + return length + 16; + } + + @Override + public int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException { + int space; + if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length) { + throw new IllegalArgumentException(); + } else { + space = ciphertext.length - ciphertextOffset; + } + if (length > space) { + throw new ShortBufferException(); + } + if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > ciphertext.length + || (ciphertext.length - ciphertextOffset) < length) { + throw new IllegalArgumentException(); + } + space = plaintext.length - plaintextOffset; + if (keySpec == null) { + // The key is not set yet - return the ciphertext as-is. + if (length > space) { + throw new ShortBufferException(); + } + if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) { + System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + } + return length; + } + if (length < 16) { + Noise.throwBadTagException(); + } + int dataLen = length - 16; + if (dataLen > space) { + throw new ShortBufferException(); + } + try { + setup(ad); + } catch (InvalidKeyException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (InvalidAlgorithmParameterException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } + ghash.update(ciphertext, ciphertextOffset, dataLen); + ghash.pad(ad != null ? ad.length : 0, dataLen); + ghash.finish(iv, 0, 16); + int temp = 0; + for (int index = 0; index < 16; ++index) { + temp |= (hashKey[index] ^ iv[index] ^ ciphertext[ciphertextOffset + dataLen + index]); + } + if ((temp & 0xFF) != 0) { + Noise.throwBadTagException(); + } + try { + int result = cipher.update(ciphertext, ciphertextOffset, dataLen, plaintext, plaintextOffset); + cipher.doFinal(plaintext, plaintextOffset + result); + } catch (IllegalBlockSizeException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } catch (BadPaddingException e) { + // Shouldn't happen. + throw new IllegalStateException(e); + } + return dataLen; + } + + @Override + public CipherState fork(byte[] key, int offset) { + CipherState cipher; + try { + cipher = new AESGCMOnCtrCipherState(); + } catch (NoSuchAlgorithmException e) { + // Shouldn't happen. + return null; + } + cipher.initializeKey(key, offset); + return cipher; + } + + @Override + public void setNonce(long nonce) { + n = nonce; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java b/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java new file mode 100644 index 0000000..7faaf7f --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +import com.southernstorm.noise.crypto.ChaChaCore; +import com.southernstorm.noise.crypto.Poly1305; + +/** + * Implements the ChaChaPoly cipher for Noise. + */ +class ChaChaPolyCipherState implements CipherState { + + private Poly1305 poly; + private int[] input; + private int[] output; + private byte[] polyKey; + long n; + private boolean haskey; + + /** + * Constructs a new cipher state for the "ChaChaPoly" algorithm. + */ + public ChaChaPolyCipherState() { + poly = new Poly1305(); + input = new int[16]; + output = new int[16]; + polyKey = new byte[32]; + n = 0; + haskey = false; + } + + @Override + public void destroy() { + poly.destroy(); + Arrays.fill(input, 0); + Arrays.fill(output, 0); + Noise.destroy(polyKey); + } + + @Override + public String getCipherName() { + return "ChaChaPoly"; + } + + @Override + public int getKeyLength() { + return 32; + } + + @Override + public int getMACLength() { + return haskey ? 16 : 0; + } + + @Override + public void initializeKey(byte[] key, int offset) { + ChaChaCore.initKey256(input, key, offset); + n = 0; + haskey = true; + } + + @Override + public boolean hasKey() { + return haskey; + } + + /** + * XOR's the output of ChaCha20 with a byte buffer. + * + * @param input The input byte buffer. + * @param inputOffset The offset of the first input byte. + * @param output The output byte buffer (can be the same as the input). + * @param outputOffset The offset of the first output byte. + * @param length The number of bytes to XOR between 1 and 64. + * @param block The ChaCha20 output block. + */ + private static void xorBlock(byte[] input, int inputOffset, byte[] output, int outputOffset, int length, + int[] block) { + int posn = 0; + int value; + while (length >= 4) { + value = block[posn++]; + output[outputOffset] = (byte) (input[inputOffset] ^ value); + output[outputOffset + 1] = (byte) (input[inputOffset + 1] ^ (value >> 8)); + output[outputOffset + 2] = (byte) (input[inputOffset + 2] ^ (value >> 16)); + output[outputOffset + 3] = (byte) (input[inputOffset + 3] ^ (value >> 24)); + inputOffset += 4; + outputOffset += 4; + length -= 4; + } + if (length == 3) { + value = block[posn]; + output[outputOffset] = (byte) (input[inputOffset] ^ value); + output[outputOffset + 1] = (byte) (input[inputOffset + 1] ^ (value >> 8)); + output[outputOffset + 2] = (byte) (input[inputOffset + 2] ^ (value >> 16)); + } else if (length == 2) { + value = block[posn]; + output[outputOffset] = (byte) (input[inputOffset] ^ value); + output[outputOffset + 1] = (byte) (input[inputOffset + 1] ^ (value >> 8)); + } else if (length == 1) { + value = block[posn]; + output[outputOffset] = (byte) (input[inputOffset] ^ value); + } + } + + /** + * Set up to encrypt or decrypt the next packet. + * + * @param ad The associated data for the packet. + */ + private void setup(byte[] ad) { + if (n == -1L) + throw new IllegalStateException("Nonce has wrapped around"); + ChaChaCore.initIV(input, n++); + ChaChaCore.hash(output, input); + Arrays.fill(polyKey, (byte) 0); + xorBlock(polyKey, 0, polyKey, 0, 32, output); + poly.reset(polyKey, 0); + if (ad != null) { + poly.update(ad, 0, ad.length); + poly.pad(); + } + if (++(input[12]) == 0) + ++(input[13]); + } + + /** + * Puts a 64-bit integer into a buffer in little-endian order. + * + * @param output The output buffer. + * @param offset The offset into the output buffer. + * @param value The 64-bit integer value. + */ + private static void putLittleEndian64(byte[] output, int offset, long value) { + output[offset] = (byte) value; + output[offset + 1] = (byte) (value >> 8); + output[offset + 2] = (byte) (value >> 16); + output[offset + 3] = (byte) (value >> 24); + output[offset + 4] = (byte) (value >> 32); + output[offset + 5] = (byte) (value >> 40); + output[offset + 6] = (byte) (value >> 48); + output[offset + 7] = (byte) (value >> 56); + } + + /** + * Finishes up the authentication tag for a packet. + * + * @param ad The associated data. + * @param length The length of the plaintext data. + */ + private void finish(byte[] ad, int length) { + poly.pad(); + putLittleEndian64(polyKey, 0, ad != null ? ad.length : 0); + putLittleEndian64(polyKey, 8, length); + poly.update(polyKey, 0, 16); + poly.finish(polyKey, 0); + } + + /** + * Encrypts or decrypts a buffer of bytes for the active packet. + * + * @param plaintext The plaintext data to be encrypted. + * @param plaintextOffset The offset to the first plaintext byte. + * @param ciphertext The ciphertext data that results from encryption. + * @param ciphertextOffset The offset to the first ciphertext byte. + * @param length The number of bytes to encrypt. + */ + private void encrypt(byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length) { + while (length > 0) { + int tempLen = 64; + if (tempLen > length) + tempLen = length; + ChaChaCore.hash(output, input); + xorBlock(plaintext, plaintextOffset, ciphertext, ciphertextOffset, tempLen, output); + if (++(input[12]) == 0) + ++(input[13]); + plaintextOffset += tempLen; + ciphertextOffset += tempLen; + length -= tempLen; + } + } + + @Override + public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, + int length) throws ShortBufferException { + int space; + if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length) + throw new IllegalArgumentException(); + if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > plaintext.length + || (plaintext.length - plaintextOffset) < length) + throw new IllegalArgumentException(); + space = ciphertext.length - ciphertextOffset; + if (!haskey) { + // The key is not set yet - return the plaintext as-is. + if (length > space) + throw new ShortBufferException(); + if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) + System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); + return length; + } + if (space < 16 || length > (space - 16)) + throw new ShortBufferException(); + setup(ad); + encrypt(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); + poly.update(ciphertext, ciphertextOffset, length); + finish(ad, length); + System.arraycopy(polyKey, 0, ciphertext, ciphertextOffset + length, 16); + return length + 16; + } + + @Override + public int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException { + int space; + if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length) + throw new IllegalArgumentException(); + else + space = ciphertext.length - ciphertextOffset; + if (length > space) + throw new ShortBufferException(); + if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > ciphertext.length + || (ciphertext.length - ciphertextOffset) < length) + throw new IllegalArgumentException(); + space = plaintext.length - plaintextOffset; + if (!haskey) { + // The key is not set yet - return the ciphertext as-is. + if (length > space) + throw new ShortBufferException(); + if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) + System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + return length; + } + if (length < 16) + Noise.throwBadTagException(); + int dataLen = length - 16; + if (dataLen > space) + throw new ShortBufferException(); + setup(ad); + poly.update(ciphertext, ciphertextOffset, dataLen); + finish(ad, dataLen); + int temp = 0; + for (int index = 0; index < 16; ++index) + temp |= (polyKey[index] ^ ciphertext[ciphertextOffset + dataLen + index]); + if ((temp & 0xFF) != 0) + Noise.throwBadTagException(); + encrypt(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen); + return dataLen; + } + + @Override + public CipherState fork(byte[] key, int offset) { + CipherState cipher = new ChaChaPolyCipherState(); + cipher.initializeKey(key, offset); + return cipher; + } + + @Override + public void setNonce(long nonce) { + n = nonce; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/CipherState.java b/src/main/java/com/southernstorm/noise/protocol/CipherState.java new file mode 100644 index 0000000..e0e8c0a --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/CipherState.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +/** + * Interface to an authenticated cipher for use in the Noise protocol. + * + * CipherState objects are used to encrypt or decrypt data during a + * session. Once the handshake has completed, HandshakeState.split() + * will create two CipherState objects for encrypting packets sent to + * the other party, and decrypting packets received from the other party. + */ +public interface CipherState extends Destroyable { + + /** + * Gets the Noise protocol name for this cipher. + * + * @return The cipher name. + */ + String getCipherName(); + + /** + * Gets the length of the key values for this cipher. + * + * @return The length of the key in bytes; usually 32. + */ + int getKeyLength(); + + /** + * Gets the length of the MAC values for this cipher. + * + * @return The length of MAC values in bytes, or zero if the + * key has not yet been initialized. + */ + int getMACLength(); + + /** + * Initializes the key on this cipher object. + * + * @param key Points to a buffer that contains the key. + * @param offset The offset of the key in the key buffer. + * + * The key buffer must contain at least getKeyLength() bytes + * starting at offset. + * + * @see #hasKey() + */ + void initializeKey(byte[] key, int offset); + + /** + * Determine if this cipher object has been configured with a key. + * + * @return true if this cipher object has a key; false if the + * key has not yet been set with initializeKey(). + * + * @see #initializeKey(byte[], int) + */ + boolean hasKey(); + + /** + * Encrypts a plaintext buffer using the cipher and a block of associated data. + * + * @param ad The associated data, or null if there is none. + * @param plaintext The buffer containing the plaintext to encrypt. + * @param plaintextOffset The offset within the plaintext buffer of the + * first byte or plaintext data. + * @param ciphertext The buffer to place the ciphertext in. This can + * be the same as the plaintext buffer. + * @param ciphertextOffset The first offset within the ciphertext buffer + * to place the ciphertext and the MAC tag. + * @param length The length of the plaintext. + * @return The length of the ciphertext plus the MAC tag, or -1 if the + * ciphertext buffer is not large enough to hold the result. + * + * @throws ShortBufferException The ciphertext buffer does not have + * enough space to hold the ciphertext plus MAC. + * + * @throws IllegalStateException The nonce has wrapped around. + * + * @throws IllegalArgumentException One of the parameters is out of range. + * + * The plaintext and ciphertext buffers can be the same for in-place + * encryption. In that case, plaintextOffset must be identical to + * ciphertextOffset. + * + * There must be enough space in the ciphertext buffer to accomodate + * length + getMACLength() bytes of data starting at ciphertextOffset. + */ + int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, + int length) throws ShortBufferException; + + /** + * Decrypts a ciphertext buffer using the cipher and a block of associated data. + * + * @param ad The associated data, or null if there is none. + * @param ciphertext The buffer containing the ciphertext to decrypt. + * @param ciphertextOffset The offset within the ciphertext buffer of + * the first byte of ciphertext data. + * @param plaintext The buffer to place the plaintext in. This can be + * the same as the ciphertext buffer. + * @param plaintextOffset The first offset within the plaintext buffer + * to place the plaintext. + * @param length The length of the incoming ciphertext plus the MAC tag. + * @return The length of the plaintext with the MAC tag stripped off. + * + * @throws ShortBufferException The plaintext buffer does not have + * enough space to store the decrypted data. + * + * @throws BadPaddingException The MAC value failed to verify. + * + * @throws IllegalStateException The nonce has wrapped around. + * + * @throws IllegalArgumentException One of the parameters is out of range. + * + * The plaintext and ciphertext buffers can be the same for in-place + * decryption. In that case, ciphertextOffset must be identical to + * plaintextOffset. + */ + int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException; + + /** + * Creates a new instance of this cipher and initializes it with a key. + * + * @param key The buffer containing the key. + * @param offset The offset into the key buffer of the first key byte. + * @return A new CipherState of the same class as this one. + */ + CipherState fork(byte[] key, int offset); + + /** + * Sets the nonce value. + * + * @param nonce The new nonce value, which must be greater than or equal + * to the current value. + * + * This function is intended for testing purposes only. If the nonce + * value goes backwards then security may be compromised. + */ + void setNonce(long nonce); +} diff --git a/src/main/java/com/southernstorm/noise/protocol/CipherStatePair.java b/src/main/java/com/southernstorm/noise/protocol/CipherStatePair.java new file mode 100644 index 0000000..fcc523d --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/CipherStatePair.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +/** + * Class that contains a pair of CipherState objects. + * + * CipherState pairs typically arise when HandshakeState.split() is called. + */ +public final class CipherStatePair implements Destroyable { + + private CipherState send; + private CipherState recv; + + /** + * Constructs a pair of CipherState objects. + * + * @param sender The CipherState to use to send packets to the remote party. + * @param receiver The CipherState to use to receive packets from the remote party. + */ + public CipherStatePair(CipherState sender, CipherState receiver) { + send = sender; + recv = receiver; + } + + /** + * Gets the CipherState to use to send packets to the remote party. + * + * @return The sending CipherState. + */ + public CipherState getSender() { + return send; + } + + /** + * Gets the CipherState to use to receive packets from the remote party. + * + * @return The receiving CipherState. + */ + public CipherState getReceiver() { + return recv; + } + + /** + * Destroys the receiving CipherState and retains only the sending CipherState. + * + * This function is intended for use with one-way handshake patterns. + */ + public void senderOnly() { + if (recv != null) { + recv.destroy(); + recv = null; + } + } + + /** + * Destroys the sending CipherState and retains only the receiving CipherState. + * + * This function is intended for use with one-way handshake patterns. + */ + public void receiverOnly() { + if (send != null) { + send.destroy(); + send = null; + } + } + + /** + * Swaps the sender and receiver. + */ + public void swap() { + CipherState temp = send; + send = recv; + recv = temp; + } + + @Override + public void destroy() { + senderOnly(); + receiverOnly(); + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/Curve25519DHState.java b/src/main/java/com/southernstorm/noise/protocol/Curve25519DHState.java new file mode 100644 index 0000000..b70a8e9 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/Curve25519DHState.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.util.Arrays; + +import com.southernstorm.noise.crypto.Curve25519; + +/** + * Implementation of the Curve25519 algorithm for the Noise protocol. + */ +class Curve25519DHState implements DHState { + + private byte[] publicKey; + private byte[] privateKey; + private int mode; + + /** + * Constructs a new Diffie-Hellman object for Curve25519. + */ + public Curve25519DHState() { + publicKey = new byte[32]; + privateKey = new byte[32]; + mode = 0; + } + + @Override + public void destroy() { + clearKey(); + } + + @Override + public String getDHName() { + return "25519"; + } + + @Override + public int getPublicKeyLength() { + return 32; + } + + @Override + public int getPrivateKeyLength() { + return 32; + } + + @Override + public int getSharedKeyLength() { + return 32; + } + + @Override + public void generateKeyPair() { + Noise.random(privateKey); + Curve25519.eval(publicKey, 0, privateKey, null); + mode = 0x03; + } + + @Override + public void getPublicKey(byte[] key, int offset) { + System.arraycopy(publicKey, 0, key, offset, 32); + } + + @Override + public void setPublicKey(byte[] key, int offset) { + System.arraycopy(key, offset, publicKey, 0, 32); + Arrays.fill(privateKey, (byte) 0); + mode = 0x01; + } + + @Override + public void getPrivateKey(byte[] key, int offset) { + System.arraycopy(privateKey, 0, key, offset, 32); + } + + @Override + public void setPrivateKey(byte[] key, int offset) { + System.arraycopy(key, offset, privateKey, 0, 32); + Curve25519.eval(publicKey, 0, privateKey, null); + mode = 0x03; + } + + @Override + public void setToNullPublicKey() { + Arrays.fill(publicKey, (byte) 0); + Arrays.fill(privateKey, (byte) 0); + mode = 0x01; + } + + @Override + public void clearKey() { + Noise.destroy(publicKey); + Noise.destroy(privateKey); + mode = 0; + } + + @Override + public boolean hasPublicKey() { + return (mode & 0x01) != 0; + } + + @Override + public boolean hasPrivateKey() { + return (mode & 0x02) != 0; + } + + @Override + public boolean isNullPublicKey() { + if ((mode & 0x01) == 0) + return false; + int temp = 0; + for (int index = 0; index < 32; ++index) + temp |= publicKey[index]; + return temp == 0; + } + + @Override + public void calculate(byte[] sharedKey, int offset, DHState publicDH) { + if (!(publicDH instanceof Curve25519DHState)) + throw new IllegalArgumentException("Incompatible DH algorithms"); + Curve25519.eval(sharedKey, offset, privateKey, ((Curve25519DHState) publicDH).publicKey); + } + + @Override + public void copyFrom(DHState other) { + if (!(other instanceof Curve25519DHState)) + throw new IllegalStateException("Mismatched DH key objects"); + if (other == this) + return; + Curve25519DHState dh = (Curve25519DHState) other; + System.arraycopy(dh.privateKey, 0, privateKey, 0, 32); + System.arraycopy(dh.publicKey, 0, publicKey, 0, 32); + mode = dh.mode; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/Curve448DHState.java b/src/main/java/com/southernstorm/noise/protocol/Curve448DHState.java new file mode 100644 index 0000000..a6d9a17 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/Curve448DHState.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.util.Arrays; + +import com.southernstorm.noise.crypto.Curve448; + +/** + * Implementation of the Curve448 algorithm for the Noise protocol. + */ +class Curve448DHState implements DHState { + + private byte[] publicKey; + private byte[] privateKey; + private int mode; + + /** + * Constructs a new Diffie-Hellman object for Curve448. + */ + public Curve448DHState() { + publicKey = new byte[56]; + privateKey = new byte[56]; + mode = 0; + } + + @Override + public void destroy() { + clearKey(); + } + + @Override + public String getDHName() { + return "448"; + } + + @Override + public int getPublicKeyLength() { + return 56; + } + + @Override + public int getPrivateKeyLength() { + return 56; + } + + @Override + public int getSharedKeyLength() { + return 56; + } + + @Override + public void generateKeyPair() { + Noise.random(privateKey); + Curve448.eval(publicKey, 0, privateKey, null); + mode = 0x03; + } + + @Override + public void getPublicKey(byte[] key, int offset) { + System.arraycopy(publicKey, 0, key, offset, 56); + } + + @Override + public void setPublicKey(byte[] key, int offset) { + System.arraycopy(key, offset, publicKey, 0, 56); + Arrays.fill(privateKey, (byte) 0); + mode = 0x01; + } + + @Override + public void getPrivateKey(byte[] key, int offset) { + System.arraycopy(privateKey, 0, key, offset, 56); + } + + @Override + public void setPrivateKey(byte[] key, int offset) { + System.arraycopy(key, offset, privateKey, 0, 56); + Curve448.eval(publicKey, 0, privateKey, null); + mode = 0x03; + } + + @Override + public void setToNullPublicKey() { + Arrays.fill(publicKey, (byte) 0); + Arrays.fill(privateKey, (byte) 0); + mode = 0x01; + } + + @Override + public void clearKey() { + Noise.destroy(publicKey); + Noise.destroy(privateKey); + mode = 0; + } + + @Override + public boolean hasPublicKey() { + return (mode & 0x01) != 0; + } + + @Override + public boolean hasPrivateKey() { + return (mode & 0x02) != 0; + } + + @Override + public boolean isNullPublicKey() { + if ((mode & 0x01) == 0) + return false; + int temp = 0; + for (int index = 0; index < 56; ++index) + temp |= publicKey[index]; + return temp == 0; + } + + @Override + public void calculate(byte[] sharedKey, int offset, DHState publicDH) { + if (!(publicDH instanceof Curve448DHState)) + throw new IllegalArgumentException("Incompatible DH algorithms"); + Curve448.eval(sharedKey, offset, privateKey, ((Curve448DHState) publicDH).publicKey); + } + + @Override + public void copyFrom(DHState other) { + if (!(other instanceof Curve448DHState)) + throw new IllegalStateException("Mismatched DH key objects"); + if (other == this) + return; + Curve448DHState dh = (Curve448DHState) other; + System.arraycopy(dh.privateKey, 0, privateKey, 0, 56); + System.arraycopy(dh.publicKey, 0, publicKey, 0, 56); + mode = dh.mode; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/DHState.java b/src/main/java/com/southernstorm/noise/protocol/DHState.java new file mode 100644 index 0000000..5e0e5d8 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/DHState.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +/** + * Interface to a Diffie-Hellman algorithm for the Noise protocol. + */ +public interface DHState extends Destroyable { + + /** + * Gets the Noise protocol name for this Diffie-Hellman algorithm. + * + * @return The algorithm name. + */ + String getDHName(); + + /** + * Gets the length of public keys for this algorithm. + * + * @return The length of public keys in bytes. + */ + int getPublicKeyLength(); + + /** + * Gets the length of private keys for this algorithm. + * + * @return The length of private keys in bytes. + */ + int getPrivateKeyLength(); + + /** + * Gets the length of shared keys for this algorithm. + * + * @return The length of shared keys in bytes. + */ + int getSharedKeyLength(); + + /** + * Generates a new random keypair. + */ + void generateKeyPair(); + + /** + * Gets the public key associated with this object. + * + * @param key The buffer to copy the public key to. + * @param offset The first offset in the key buffer to copy to. + */ + void getPublicKey(byte[] key, int offset); + + /** + * Sets the public key for this object. + * + * @param key The buffer containing the public key. + * @param offset The first offset in the buffer that contains the key. + * + * If this object previously held a key pair, then this function + * will change it into a public key only object. + */ + void setPublicKey(byte[] key, int offset); + + /** + * Gets the private key associated with this object. + * + * @param key The buffer to copy the private key to. + * @param offset The first offset in the key buffer to copy to. + */ + void getPrivateKey(byte[] key, int offset); + + /** + * Sets the private key for this object. + * + * @param key The buffer containing the [rivate key. + * @param offset The first offset in the buffer that contains the key. + * + * If this object previously held only a public key, then + * this function will change it into a key pair. + */ + void setPrivateKey(byte[] key, int offset); + + /** + * Sets this object to the null public key and clears the private key. + */ + void setToNullPublicKey(); + + /** + * Clears the key pair. + */ + void clearKey(); + + /** + * Determine if this object contains a public key. + * + * @return Returns true if this object contains a public key, + * or false if the public key has not yet been set. + */ + boolean hasPublicKey(); + + /** + * Determine if this object contains a private key. + * + * @return Returns true if this object contains a private key, + * or false if the private key has not yet been set. + */ + boolean hasPrivateKey(); + + /** + * Determine if the public key in this object is the special null value. + * + * @return Returns true if the public key is the special null value, + * or false otherwise. + */ + boolean isNullPublicKey(); + + /** + * Performs a Diffie-Hellman calculation with this object as the private key. + * + * @param sharedKey Buffer to put the shared key into. + * @param offset Offset of the first byte for the shared key. + * @param publicDH Object that contains the public key for the calculation. + * + * @throws IllegalArgumentException The publicDH object is not the same + * type as this object, or one of the objects does not contain a valid key. + */ + void calculate(byte[] sharedKey, int offset, DHState publicDH); + + /** + * Copies the key values from another DH object of the same type. + * + * @param other The other DH object to copy from + * + * @throws IllegalStateException The other DH object does not have + * the same type as this object. + */ + void copyFrom(DHState other); +} diff --git a/src/main/java/com/southernstorm/noise/protocol/DHStateHybrid.java b/src/main/java/com/southernstorm/noise/protocol/DHStateHybrid.java new file mode 100644 index 0000000..e1887c3 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/DHStateHybrid.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +/** + * Additional API for DH objects that need special handling for + * hybrid operations. + */ +public interface DHStateHybrid extends DHState { + + /** + * Generates a new random keypair relative to the parameters + * in another object. + * + * @param remote The remote party in this communication to obtain parameters. + * + * @throws IllegalStateException The other or remote DH object does not have + * the same type as this object. + */ + void generateKeyPair(DHState remote); + + /** + * Copies the key values from another DH object of the same type. + * + * @param other The other DH object to copy from + * @param remote The remote party in this communication to obtain parameters. + * + * @throws IllegalStateException The other or remote DH object does not have + * the same type as this object. + */ + void copyFrom(DHState other, DHState remote); + + /** + * Specifies the local peer object prior to setting a public key + * on a remote object. + * + * @param local The local peer object. + */ + void specifyPeer(DHState local); +} diff --git a/src/main/java/com/southernstorm/noise/protocol/Destroyable.java b/src/main/java/com/southernstorm/noise/protocol/Destroyable.java new file mode 100644 index 0000000..30b514a --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/Destroyable.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +/** + * Interface for objects that implement destroying. + * + * Applications that use the Noise protocol can inadvertently leave + * sensitive data in the heap if steps are not taken to clean up. + * + * This interface can be implemented by objects that know how to + * securely clean up after themselves. + * + * 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(); +} diff --git a/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java new file mode 100644 index 0000000..ec19381 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/HandshakeState.java @@ -0,0 +1,1221 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +/** + * Interface to a Noise handshake. + */ +public class HandshakeState implements Destroyable { + + private SymmetricState symmetric; + private boolean isInitiator; + private DHState localKeyPair; + private DHState localEphemeral; + private DHState localHybrid; + private DHState remotePublicKey; + private DHState remoteEphemeral; + private DHState remoteHybrid; + private DHState fixedEphemeral; + private DHState fixedHybrid; + private int action; + private int requirements; + private short[] pattern; + private int patternIndex; + private byte[] preSharedKeyForNoisePSK; + private byte[] prologue; + private boolean isNoisePsk; + + /** + * Enumerated value that indicates that the handshake object + * is handling the initiator role. + */ + public static final int INITIATOR = 1; + + /** + * Enumerated value that indicates that the handshake object + * is handling the responder role. + */ + public static final int RESPONDER = 2; + + /** + * No action is required of the application yet because the + * handshake has not started. + */ + public static final int NO_ACTION = 0; + + /** + * The HandshakeState expects the application to write the + * next message payload for the handshake. + */ + public static final int WRITE_MESSAGE = 1; + + /** + * The HandshakeState expects the application to read the + * next message payload from the handshake. + */ + public static final int READ_MESSAGE = 2; + + /** + * The handshake has failed due to some kind of error. + */ + public static final int FAILED = 3; + + /** + * The handshake is over and the application is expected to call + * split() and begin data session communications. + */ + public static final int SPLIT = 4; + + /** + * The handshake is complete and the data session ciphers + * have been split() out successfully. + */ + public static final int COMPLETE = 5; + + /** + * Local static keypair is required for the handshake. + */ + private static final int LOCAL_REQUIRED = 0x01; + + /** + * Remote static keypai is required for the handshake. + */ + private static final int REMOTE_REQUIRED = 0x02; + + /** + * Pre-shared key is required for the handshake. + */ + private static final int PSK_REQUIRED = 0x04; + + /** + * Ephemeral key for fallback pre-message has been provided. + */ + private static final int FALLBACK_PREMSG = 0x08; + + /** + * The local public key is part of the pre-message. + */ + private static final int LOCAL_PREMSG = 0x10; + + /** + * The remote public key is part of the pre-message. + */ + private static final int REMOTE_PREMSG = 0x20; + + /** + * Fallback is possible from this pattern (two-way, ends in "K"). + */ + private static final int FALLBACK_POSSIBLE = 0x40; + + /** + * Creates a new Noise handshake. + * + * @param protocolName The name of the Noise protocol. + * @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER. + * + * @throws IllegalArgumentException The protocolName is not + * formatted correctly, or the role is not recognized. + * + * @throws NoSuchAlgorithmException One of the cryptographic algorithms + * that is specified in the protocolName is not supported. + */ + public HandshakeState(String protocolName, int role) throws NoSuchAlgorithmException { + // Parse the protocol name into its components. + String[] components = protocolName.split("_"); + if (components.length != 5) + throw new IllegalArgumentException("Protocol name must have 5 components"); + String prefix = components[0]; + String patternId = components[1]; + String dh = components[2]; + String hybrid = null; + String cipher = components[3]; + String hash = components[4]; + if (!prefix.equals("Noise") && !prefix.equals("NoisePSK")) + throw new IllegalArgumentException("Prefix must be Noise or NoisePSK"); + isNoisePsk = prefix.equals("NoisePSK"); + pattern = Pattern.lookup(patternId); + if (pattern == null) + throw new IllegalArgumentException("Handshake pattern is not recognized " + patternId + " " + protocolName); + short flags = pattern[0]; + int extraReqs = 0; + if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0 && patternId.length() > 1) + extraReqs |= FALLBACK_POSSIBLE; + if ((flags & Pattern.FLAG_PSK) != 0) { + extraReqs |= PSK_REQUIRED; + } + if (role == RESPONDER) { + // Reverse the pattern flags so that the responder is "local". + flags = Pattern.reverseFlags(flags); + } + int index = dh.indexOf('+'); + if (index != -1) { + // The DH name has two components: regular and hybrid. + hybrid = dh.substring(index + 1); + dh = dh.substring(0, index); + if ((flags & Pattern.FLAG_LOCAL_HYBRID) == 0 || (flags & Pattern.FLAG_REMOTE_HYBRID) == 0) + throw new IllegalArgumentException("Hybrid function specified for non-hybrid pattern"); + } else { + if ((flags & Pattern.FLAG_LOCAL_HYBRID) != 0 || (flags & Pattern.FLAG_REMOTE_HYBRID) != 0) + throw new IllegalArgumentException("Hybrid function not specified for hybrid pattern"); + } + + // Check that the role is correctly specified. + if (role != INITIATOR && role != RESPONDER) + throw new IllegalArgumentException("Role must be initiator or responder"); + + // Initialize this object. This will also create the cipher and hash objects. + symmetric = new SymmetricState(protocolName, cipher, hash); + isInitiator = (role == INITIATOR); + action = NO_ACTION; + requirements = extraReqs | computeRequirements(flags, prefix, role, false); + patternIndex = 1; + + // Create the DH objects that we will need later. + if ((flags & Pattern.FLAG_LOCAL_STATIC) != 0) + localKeyPair = Noise.createDH(dh); + if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0) + localEphemeral = Noise.createDH(dh); + if ((flags & Pattern.FLAG_LOCAL_HYBRID) != 0) + localHybrid = Noise.createDH(hybrid); + if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0) + remotePublicKey = Noise.createDH(dh); + if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0) + remoteEphemeral = Noise.createDH(dh); + if ((flags & Pattern.FLAG_REMOTE_HYBRID) != 0) + remoteHybrid = Noise.createDH(hybrid); + + // We cannot use hybrid algorithms like New Hope for ephemeral or static keys, + // as the unbalanced nature of the algorithm only works with "f" and "ff" tokens. + if (localKeyPair instanceof DHStateHybrid) + throw new NoSuchAlgorithmException("Cannot use '" + localKeyPair.getDHName() + "' for static keys"); + if (localEphemeral instanceof DHStateHybrid) + throw new NoSuchAlgorithmException("Cannot use '" + localEphemeral.getDHName() + "' for ephemeral keys"); + if (remotePublicKey instanceof DHStateHybrid) + throw new NoSuchAlgorithmException("Cannot use '" + remotePublicKey.getDHName() + "' for static keys"); + if (remoteEphemeral instanceof DHStateHybrid) + throw new NoSuchAlgorithmException("Cannot use '" + remoteEphemeral.getDHName() + "' for ephemeral keys"); + } + + /** + * Gets the name of the Noise protocol. + * + * @return The protocol name. + */ + public String getProtocolName() { + return symmetric.getProtocolName(); + } + + /** + * Gets the role for this handshake. + * + * @return The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER. + */ + public int getRole() { + return isInitiator ? INITIATOR : RESPONDER; + } + + /** + * Determine if this handshake needs a pre-shared key value + * and one has not been configured yet. + * + * @return true if a pre-shared key is needed; false if not. + */ + public boolean needsPreSharedKey() { + if (preSharedKeyForNoisePSK != null) + return false; + else + return (requirements & PSK_REQUIRED) != 0; + } + + /** + * Determine if this object has already been configured with a + * pre-shared key. + * + * @return true if the pre-shared key has already been configured; + * false if one is not needed or it has not been configured yet. + */ + public boolean hasPreSharedKey() { + return preSharedKeyForNoisePSK != null; + } + + /** + * Sets the pre-shared key for this handshake. + * + * @param key Buffer containing the pre-shared key value. + * @param offset Offset into the buffer of the first byte of the key. + * @param length The length of the pre-shared key, which must be 32. + * + * @throws IllegalArgumentException The length is not 32. + * + * @throws UnsupportedOperationException Pre-shared keys are not + * supported for this handshake type. + * + * @throws IllegalStateException The handshake has already started, + * so the pre-shared key can no longer be set. + */ + public void setPreSharedKey(byte[] key, int offset, int length) { + if (length != 32) { + throw new IllegalArgumentException("Pre-shared keys must be 32 bytes in length"); + } + if ((requirements & PSK_REQUIRED) == 0) { + throw new UnsupportedOperationException("Pre-shared keys are not supported for this handshake"); + } + if (action != NO_ACTION) { + throw new IllegalStateException("Handshake has already started; cannot set pre-shared key"); + } + if (preSharedKeyForNoisePSK != null) { + Noise.destroy(preSharedKeyForNoisePSK); + preSharedKeyForNoisePSK = null; + } + preSharedKeyForNoisePSK = Noise.copySubArray(key, offset, length); + } + + /** + * Sets the prologue for this handshake. + * + * @param prologue Buffer containing the prologue value. + * @param offset Offset into the buffer of the first byte of the prologue. + * @param length The length of the prologue in bytes. + * + * @throws IllegalStateException The handshake has already started, + * so the prologue can no longer be set. + */ + public void setPrologue(byte[] prologue, int offset, int length) { + if (action != NO_ACTION) { + throw new IllegalStateException("Handshake has already started; cannot set prologue"); + } + if (this.prologue != null) { + Noise.destroy(this.prologue); + this.prologue = null; + } + this.prologue = Noise.copySubArray(prologue, offset, length); + } + + /** + * Gets the keypair object for the local static key. + * + * @return The keypair, or null if a local static key is not required. + */ + public DHState getLocalKeyPair() { + return localKeyPair; + } + + /** + * Determine if this handshake requires a local static key. + * + * @return true if a local static key is needed; false if not. + * + * If the local static key has already been set, then this function + * will return false. + */ + public boolean needsLocalKeyPair() { + if (localKeyPair != null) + return !localKeyPair.hasPrivateKey(); + else + return false; + } + + /** + * Determine if this handshake has already been configured + * with a local static key. + * + * @return true if the local static key has been configured; + * false if not. + */ + public boolean hasLocalKeyPair() { + if (localKeyPair != null) + return localKeyPair.hasPrivateKey(); + else + return false; + } + + /** + * Gets the public key object for the remote static key. + * + * @return The public key, or null if a remote static key + * is not required. + */ + public DHState getRemotePublicKey() { + return remotePublicKey; + } + + /** + * Determine if this handshake requires a remote static key. + * + * @return true if a remote static key is needed; false if not. + * + * If the remote static key has already been set, then this function + * will return false. + */ + public boolean needsRemotePublicKey() { + if (remotePublicKey != null) + return !remotePublicKey.hasPublicKey(); + else + return false; + } + + /** + * Determine if this handshake has already been configured + * with a remote static key. + * + * @return true if the remote static key has been configured; + * false if not. + */ + public boolean hasRemotePublicKey() { + if (remotePublicKey != null) + return remotePublicKey.hasPublicKey(); + else + return false; + } + + /** + * Gets the DHState object containing a fixed local ephemeral + * key value for this handshake. + * + * @return The fixed ephemeral key object, or null if a local + * ephemeral key is not required by this handshake. + * + * This function is intended for testing only. It can be used + * to establish a fixed ephemeral key for test vectors. This + * function should not be used in real applications. + */ + public DHState getFixedEphemeralKey() { + if (fixedEphemeral != null) + return fixedEphemeral; + if (localEphemeral == null) + return null; + try { + fixedEphemeral = Noise.createDH(localEphemeral.getDHName()); + } catch (NoSuchAlgorithmException e) { + // This shouldn't happen - the local ephemeral key would + // have already been created with the same name! + fixedEphemeral = null; + } + return fixedEphemeral; + } + + /** + * Gets the DHState object containing a fixed local hybrid + * key value for this handshake. + * + * @return The fixed hybrid key object, or null if a local + * hybrid key is not required by this handshake. + * + * This function is intended for testing only. It can be used + * to establish a fixed hybrid key for test vectors. This + * function should not be used in real applications. + */ + public DHState getFixedHybridKey() { + if (fixedHybrid != null) + return fixedHybrid; + if (localHybrid == null) + return null; + try { + fixedHybrid = Noise.createDH(localHybrid.getDHName()); + } catch (NoSuchAlgorithmException e) { + // This shouldn't happen - the local hybrid key would + // have already been created with the same name! + fixedHybrid = null; + } + return fixedHybrid; + } + + // Empty value for when the prologue is not supplied. + private static final byte[] emptyPrologue = new byte[0]; + + /** + * Starts the handshake running. + * + * This function is called after all of the handshake parameters have been + * provided to the HandshakeState object. This function should be followed + * by calls to writeMessage() or readMessage() to process the handshake + * messages. The getAction() function indicates the action to take next. + * + * @throws IllegalStateException The handshake has already started, or one or + * more of the required parameters has not been supplied. + * + * @throws UnsupportedOperationException An attempt was made to start a + * fallback handshake pattern without first calling fallback() on a + * previous handshake. + * + * @see #getAction() + * @see #writeMessage(byte[], int, byte[], int, int) + * @see #readMessage(byte[], int, int, byte[], int) + * @see #fallback() + */ + public void start() { + if (action != NO_ACTION) { + throw new IllegalStateException("Handshake has already started; cannot start again"); + } + if ((pattern[0] & Pattern.FLAG_REMOTE_EPHEM_REQ) != 0 && (requirements & FALLBACK_PREMSG) == 0) { + throw new UnsupportedOperationException("Cannot start a fallback pattern"); + } + + // Check that we have satisfied all of the pattern requirements. + if ((requirements & LOCAL_REQUIRED) != 0) { + if (localKeyPair == null || !localKeyPair.hasPrivateKey()) + throw new IllegalStateException("Local static key required"); + } + if ((requirements & REMOTE_REQUIRED) != 0) { + if (remotePublicKey == null || !remotePublicKey.hasPublicKey()) + throw new IllegalStateException("Remote static key required"); + } + if ((requirements & PSK_REQUIRED) != 0) { + if (preSharedKeyForNoisePSK == null) + throw new IllegalStateException("Pre-shared key required"); + } + + // Hash the prologue value. + if (prologue != null) + symmetric.mixHash(prologue, 0, prologue.length); + else + symmetric.mixHash(emptyPrologue, 0, 0); + + // Hash the pre-shared key into the chaining key and handshake hash. + // FIXME: AM: isNoisePsk needed to support NNpsk0 etc. Why? ;) + if (isNoisePsk && preSharedKeyForNoisePSK != null) + symmetric.mixPreSharedKey(preSharedKeyForNoisePSK); + + // Mix the pre-supplied public keys into the handshake hash. + if (isInitiator) { + if ((requirements & LOCAL_PREMSG) != 0) + symmetric.mixPublicKey(localKeyPair); + if ((requirements & FALLBACK_PREMSG) != 0) { + symmetric.mixPublicKey(remoteEphemeral); + if (remoteHybrid != null) + symmetric.mixPublicKey(remoteHybrid); + if (preSharedKeyForNoisePSK != null) + symmetric.mixPublicKeyIntoCK(remoteEphemeral); + } + if ((requirements & REMOTE_PREMSG) != 0) + symmetric.mixPublicKey(remotePublicKey); + } else { + if ((requirements & REMOTE_PREMSG) != 0) + symmetric.mixPublicKey(remotePublicKey); + if ((requirements & FALLBACK_PREMSG) != 0) { + symmetric.mixPublicKey(localEphemeral); + if (localHybrid != null) + symmetric.mixPublicKey(localHybrid); + if (preSharedKeyForNoisePSK != null) + symmetric.mixPublicKeyIntoCK(localEphemeral); + } + if ((requirements & LOCAL_PREMSG) != 0) + symmetric.mixPublicKey(localKeyPair); + } + + // The handshake has officially started - set the first action. + if (isInitiator) + action = WRITE_MESSAGE; + else + action = READ_MESSAGE; + } + + /** + * Falls back to the "XXfallback" handshake pattern. + * + * This function is intended used to help implement the "Noise Pipes" protocol. + * It resets a HandshakeState object with the original handshake pattern + * (usually "IK"), converting it into an object with the handshake pattern + * "XXfallback". Information from the previous session such as the local + * keypair, the initiator's ephemeral key, the prologue value, and the + * pre-shared key, are passed to the new session. + * + * Once the fallback has been initiated, the application can set + * new values for the handshake parameters if the values from the + * previous session do not apply. For example, the application may + * use a different prologue for the fallback than for the original + * session. + * + * After setting any new parameters, the application calls start() + * again to restart the handshake from where it left off before the fallback. + * + * Note that this function reverses the roles of initiator and responder. + * + * @throws UnsupportedOperationException The current handshake pattern + * is not compatible with "XXfallback". + * + * @throws IllegalStateException The previous protocol has not started + * or it has not reached the fallback position yet. + * + * @throws NoSuchAlgorithmException One of the cryptographic algorithms + * that is specified in the new protocolName is not supported. + * + * @see #start() + */ + public void fallback() throws NoSuchAlgorithmException { + fallback("XXfallback"); + } + + /** + * Falls back to another handshake pattern. + * + * @param patternName The name of the pattern to fall back to; + * e.g. "XXfallback", "NXfallback", etc. + * + * This function resets a HandshakeState object with the original + * handshake pattern, and converts it into an object with the new handshake + * patternName. Information from the previous session such as the local + * keypair, the initiator's ephemeral key, the prologue value, and the + * pre-shared key, are passed to the new session. + * + * Once the fallback has been initiated, the application can set + * new values for the handshake parameters if the values from the + * previous session do not apply. For example, the application may + * use a different prologue for the fallback than for the original + * session. + * + * After setting any new parameters, the application calls start() + * again to restart the handshake from where it left off before the fallback. + * + * The new pattern may have greater key requirements than the original; + * for example changing from "NK" from "XXfallback" requires that the + * initiator's static public key be set. The application is responsible for + * setting any extra keys before calling start(). + * + * Note that this function reverses the roles of initiator and responder. + * + * @throws UnsupportedOperationException The current handshake pattern + * is not compatible with the patternName, or patternName is not a + * fallback pattern. + * + * @throws IllegalStateException The previous protocol has not started + * or it has not reached the fallback position yet. + * + * @throws NoSuchAlgorithmException One of the cryptographic algorithms + * that is specified in the new protocolName is not supported. + * + * @see #start() + */ + public void fallback(String patternName) throws NoSuchAlgorithmException { + // The original pattern must end in "K" for fallback to be possible. + if ((requirements & FALLBACK_POSSIBLE) == 0) + throw new UnsupportedOperationException("Previous handshake pattern does not support fallback"); + + // Check that "patternName" supports fallback. + short[] newPattern = Pattern.lookup(patternName); + if (newPattern == null || (newPattern[0] & Pattern.FLAG_REMOTE_EPHEM_REQ) == 0) + throw new UnsupportedOperationException("New pattern is not a fallback pattern"); + + // The initiator should be waiting for a return message from the + // responder, and the responder should have failed on the first + // handshake message from the initiator. We also allow the + // responder to fallback after processing the first message + // successfully; it decides to always fall back anyway. + if (isInitiator) { + if ((action != FAILED && action != READ_MESSAGE) || !localEphemeral.hasPublicKey()) + throw new IllegalStateException("Initiator cannot fall back from this state"); + } else { + if ((action != FAILED && action != WRITE_MESSAGE) || !remoteEphemeral.hasPublicKey()) + throw new IllegalStateException("Responder cannot fall back from this state"); + } + + // Format a new protocol name for the fallback variant + // and recreate the SymmetricState object. + String[] components = symmetric.getProtocolName().split("_"); + components[1] = patternName; + StringBuilder builder = new StringBuilder(); + builder.append(components[0]); + for (int index = 1; index < components.length; ++index) { + builder.append('_'); + builder.append(components[index]); + } + String name = builder.toString(); + SymmetricState newSymmetric = new SymmetricState(name, components[3], components[4]); + symmetric.destroy(); + symmetric = newSymmetric; + + // Convert the HandshakeState to the "XXfallback" pattern. + if (isInitiator) { + if (remoteEphemeral != null) + remoteEphemeral.clearKey(); + if (remoteHybrid != null) + remoteHybrid.clearKey(); + if (remotePublicKey != null) + remotePublicKey.clearKey(); + isInitiator = false; + } else { + if (localEphemeral != null) + localEphemeral.clearKey(); + if (localHybrid != null) + localHybrid.clearKey(); + if ((newPattern[0] & Pattern.FLAG_REMOTE_REQUIRED) == 0 && remotePublicKey != null) + remotePublicKey.clearKey(); + isInitiator = true; + } + action = NO_ACTION; + pattern = newPattern; + patternIndex = 1; + short flags = pattern[0]; + if (!isInitiator) { + // Reverse the pattern flags so that the responder is "local". + flags = Pattern.reverseFlags(flags); + } + requirements = computeRequirements(flags, components[0], isInitiator ? INITIATOR : RESPONDER, true); + } + + /** + * Gets the next action that the application should perform for + * the handshake part of the protocol. + * + * @return One of HandshakeState.NO_ACTION, HandshakeState.WRITE_MESSAGE, + * HandshakeState.READ_MESSAGE, HandshakeState.SPLIT, or + * HandshakeState.FAILED. + */ + public int getAction() { + return action; + } + + /** + * Mixes the result of a Diffie-Hellman calculation into the chaining key. + * + * @param local Local private key object. + * @param remote Remote public key object. + */ + private void mixDH(DHState local, DHState remote) { + if (local == null || remote == null) + throw new IllegalStateException("Pattern definition error"); + int len = local.getSharedKeyLength(); + byte[] shared = new byte[len]; + try { + local.calculate(shared, 0, remote); + symmetric.mixKey(shared, 0, len); + } finally { + Noise.destroy(shared); + } + } + + /** + * Writes a message payload during the handshake. + * + * @param message The buffer that will be populated with the + * handshake packet to be written to the transport. + * @param messageOffset First offset within the message buffer + * to be populated. + * @param payload Buffer containing the payload to add to the + * handshake message; can be null if there is no payload. + * @param payloadOffset Offset into the payload buffer of the + * first payload buffer. + * @param payloadLength Length of the payload in bytes. + * + * @return The length of the data written to the message buffer. + * + * @throws IllegalStateException The action is not WRITE_MESSAGE. + * + * @throws IllegalArgumentException The payload is null, but + * payloadOffset or payloadLength is non-zero. + * + * @throws ShortBufferException The message buffer does not have + * enough space for the handshake message. + * + * @see #getAction() + * @see #readMessage(byte[], int, int, byte[], int) + */ + public int writeMessage(byte[] message, int messageOffset, byte[] payload, int payloadOffset, int payloadLength) + throws ShortBufferException { + int messagePosn = messageOffset; + boolean success = false; + + // Validate the parameters and state. + if (action != WRITE_MESSAGE) { + throw new IllegalStateException("Handshake state does not allow writing messages"); + } + if (payload == null && (payloadOffset != 0 || payloadLength != 0)) { + throw new IllegalArgumentException("Invalid payload argument"); + } + if (messageOffset > message.length) { + throw new ShortBufferException(); + } + + // Format the message. + try { + // Process tokens until the direction changes or the patten ends. + for (;;) { + if (patternIndex >= pattern.length) { + // The pattern has finished, so the next action is "split". + action = SPLIT; + break; + } + short token = pattern[patternIndex++]; + if (token == Pattern.FLIP_DIR) { + // Change directions, so this message is complete and the + // next action is "read message". + action = READ_MESSAGE; + break; + } + int space = message.length - messagePosn; + int len, macLen; + switch (token) { + case Pattern.E: { + // Generate a local ephemeral keypair and add the public + // key to the message. If we are running fixed vector tests, + // then the ephemeral key may have already been provided. + if (localEphemeral == null) + throw new IllegalStateException("Pattern definition error"); + if (fixedEphemeral == null) + localEphemeral.generateKeyPair(); + else + localEphemeral.copyFrom(fixedEphemeral); + len = localEphemeral.getPublicKeyLength(); + if (space < len) + throw new ShortBufferException(); + localEphemeral.getPublicKey(message, messagePosn); + symmetric.mixHash(message, messagePosn, len); + + // If the protocol is using pre-shared keys, then also mix + // the local ephemeral key into the chaining key. + if (preSharedKeyForNoisePSK != null) + symmetric.mixKey(message, messagePosn, len); + messagePosn += len; + } + break; + + case Pattern.S: { + // Encrypt the local static public key and add it to the message. + if (localKeyPair == null) + throw new IllegalStateException("Pattern definition error"); + len = localKeyPair.getPublicKeyLength(); + macLen = symmetric.getMACLength(); + if (space < (len + macLen)) + throw new ShortBufferException(); + localKeyPair.getPublicKey(message, messagePosn); + messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len); + } + break; + + case Pattern.EE: { + // DH operation with initiator and responder ephemeral keys. + mixDH(localEphemeral, remoteEphemeral); + } + break; + + case Pattern.ES: { + // DH operation with initiator ephemeral and responder static keys. + if (isInitiator) + mixDH(localEphemeral, remotePublicKey); + else + mixDH(localKeyPair, remoteEphemeral); + } + break; + + case Pattern.SE: { + // DH operation with initiator static and responder ephemeral keys. + if (isInitiator) + mixDH(localKeyPair, remoteEphemeral); + else + mixDH(localEphemeral, remotePublicKey); + } + break; + + case Pattern.SS: { + // DH operation with initiator and responder static keys. + mixDH(localKeyPair, remotePublicKey); + } + break; + + case Pattern.F: { + // Generate a local hybrid keypair and add the public + // key to the message. If we are running fixed vector tests, + // then a fixed hybrid key may have already been provided. + if (localHybrid == null) + throw new IllegalStateException("Pattern definition error"); + if (localHybrid instanceof DHStateHybrid) { + // The DH object is something like New Hope which needs to + // generate keys relative to the other party's public key. + DHStateHybrid hybrid = (DHStateHybrid) localHybrid; + if (fixedHybrid == null) + hybrid.generateKeyPair(remoteHybrid); + else + hybrid.copyFrom(fixedHybrid, remoteHybrid); + } else { + if (fixedHybrid == null) + localHybrid.generateKeyPair(); + else + localHybrid.copyFrom(fixedHybrid); + } + len = localHybrid.getPublicKeyLength(); + if (space < len) + throw new ShortBufferException(); + macLen = symmetric.getMACLength(); + if (space < (len + macLen)) + throw new ShortBufferException(); + localHybrid.getPublicKey(message, messagePosn); + messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len); + } + break; + + case Pattern.FF: { + // DH operation with initiator and responder hybrid keys. + mixDH(localHybrid, remoteHybrid); + } + break; + + case Pattern.PSK: { + symmetric.mixKeyAndHash(preSharedKeyForNoisePSK, 0, preSharedKeyForNoisePSK.length); + } + break; + + default: { + // Unknown token code. Abort. + throw new IllegalStateException("Unknown handshake token " + Integer.toString(token)); + } + } + } + + // Add the payload to the message buffer and encrypt it. + if (payload != null) + messagePosn += symmetric.encryptAndHash(payload, payloadOffset, message, messagePosn, payloadLength); + else + messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, 0); + success = true; + } finally { + // If we failed, then clear any sensitive data that may have + // already been written to the message buffer. + if (!success) { + Arrays.fill(message, messageOffset, message.length - messageOffset, (byte) 0); + action = FAILED; + } + } + return messagePosn - messageOffset; + } + + /** + * Reads a message payload during the handshake. + * + * @param message Buffer containing the incoming handshake + * that was read from the transport. + * @param messageOffset Offset of the first message byte. + * @param messageLength The length of the incoming message. + * @param payload Buffer that will be populated with the message payload. + * @param payloadOffset Offset of the first byte in the + * payload buffer to be populated with payload data. + * + * @return The length of the payload. + * + * @throws IllegalStateException The action is not READ_MESSAGE. + * + * @throws ShortBufferException The message buffer does not have + * sufficient bytes for a valid message or the payload buffer does + * not have enough space for the decrypted payload. + * + * @throws BadPaddingException A MAC value in the message failed + * to verify. + * + * @see #getAction() + * @see #writeMessage(byte[], int, byte[], int, int) + */ + public int readMessage(byte[] message, int messageOffset, int messageLength, byte[] payload, int payloadOffset) + throws ShortBufferException, BadPaddingException { + boolean success = false; + int messageEnd = messageOffset + messageLength; + + // Validate the parameters. + if (action != READ_MESSAGE) { + throw new IllegalStateException("Handshake state does not allow reading messages"); + } + if (messageOffset > message.length || payloadOffset > payload.length) { + throw new ShortBufferException(); + } + if (messageLength > (message.length - messageOffset)) { + throw new ShortBufferException(); + } + + // Process the message. + try { + // Process tokens until the direction changes or the patten ends. + for (;;) { + if (patternIndex >= pattern.length) { + // The pattern has finished, so the next action is "split". + action = SPLIT; + break; + } + short token = pattern[patternIndex++]; + if (token == Pattern.FLIP_DIR) { + // Change directions, so this message is complete and the + // next action is "write message". + action = WRITE_MESSAGE; + break; + } + int space = messageEnd - messageOffset; + int len, macLen; + switch (token) { + case Pattern.E: { + // Save the remote ephemeral key and hash it. + if (remoteEphemeral == null) + throw new IllegalStateException("Pattern definition error"); + len = remoteEphemeral.getPublicKeyLength(); + if (space < len) + throw new ShortBufferException(); + symmetric.mixHash(message, messageOffset, len); + remoteEphemeral.setPublicKey(message, messageOffset); + if (remoteEphemeral.isNullPublicKey()) { + // The remote ephemeral key is null, which means that it is + // not contributing anything to the security of the session + // and is in fact downgrading the security to "none at all" + // in some of the message patterns. Reject all such keys. + throw new BadPaddingException("Null remote public key"); + } + + // If the protocol is using pre-shared keys, then also mix + // the remote ephemeral key into the chaining key. + if (preSharedKeyForNoisePSK != null) + symmetric.mixKey(message, messageOffset, len); + messageOffset += len; + } + break; + + case Pattern.S: { + // Decrypt and read the remote static key. + if (remotePublicKey == null) + throw new IllegalStateException("Pattern definition error"); + len = remotePublicKey.getPublicKeyLength(); + macLen = symmetric.getMACLength(); + if (space < (len + macLen)) + throw new ShortBufferException(); + byte[] temp = new byte[len]; + try { + if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len) + throw new ShortBufferException(); + remotePublicKey.setPublicKey(temp, 0); + } finally { + Noise.destroy(temp); + } + messageOffset += len + macLen; + } + break; + + case Pattern.EE: { + // DH operation with initiator and responder ephemeral keys. + mixDH(localEphemeral, remoteEphemeral); + } + break; + + case Pattern.ES: { + // DH operation with initiator ephemeral and responder static keys. + if (isInitiator) + mixDH(localEphemeral, remotePublicKey); + else + mixDH(localKeyPair, remoteEphemeral); + } + break; + + case Pattern.SE: { + // DH operation with initiator static and responder ephemeral keys. + if (isInitiator) + mixDH(localKeyPair, remoteEphemeral); + else + mixDH(localEphemeral, remotePublicKey); + } + break; + + case Pattern.SS: { + // DH operation with initiator and responder static keys. + mixDH(localKeyPair, remotePublicKey); + } + break; + + case Pattern.F: { + // Decrypt and read the remote hybrid ephemeral key. + if (remoteHybrid == null) + throw new IllegalStateException("Pattern definition error"); + if (remoteHybrid instanceof DHStateHybrid) { + // The DH object is something like New Hope. The public key + // length may need to change based on whether we already have + // generated a local hybrid keypair or not. + ((DHStateHybrid) remoteHybrid).specifyPeer(localHybrid); + } + len = remoteHybrid.getPublicKeyLength(); + macLen = symmetric.getMACLength(); + if (space < (len + macLen)) + throw new ShortBufferException(); + byte[] temp = new byte[len]; + try { + if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len) + throw new ShortBufferException(); + remoteHybrid.setPublicKey(temp, 0); + } finally { + Noise.destroy(temp); + } + messageOffset += len + macLen; + } + break; + + case Pattern.FF: { + // DH operation with initiator and responder hybrid keys. + mixDH(localHybrid, remoteHybrid); + } + break; + case Pattern.PSK: { + symmetric.mixKeyAndHash(preSharedKeyForNoisePSK, 0, preSharedKeyForNoisePSK.length); + } + break; + + default: { + // Unknown token code. Abort. + throw new IllegalStateException("Unknown handshake token " + Integer.toString(token)); + } + } + } + + // Decrypt the message payload. + int payloadLength = symmetric.decryptAndHash(message, messageOffset, payload, payloadOffset, + messageEnd - messageOffset); + success = true; + return payloadLength; + } finally { + // If we failed, then clear any sensitive data that may have + // already been written to the payload buffer. + if (!success) { + Arrays.fill(payload, payloadOffset, payload.length - payloadOffset, (byte) 0); + action = FAILED; + } + } + } + + /** + * Splits the transport encryption CipherState objects out of + * this HandshakeState object once the handshake completes. + * + * @return The pair of ciphers for sending and receiving. + * + * @throws IllegalStateException The action is not SPLIT. + */ + public CipherStatePair split() { + if (action != SPLIT) { + throw new IllegalStateException("Handshake has not finished"); + } + CipherStatePair pair = symmetric.split(); + if (!isInitiator) + pair.swap(); + action = COMPLETE; + return pair; + } + + /** + * Splits the transport encryption CipherState objects out of + * this HandshakeObject after mixing in a secondary symmetric key. + * + * @param secondaryKey The buffer containing the secondary key. + * @param offset The offset of the first secondary key byte. + * @param length The length of the secondary key in bytes, which + * must be either 0 or 32. + * @return The pair of ciphers for sending and receiving. + * + * @throws IllegalStateException The action is not SPLIT. + * + * @throws IllegalArgumentException The length is not 0 or 32. + */ + public CipherStatePair split(byte[] secondaryKey, int offset, int length) { + if (action != SPLIT) { + throw new IllegalStateException("Handshake has not finished"); + } + CipherStatePair pair = symmetric.split(secondaryKey, offset, length); + if (!isInitiator) { + // Swap the sender and receiver objects for the responder + // to make it easier on the application to know which is which. + pair.swap(); + } + action = COMPLETE; + return pair; + } + + /** + * Gets the current value of the handshake hash. + * + * @return The handshake hash. This must not be modified by the caller. + * + * @throws IllegalStateException The action is not SPLIT or COMPLETE. + */ + public byte[] getHandshakeHash() { + if (action != SPLIT && action != COMPLETE) { + throw new IllegalStateException("Handshake has not completed"); + } + return symmetric.getHandshakeHash(); + } + + @Override + public void destroy() { + if (symmetric != null) + symmetric.destroy(); + if (localKeyPair != null) + localKeyPair.destroy(); + if (localEphemeral != null) + localEphemeral.destroy(); + if (localHybrid != null) + localHybrid.destroy(); + if (remotePublicKey != null) + remotePublicKey.destroy(); + if (remoteEphemeral != null) + remoteEphemeral.destroy(); + if (remoteHybrid != null) + remoteHybrid.destroy(); + if (fixedEphemeral != null) + fixedEphemeral.destroy(); + if (fixedHybrid != null) + fixedHybrid.destroy(); + if (preSharedKeyForNoisePSK != null) + Noise.destroy(preSharedKeyForNoisePSK); + if (prologue != null) + Noise.destroy(prologue); + } + + /** + * Computes the requirements for a handshake. + * + * @param flags The flags from the handshake's pattern. + * @param prefix The prefix from the protocol name; typically + * "Noise" or "NoisePSK". + * @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER. + * @param isFallback Set to true if we need the requirements for a + * fallback pattern; false for a regular pattern. + * + * @return The set of requirements for the handshake. + */ + private static int computeRequirements(short flags, String prefix, int role, boolean isFallback) { + int requirements = 0; + if ((flags & Pattern.FLAG_LOCAL_STATIC) != 0) { + requirements |= LOCAL_REQUIRED; + } + if ((flags & Pattern.FLAG_LOCAL_REQUIRED) != 0) { + requirements |= LOCAL_REQUIRED; + requirements |= LOCAL_PREMSG; + } + if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0) { + requirements |= REMOTE_REQUIRED; + requirements |= REMOTE_PREMSG; + } + if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ | Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) { + if (isFallback) + requirements |= FALLBACK_PREMSG; + } + if (prefix.equals("NoisePSK")) { + requirements |= PSK_REQUIRED; + } + return requirements; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/NewHopeDHState.java b/src/main/java/com/southernstorm/noise/protocol/NewHopeDHState.java new file mode 100644 index 0000000..5f43f09 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/NewHopeDHState.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.util.Arrays; + +import com.southernstorm.noise.crypto.NewHope; +import com.southernstorm.noise.crypto.NewHopeTor; + +/** + * Implementation of the New Hope post-quantum algorithm for the Noise protocol. + */ +final class NewHopeDHState implements DHStateHybrid { + + enum KeyType { + None, + AlicePrivate, + AlicePublic, + BobPrivate, + BobPublic, + BobCalculated; + } + + private NewHopeTor nh; + private byte[] publicKey; + private byte[] privateKey; + private KeyType keyType; + + /** + * Special version of NewHopeTor that allows explicit random data + * to be specified for test vectors. + */ + private class NewHopeWithPrivateKey extends NewHopeTor { + + byte[] randomData; + + public NewHopeWithPrivateKey(byte[] randomData) { + this.randomData = randomData; + } + + @Override + protected void randombytes(byte[] buffer) { + System.arraycopy(randomData, 0, buffer, 0, buffer.length); + } + } + + /** + * Constructs a new key exchange object for New Hope. + */ + public NewHopeDHState() { + nh = null; + publicKey = null; + privateKey = null; + keyType = KeyType.None; + } + + private boolean isAlice() { + return keyType == KeyType.AlicePrivate || keyType == KeyType.AlicePublic; + } + + @Override + public void destroy() { + clearKey(); + } + + @Override + public String getDHName() { + return "NewHope"; + } + + @Override + public int getPublicKeyLength() { + if (isAlice()) + return NewHope.SENDABYTES; + else + return NewHope.SENDBBYTES; + } + + @Override + public int getPrivateKeyLength() { + // New Hope doesn't have private keys in the same sense as + // Curve25519 and Curve448. Instead return the number of + // random bytes that we need to generate each key type. + if (isAlice()) + return 64; + else + return 32; + } + + @Override + public int getSharedKeyLength() { + return NewHope.SHAREDBYTES; + } + + @Override + public void generateKeyPair() { + clearKey(); + keyType = KeyType.AlicePrivate; + nh = new NewHopeTor(); + publicKey = new byte[NewHope.SENDABYTES]; + nh.keygen(publicKey, 0); + } + + @Override + public void generateKeyPair(DHState remote) { + if (remote == null) { + // No remote public key, so always generate in Alice mode. + generateKeyPair(); + return; + } else if (!(remote instanceof NewHopeDHState)) { + throw new IllegalStateException("Mismatched DH objects"); + } + NewHopeDHState r = (NewHopeDHState) remote; + if (r.isAlice() && r.publicKey != null) { + // We have a remote public key for Alice, so generate in Bob mode. + clearKey(); + keyType = KeyType.BobCalculated; + nh = new NewHopeTor(); + publicKey = new byte[NewHope.SENDBBYTES]; + privateKey = new byte[NewHope.SHAREDBYTES]; + nh.sharedb(privateKey, 0, publicKey, 0, r.publicKey, 0); + } else { + generateKeyPair(); + } + } + + @Override + public void getPublicKey(byte[] key, int offset) { + if (publicKey != null) + System.arraycopy(publicKey, 0, key, offset, getPublicKeyLength()); + else + Arrays.fill(key, 0, getPublicKeyLength(), (byte) 0); + } + + @Override + public void setPublicKey(byte[] key, int offset) { + if (publicKey != null) + Noise.destroy(publicKey); + publicKey = new byte[getPublicKeyLength()]; + System.arraycopy(key, 0, publicKey, 0, publicKey.length); + } + + @Override + public void getPrivateKey(byte[] key, int offset) { + if (privateKey != null) + System.arraycopy(privateKey, 0, key, offset, getPrivateKeyLength()); + else + Arrays.fill(key, 0, getPrivateKeyLength(), (byte) 0); + } + + @Override + public void setPrivateKey(byte[] key, int offset) { + clearKey(); + // Guess the key type from the length of the test data. + if (offset == 0 && key.length == 64) + keyType = KeyType.AlicePrivate; + else + keyType = KeyType.BobPrivate; + privateKey = new byte[getPrivateKeyLength()]; + System.arraycopy(key, 0, privateKey, 0, privateKey.length); + } + + @Override + public void setToNullPublicKey() { + // Null public keys are not supported by New Hope. + // Destroy the current values but otherwise ignore. + clearKey(); + } + + @Override + public void clearKey() { + if (nh != null) { + nh.destroy(); + nh = null; + } + if (publicKey != null) { + Noise.destroy(publicKey); + publicKey = null; + } + if (privateKey != null) { + Noise.destroy(privateKey); + privateKey = null; + } + keyType = KeyType.None; + } + + @Override + public boolean hasPublicKey() { + return publicKey != null; + } + + @Override + public boolean hasPrivateKey() { + return privateKey != null; + } + + @Override + public boolean isNullPublicKey() { + return false; + } + + @Override + public void calculate(byte[] sharedKey, int offset, DHState publicDH) { + if (!(publicDH instanceof NewHopeDHState)) + throw new IllegalArgumentException("Incompatible DH algorithms"); + NewHopeDHState other = (NewHopeDHState) publicDH; + if (keyType == KeyType.AlicePrivate) { + // Compute the shared key for Alice. + nh.shareda(sharedKey, 0, other.publicKey, 0); + } else if (keyType == KeyType.BobCalculated) { + // The shared key for Bob was already computed when the key was generated. + System.arraycopy(privateKey, 0, sharedKey, 0, NewHope.SHAREDBYTES); + } else { + throw new IllegalStateException("Cannot calculate with this DH object"); + } + } + + @Override + public void copyFrom(DHState other) { + if (!(other instanceof NewHopeDHState)) + throw new IllegalStateException("Mismatched DH key objects"); + if (other == this) + return; + NewHopeDHState dh = (NewHopeDHState) other; + clearKey(); + switch (dh.keyType) { + case None: + break; + + case AlicePrivate: + if (dh.privateKey != null) { + keyType = KeyType.AlicePrivate; + privateKey = new byte[dh.privateKey.length]; + System.arraycopy(dh.privateKey, 0, privateKey, 0, privateKey.length); + } else { + throw new IllegalStateException("Cannot copy generated key for Alice"); + } + break; + + case BobPrivate: + case BobCalculated: + throw new IllegalStateException("Cannot copy private key for Bob without public key for Alice"); + + case AlicePublic: + case BobPublic: + keyType = dh.keyType; + publicKey = new byte[dh.publicKey.length]; + System.arraycopy(dh.publicKey, 0, publicKey, 0, publicKey.length); + break; + } + } + + @Override + public void copyFrom(DHState other, DHState remote) { + if (remote == null) { + copyFrom(other); + return; + } + if (!(other instanceof NewHopeDHState) || !(remote instanceof NewHopeDHState)) + throw new IllegalStateException("Mismatched DH key objects"); + if (other == this) + return; + NewHopeDHState dh = (NewHopeDHState) other; + NewHopeDHState remotedh = (NewHopeDHState) remote; + clearKey(); + switch (dh.keyType) { + case None: + break; + + case AlicePrivate: + if (dh.privateKey != null) { + // Generate Alice's public and private key now. + keyType = KeyType.AlicePrivate; + nh = new NewHopeWithPrivateKey(dh.privateKey); + publicKey = new byte[NewHope.SENDABYTES]; + nh.keygen(publicKey, 0); + } else { + throw new IllegalStateException("Cannot copy generated key for Alice"); + } + break; + + case BobPrivate: + if (dh.privateKey != null && remotedh.keyType == KeyType.AlicePublic) { + // Now we know the public key for Alice, we can calculate Bob's public and shared keys. + keyType = KeyType.BobCalculated; + nh = new NewHopeWithPrivateKey(dh.privateKey); + publicKey = new byte[NewHope.SENDBBYTES]; + privateKey = new byte[NewHope.SHAREDBYTES]; + nh.sharedb(privateKey, 0, publicKey, 0, remotedh.publicKey, 0); + } else { + throw new IllegalStateException("Cannot copy private key for Bob without public key for Alice"); + } + break; + + case BobCalculated: + throw new IllegalStateException("Cannot copy generated key for Bob"); + + case AlicePublic: + case BobPublic: + keyType = dh.keyType; + publicKey = new byte[dh.publicKey.length]; + System.arraycopy(dh.publicKey, 0, publicKey, 0, publicKey.length); + break; + } + } + + @Override + public void specifyPeer(DHState local) { + if (!(local instanceof NewHopeDHState)) + return; + clearKey(); + if (((NewHopeDHState) local).keyType == KeyType.AlicePrivate) + keyType = KeyType.BobPublic; + else + keyType = KeyType.AlicePublic; + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/Noise.java b/src/main/java/com/southernstorm/noise/protocol/Noise.java new file mode 100644 index 0000000..9b1fad1 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/Noise.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; + +import com.southernstorm.noise.crypto.Blake2bMessageDigest; +import com.southernstorm.noise.crypto.Blake2sMessageDigest; +import com.southernstorm.noise.crypto.SHA256MessageDigest; +import com.southernstorm.noise.crypto.SHA512MessageDigest; + +/** + * Utility functions for the Noise protocol library. + */ +public final class Noise { + + /** + * Maximum length for Noise packets. + */ + public static final int MAX_PACKET_LEN = 65535; + + private static SecureRandom random = new SecureRandom(); + + /** + * Generates random data using the system random number generator. + * + * @param data The data buffer to fill with random data. + */ + public static void random(byte[] data) { + random.nextBytes(data); + } + + private static boolean forceFallbacks = false; + + /** + * Force the use of plain Java fallback crypto implementations. + * + * @param force Set to true for force fallbacks, false to + * try to use the system implementation before falling back. + * + * This function is intended for testing purposes to toggle between + * the system JCA/JCE implementations and the plain Java fallback + * reference implementations. + */ + public static void setForceFallbacks(boolean force) { + forceFallbacks = force; + } + + /** + * Creates a Diffie-Hellman object from its Noise protocol name. + * + * @param name The name of the DH algorithm; e.g. "25519", "448", etc. + * + * @return The Diffie-Hellman object if the name is recognized. + * + * @throws NoSuchAlgorithmException The name is not recognized as a + * valid Noise protocol name, or there is no cryptography provider + * in the system that implements the algorithm. + */ + public static DHState createDH(String name) throws NoSuchAlgorithmException { + if (name.equals("25519")) + return new Curve25519DHState(); + if (name.equals("448")) + return new Curve448DHState(); + if (name.equals("NewHope")) + return new NewHopeDHState(); + throw new NoSuchAlgorithmException("Unknown Noise DH algorithm name: " + name); + } + + /** + * Creates a cipher object from its Noise protocol name. + * + * @param name The name of the cipher algorithm; e.g. "AESGCM", "ChaChaPoly", etc. + * + * @return The cipher object if the name is recognized. + * + * @throws NoSuchAlgorithmException The name is not recognized as a + * valid Noise protocol name, or there is no cryptography provider + * in the system that implements the algorithm. + */ + public static CipherState createCipher(String name) throws NoSuchAlgorithmException { + if (name.equals("AESGCM")) { + if (forceFallbacks) + return new AESGCMFallbackCipherState(); + // "AES/GCM/NoPadding" exists in some recent JDK's but it is flaky + // to use and not easily back-portable to older Android versions. + // We instead emulate AESGCM on top of "AES/CTR/NoPadding". + try { + return new AESGCMOnCtrCipherState(); + } catch (NoSuchAlgorithmException e1) { + // Could not find anything useful in the JCA/JCE so + // use the pure Java fallback implementation instead. + return new AESGCMFallbackCipherState(); + } + } else if (name.equals("ChaChaPoly")) { + return new ChaChaPolyCipherState(); + } + throw new NoSuchAlgorithmException("Unknown Noise cipher algorithm name: " + name); + } + + /** + * Creates a hash object from its Noise protocol name. + * + * @param name The name of the hash algorithm; e.g. "SHA256", "BLAKE2s", etc. + * + * @return The hash object if the name is recognized. + * + * @throws NoSuchAlgorithmException The name is not recognized as a + * valid Noise protocol name, or there is no cryptography provider + * in the system that implements the algorithm. + */ + public static MessageDigest createHash(String name) throws NoSuchAlgorithmException { + // Look for a JCA/JCE provider first and if that doesn't work, + // use the fallback implementations in this library instead. + // The only algorithm that is required to be implemented by a + // JDK is "SHA-256", although "SHA-512" is fairly common as well. + if (name.equals("SHA256")) { + if (forceFallbacks) + return new SHA256MessageDigest(); + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + return new SHA256MessageDigest(); + } + } else if (name.equals("SHA512")) { + if (forceFallbacks) + return new SHA512MessageDigest(); + try { + return MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + return new SHA512MessageDigest(); + } + } else if (name.equals("BLAKE2b")) { + // Bouncy Castle registers the BLAKE2b variant we + // want under the name "BLAKE2B-512". + if (forceFallbacks) + return new Blake2bMessageDigest(); + try { + return MessageDigest.getInstance("BLAKE2B-512"); + } catch (NoSuchAlgorithmException e) { + return new Blake2bMessageDigest(); + } + } else if (name.equals("BLAKE2s")) { + // Bouncy Castle doesn't currently (June 2016) have an + // implementation of BLAKE2s, but look for the most + // obvious provider name in case one is added in the future. + if (forceFallbacks) + return new Blake2sMessageDigest(); + try { + return MessageDigest.getInstance("BLAKE2S-256"); + } catch (NoSuchAlgorithmException e) { + return new Blake2sMessageDigest(); + } + } + throw new NoSuchAlgorithmException("Unknown Noise hash algorithm name: " + name); + } + + // The rest of this class consists of internal utility functions + // that are not part of the public API. + + /** + * Destroys the contents of a byte array. + * + * @param array The array whose contents should be destroyed. + */ + static void destroy(byte[] array) { + Arrays.fill(array, (byte) 0); + } + + /** + * Makes a copy of part of an array. + * + * @param data The buffer containing the data to copy. + * @param offset Offset of the first byte to copy. + * @param length The number of bytes to copy. + * + * @return A new array with a copy of the sub-array. + */ + static byte[] copySubArray(byte[] data, int offset, int length) { + byte[] copy = new byte[length]; + System.arraycopy(data, offset, copy, 0, length); + return copy; + } + + /** + * Throws an instance of AEADBadTagException. + * + * @throws BadPaddingException The AEAD exception. + * + * If the underlying JDK does not have the AEADBadTagException + * class, then this function will instead throw an instance of + * the superclass BadPaddingException. + */ + static void throwBadTagException() throws BadPaddingException { + try { + Class c = Class.forName("javax.crypto.AEADBadTagException"); + throw (BadPaddingException) (c.newInstance()); + } catch (ClassNotFoundException e) { + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } + throw new BadPaddingException(); + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/Pattern.java b/src/main/java/com/southernstorm/noise/protocol/Pattern.java new file mode 100644 index 0000000..9c4a59c --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/Pattern.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +/** + * Information about all supported handshake patterns. + */ +class Pattern { + + private Pattern() { + } + + // Token codes. + public static final short S = 1; + public static final short E = 2; + public static final short EE = 3; + public static final short ES = 4; + public static final short SE = 5; + public static final short SS = 6; + public static final short F = 7; + public static final short FF = 8; + public static final short PSK = 9; + public static final short FLIP_DIR = 255; + + // Pattern flag bits. + public static final short FLAG_LOCAL_STATIC = 0x0001; + public static final short FLAG_LOCAL_EPHEMERAL = 0x0002; + public static final short FLAG_LOCAL_REQUIRED = 0x0004; + public static final short FLAG_LOCAL_EPHEM_REQ = 0x0008; + public static final short FLAG_LOCAL_HYBRID = 0x0010; + public static final short FLAG_LOCAL_HYBRID_REQ = 0x0020; + public static final short FLAG_REMOTE_STATIC = 0x0100; + public static final short FLAG_REMOTE_EPHEMERAL = 0x0200; + public static final short FLAG_REMOTE_REQUIRED = 0x0400; + public static final short FLAG_REMOTE_EPHEM_REQ = 0x0800; + public static final short FLAG_REMOTE_HYBRID = 0x1000; + public static final short FLAG_REMOTE_HYBRID_REQ = 0x2000; + public static final short FLAG_PSK = 0x4000; + + private static final short[] noise_pattern_N = { FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_REQUIRED, + + E, ES }; + + private static final short[] noise_pattern_K = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_REMOTE_STATIC | FLAG_REMOTE_REQUIRED, + + E, ES, SS }; + + private static final short[] noise_pattern_X = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_REQUIRED, + + E, ES, S, SS }; + + private static final short[] noise_pattern_NN = { FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, EE }; + + private static final short[] noise_pattern_NK = { + FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_REQUIRED, + + E, ES, FLIP_DIR, E, EE }; + + private static final short[] noise_pattern_NX = { FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, EE, S, ES }; + + private static final short[] noise_pattern_XN = { FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, EE, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_XK = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_REQUIRED, + + E, ES, FLIP_DIR, E, EE, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_XX = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, EE, S, ES, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_KN = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, EE, SE }; + + private static final short[] noise_pattern_KK = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_REQUIRED, + + E, ES, SS, FLIP_DIR, E, EE, SE }; + + private static final short[] noise_pattern_KX = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, EE, SE, S, ES }; + + private static final short[] noise_pattern_IN = { FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_EPHEMERAL, + + E, S, FLIP_DIR, E, EE, SE }; + + private static final short[] noise_pattern_IK = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_REQUIRED, + + E, ES, S, SS, FLIP_DIR, E, EE, SE }; + + private static final short[] noise_pattern_IX = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, S, FLIP_DIR, E, EE, SE, S, ES }; + + private static final short[] noise_pattern_XXfallback = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_EPHEM_REQ, + + E, EE, S, SE, FLIP_DIR, S, ES }; + + private static final short[] noise_pattern_Xnoidh = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_REQUIRED, + + E, S, ES, SS }; + + private static final short[] noise_pattern_NXnoidh = { + FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, S, EE, ES }; + + private static final short[] noise_pattern_XXnoidh = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, S, EE, ES, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_KXnoidh = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, FLIP_DIR, E, S, EE, SE, ES }; + + private static final short[] noise_pattern_IKnoidh = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_REQUIRED, + + E, S, ES, SS, FLIP_DIR, E, EE, SE }; + + private static final short[] noise_pattern_IXnoidh = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL, + + E, S, FLIP_DIR, E, S, EE, SE, ES }; + + private static final short[] noise_pattern_NNhfs = { + FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, EE, FF }; + + private static final short[] noise_pattern_NKhfs = { + FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID + | FLAG_REMOTE_REQUIRED, + + E, F, ES, FLIP_DIR, E, F, EE, FF }; + + private static final short[] noise_pattern_NXhfs = { + FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, EE, FF, S, ES }; + + private static final short[] noise_pattern_XNhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, EE, FF, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_XKhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID | FLAG_REMOTE_REQUIRED, + + E, F, ES, FLIP_DIR, E, F, EE, FF, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_XXhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, EE, FF, S, ES, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_KNhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_LOCAL_HYBRID | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, EE, FF, SE }; + + private static final short[] noise_pattern_KKhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC + | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID | FLAG_REMOTE_REQUIRED, + + E, F, ES, SS, FLIP_DIR, E, F, EE, FF, SE }; + + private static final short[] noise_pattern_KXhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC + | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, EE, FF, SE, S, ES }; + + private static final short[] noise_pattern_INhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, S, FLIP_DIR, E, F, EE, FF, SE }; + + private static final short[] noise_pattern_IKhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID | FLAG_REMOTE_REQUIRED, + + E, F, ES, S, SS, FLIP_DIR, E, F, EE, FF, SE }; + + private static final short[] noise_pattern_IXhfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID, + + E, F, S, FLIP_DIR, E, F, EE, FF, SE, S, ES }; + + private static final short[] noise_pattern_XXfallback_hfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_EPHEM_REQ | FLAG_REMOTE_HYBRID | FLAG_REMOTE_HYBRID_REQ, + + E, F, EE, FF, S, SE, FLIP_DIR, S, ES }; + + private static final short[] noise_pattern_NXnoidh_hfs = { + FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, S, EE, FF, ES }; + + private static final short[] noise_pattern_XXnoidh_hfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, S, EE, FF, ES, FLIP_DIR, S, SE }; + + private static final short[] noise_pattern_KXnoidh_hfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_REQUIRED | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC + | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, FLIP_DIR, E, F, S, EE, FF, SE, ES }; + + private static final short[] noise_pattern_IKnoidh_hfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_EPHEMERAL | FLAG_REMOTE_HYBRID, + + E, F, S, ES, SS, FLIP_DIR, E, F, EE, FF, SE }; + + private static final short[] noise_pattern_IXnoidh_hfs = { + FLAG_LOCAL_STATIC | FLAG_LOCAL_EPHEMERAL | FLAG_LOCAL_HYBRID | FLAG_REMOTE_STATIC | FLAG_REMOTE_EPHEMERAL + | FLAG_REMOTE_HYBRID, + + E, F, S, FLIP_DIR, E, F, S, EE, FF, SE, ES }; + + /** + * Look up the description information for a pattern. + * + * @param name The name of the pattern. + * @return The pattern description or null. + */ + public static short[] lookup(String name) { + int pskIndex = pskModifier(name); + name = cutPsk(name); + short[] pattern = get(name); + pattern = insertPsk(pattern, pskIndex); + return pattern; + } + + /** + * Insert psk token if needed + */ + private static short[] insertPsk(short[] pattern, int pskIndex) { + if (pattern != null && pskIndex > -1) { + if (pskIndex != 0) { + int handshake = 0; + int pos = 1; + for (; pos < pattern.length; pos++) { + if (pattern[pos] == FLIP_DIR) { + handshake++; + } + if (handshake == pskIndex) { + break; + } + } + pskIndex = pos - 1; + } + pattern = insertPskTokenAt(pattern, pskIndex); + pattern[0] |= FLAG_PSK; + } + return pattern; + } + + private static short[] insertPskTokenAt(short[] pattern, int pskIndex) { + short[] newPattern = new short[pattern.length + 1]; + for (int pos = 0; pos <= pskIndex; pos++) { + newPattern[pos] = pattern[pos]; + } + newPattern[pskIndex + 1] = PSK; + for (int pos = pskIndex + 1; pos < pattern.length; pos++) { + newPattern[pos + 1] = pattern[pos]; + } + return newPattern; + } + + private static short[] get(String name) { + + if (name.equals("N")) + return noise_pattern_N; + else if (name.equals("K")) + return noise_pattern_K; + else if (name.equals("X")) + return noise_pattern_X; + else if (name.equals("NN")) + return noise_pattern_NN; + else if (name.equals("NK")) + return noise_pattern_NK; + else if (name.equals("NX")) + return noise_pattern_NX; + else if (name.equals("XN")) + return noise_pattern_XN; + else if (name.equals("XK")) + return noise_pattern_XK; + else if (name.equals("XX")) + return noise_pattern_XX; + else if (name.equals("KN")) + return noise_pattern_KN; + else if (name.equals("KK")) + return noise_pattern_KK; + else if (name.equals("KX")) + return noise_pattern_KX; + else if (name.equals("IN")) + return noise_pattern_IN; + else if (name.equals("IK")) + return noise_pattern_IK; + else if (name.equals("IX")) + return noise_pattern_IX; + else if (name.equals("XXfallback")) + return noise_pattern_XXfallback; + else if (name.equals("Xnoidh")) + return noise_pattern_Xnoidh; + else if (name.equals("NXnoidh")) + return noise_pattern_NXnoidh; + else if (name.equals("XXnoidh")) + return noise_pattern_XXnoidh; + else if (name.equals("KXnoidh")) + return noise_pattern_KXnoidh; + else if (name.equals("IKnoidh")) + return noise_pattern_IKnoidh; + else if (name.equals("IXnoidh")) + return noise_pattern_IXnoidh; + else if (name.equals("NNhfs")) + return noise_pattern_NNhfs; + else if (name.equals("NKhfs")) + return noise_pattern_NKhfs; + else if (name.equals("NXhfs")) + return noise_pattern_NXhfs; + else if (name.equals("XNhfs")) + return noise_pattern_XNhfs; + else if (name.equals("XKhfs")) + return noise_pattern_XKhfs; + else if (name.equals("XXhfs")) + return noise_pattern_XXhfs; + else if (name.equals("KNhfs")) + return noise_pattern_KNhfs; + else if (name.equals("KKhfs")) + return noise_pattern_KKhfs; + else if (name.equals("KXhfs")) + return noise_pattern_KXhfs; + else if (name.equals("INhfs")) + return noise_pattern_INhfs; + else if (name.equals("IKhfs")) + return noise_pattern_IKhfs; + else if (name.equals("IXhfs")) + return noise_pattern_IXhfs; + else if (name.equals("XXfallback+hfs")) + return noise_pattern_XXfallback_hfs; + else if (name.equals("NXnoidh+hfs")) + return noise_pattern_NXnoidh_hfs; + else if (name.equals("XXnoidh+hfs")) + return noise_pattern_XXnoidh_hfs; + else if (name.equals("KXnoidh+hfs")) + return noise_pattern_KXnoidh_hfs; + else if (name.equals("IKnoidh+hfs")) + return noise_pattern_IKnoidh_hfs; + else if (name.equals("IXnoidh+hfs")) + return noise_pattern_IXnoidh_hfs; + return null; + } + + private static String cutPsk(String name) { + int pos = name.indexOf("+psk"); + if (pos > 0) { + return name.substring(0, pos) + name.substring(pos + 4); + } + pos = name.indexOf("psk"); + if (pos > 0) { + return name.substring(0, pos) + name.substring(pos + 4); + } + return name; + } + + /* + * determine the psk modifier if used in pattern. + */ + private static int pskModifier(String name) { + int pos = name.indexOf("psk"); + if (pos > -1) { + return Integer.parseInt(name.substring(pos + 3, pos + 4)); + } + return -1; + } + + /** + * Reverses the local and remote flags for a pattern. + * + * @param flags The flags, assuming that the initiator is "local". + * @return The reversed flags, with the responder now being "local". + */ + public static short reverseFlags(short flags) { + return (short) (((flags >> 8) & 0x00FF) | ((flags << 8) & 0xFF00)); + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java new file mode 100644 index 0000000..ef98d52 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.nio.charset.StandardCharsets; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +/** + * Symmetric state for helping manage a Noise handshake. + */ +class SymmetricState implements Destroyable { + + private String name; + private CipherState cipher; + private MessageDigest hash; + private byte[] ck; + private byte[] h; + private byte[] prev_h; + + /** + * Constructs a new symmetric state object. + * + * @param protocolName The name of the Noise protocol, which is assumed to be valid. + * @param cipherName The name of the cipher within protocolName. + * @param hashName The name of the hash within protocolName. + * + * @throws NoSuchAlgorithmException The cipher or hash algorithm in the + * protocol name is not supported. + */ + public SymmetricState(String protocolName, String cipherName, String hashName) throws NoSuchAlgorithmException { + name = protocolName; + cipher = Noise.createCipher(cipherName); + hash = Noise.createHash(hashName); + int hashLength = hash.getDigestLength(); + ck = new byte[hashLength]; + h = new byte[hashLength]; + prev_h = new byte[hashLength]; + + byte[] protocolNameBytes; + protocolNameBytes = protocolName.getBytes(StandardCharsets.UTF_8); + + if (protocolNameBytes.length <= hashLength) { + System.arraycopy(protocolNameBytes, 0, h, 0, protocolNameBytes.length); + Arrays.fill(h, protocolNameBytes.length, h.length, (byte) 0); + } else { + hashOne(protocolNameBytes, 0, protocolNameBytes.length, h, 0, h.length); + } + + System.arraycopy(h, 0, ck, 0, hashLength); + } + + /** + * Gets the name of the Noise protocol. + * + * @return The protocol name. + */ + public String getProtocolName() { + return name; + } + + /** + * Gets the length of MAC values in the current state. + * + * @return The length of the MAC value for the underlying cipher + * or zero if the cipher has not yet been initialized with a key. + */ + public int getMACLength() { + return cipher.getMACLength(); + } + + /** + * Mixes data into the chaining key. + * + * @param data The buffer containing the data to mix in. + * @param offset The offset of the first data byte to mix in. + * @param length The number of bytes to mix in. + */ + public void mixKey(byte[] data, int offset, int length) { + int keyLength = cipher.getKeyLength(); + byte[] tempKey = new byte[keyLength]; + try { + hkdf(ck, 0, ck.length, data, offset, length, ck, 0, ck.length, tempKey, 0, keyLength); + cipher.initializeKey(tempKey, 0); + } finally { + Noise.destroy(tempKey); + } + } + + /** + * Mixes data into the handshake hash. + * + * @param data The buffer containing the data to mix in. + * @param offset The offset of the first data byte to mix in. + * @param length The number of bytes to mix in. + */ + public void mixHash(byte[] data, int offset, int length) { + hashTwo(h, 0, h.length, data, offset, length, h, 0, h.length); + } + + /** + * Mixes a pre-shared key into the chaining key and handshake hash. + * + * @param key The pre-shared key value. + */ + public void mixPreSharedKey(byte[] key) + + { + byte[] temp = new byte[hash.getDigestLength()]; + try { + hkdf(ck, 0, ck.length, key, 0, key.length, ck, 0, ck.length, temp, 0, temp.length); + mixHash(temp, 0, temp.length); + } finally { + Noise.destroy(temp); + } + } + + /** + * Mixes a pre-supplied public key into the handshake hash. + * + * @param dh The object containing the public key. + */ + public void mixPublicKey(DHState dh) { + byte[] temp = new byte[dh.getPublicKeyLength()]; + try { + dh.getPublicKey(temp, 0); + mixHash(temp, 0, temp.length); + } finally { + Noise.destroy(temp); + } + } + + /** + * Mixes data into the chaining key. + * + * @param data The buffer containing the data to mix in. + * @param offset The offset of the first data byte to mix in. + * @param length The number of bytes to mix in. + */ + public void mixKeyAndHash(byte[] data, int offset, int length) { + int keyLength = cipher.getKeyLength(); + byte[] tempKey = new byte[keyLength]; + + int hashLength = hash.getDigestLength(); + byte[] tempHash = new byte[hashLength]; + + try { + hkdf(ck, 0, ck.length, data, offset, length, ck, 0, ck.length, tempHash, 0, hashLength, tempKey, 0, + keyLength); + + mixHash(tempHash, 0, hashLength); + + // Truncate tempKey + if (hashLength == 64 && keyLength > 32) { + byte[] newKey = Noise.copySubArray(tempKey, 0, 32); + Noise.destroy(tempKey); + tempKey = newKey; + } + cipher.initializeKey(tempKey, 0); + } finally { + Noise.destroy(tempKey); + Noise.destroy(tempHash); + } + } + + /** + * Mixes a pre-supplied public key into the chaining key. + * + * @param dh The object containing the public key. + */ + public void mixPublicKeyIntoCK(DHState dh) { + byte[] temp = new byte[dh.getPublicKeyLength()]; + try { + dh.getPublicKey(temp, 0); + mixKey(temp, 0, temp.length); + } finally { + Noise.destroy(temp); + } + } + + /** + * Encrypts a block of plaintext and mixes the ciphertext into the handshake hash. + * + * @param plaintext The buffer containing the plaintext to encrypt. + * @param plaintextOffset The offset within the plaintext buffer of the + * first byte or plaintext data. + * @param ciphertext The buffer to place the ciphertext in. This can + * be the same as the plaintext buffer. + * @param ciphertextOffset The first offset within the ciphertext buffer + * to place the ciphertext and the MAC tag. + * @param length The length of the plaintext. + * @return The length of the ciphertext plus the MAC tag. + * + * @throws ShortBufferException There is not enough space in the + * ciphertext buffer for the encrypted data plus MAC value. + * + * The plaintext and ciphertext buffers can be the same for in-place + * encryption. In that case, plaintextOffset must be identical to + * ciphertextOffset. + * + * There must be enough space in the ciphertext buffer to accomodate + * length + getMACLength() bytes of data starting at ciphertextOffset. + */ + public int encryptAndHash(byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, + int length) throws ShortBufferException { + int ciphertextLength = cipher.encryptWithAd(h, plaintext, plaintextOffset, ciphertext, ciphertextOffset, + length); + mixHash(ciphertext, ciphertextOffset, ciphertextLength); + return ciphertextLength; + } + + /** + * Decrypts a block of ciphertext and mixes it into the handshake hash. + * + * @param ciphertext The buffer containing the ciphertext to decrypt. + * @param ciphertextOffset The offset within the ciphertext buffer of + * the first byte of ciphertext data. + * @param plaintext The buffer to place the plaintext in. This can be + * the same as the ciphertext buffer. + * @param plaintextOffset The first offset within the plaintext buffer + * to place the plaintext. + * @param length The length of the incoming ciphertext plus the MAC tag. + * @return The length of the plaintext with the MAC tag stripped off. + * + * @throws ShortBufferException There is not enough space in the plaintext + * buffer for the decrypted data. + * + * @throws BadPaddingException The MAC value failed to verify. + * + * The plaintext and ciphertext buffers can be the same for in-place + * decryption. In that case, ciphertextOffset must be identical to + * plaintextOffset. + */ + public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, + int length) throws ShortBufferException, BadPaddingException { + System.arraycopy(h, 0, prev_h, 0, h.length); + mixHash(ciphertext, ciphertextOffset, length); + return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + } + + /** + * Splits the symmetric state into two ciphers for session encryption. + * + * @return The pair of ciphers for sending and receiving. + */ + public CipherStatePair split() { + return split(new byte[0], 0, 0); + } + + /** + * Splits the symmetric state into two ciphers for session encryption, + * and optionally mixes in a secondary symmetric key. + * + * @param secondaryKey The buffer containing the secondary key. + * @param offset The offset of the first secondary key byte. + * @param length The length of the secondary key in bytes, which + * must be either 0 or 32. + * @return The pair of ciphers for sending and receiving. + * + * @throws IllegalArgumentException The length is not 0 or 32. + */ + public CipherStatePair split(byte[] secondaryKey, int offset, int length) { + if (length != 0 && length != 32) + throw new IllegalArgumentException("Secondary keys must be 0 or 32 bytes in length"); + int keyLength = cipher.getKeyLength(); + byte[] k1 = new byte[keyLength]; + byte[] k2 = new byte[keyLength]; + try { + hkdf(ck, 0, ck.length, secondaryKey, offset, length, k1, 0, k1.length, k2, 0, k2.length); + CipherState c1 = null; + CipherState c2 = null; + CipherStatePair pair = null; + try { + c1 = cipher.fork(k1, 0); + c2 = cipher.fork(k2, 0); + pair = new CipherStatePair(c1, c2); + } finally { + if (c1 == null || c2 == null || pair == null) { + // Could not create some of the objects. Clean up the others + // to avoid accidental leakage of k1 or k2. + if (c1 != null) + c1.destroy(); + if (c2 != null) + c2.destroy(); + pair = null; + } + } + return pair; + } finally { + Noise.destroy(k1); + Noise.destroy(k2); + } + } + + /** + * Gets the current value of the handshake hash. + * + * @return The handshake hash. This must not be modified by the caller. + * + * The handshake hash value is only of use to the application after + * split() has been called. + */ + public byte[] getHandshakeHash() { + return h; + } + + @Override + public void destroy() { + if (cipher != null) { + cipher.destroy(); + cipher = null; + } + if (hash != null) { + // The built-in fallback implementations are destroyable. + // JCA/JCE implementations aren't, so try reset() instead. + if (hash instanceof Destroyable) + ((Destroyable) hash).destroy(); + else + hash.reset(); + hash = null; + } + if (ck != null) { + Noise.destroy(ck); + ck = null; + } + if (h != null) { + Noise.destroy(h); + h = null; + } + if (prev_h != null) { + Noise.destroy(prev_h); + prev_h = null; + } + } + + /** + * Hashes a single data buffer. + * + * @param data The buffer containing the data to hash. + * @param offset Offset into the data buffer of the first byte to hash. + * @param length Length of the data to be hashed. + * @param output The buffer to receive the output hash value. + * @param outputOffset Offset into the output buffer to place the hash value. + * @param outputLength The length of the hash output. + * + * The output buffer can be the same as the input data buffer. + */ + private void hashOne(byte[] data, int offset, int length, byte[] output, int outputOffset, int outputLength) { + hash.reset(); + hash.update(data, offset, length); + try { + hash.digest(output, outputOffset, outputLength); + } catch (DigestException e) { + Arrays.fill(output, outputOffset, outputLength, (byte) 0); + } + } + + /** + * Hashes two data buffers. + * + * @param data1 The buffer containing the first data to hash. + * @param offset1 Offset into the first data buffer of the first byte to hash. + * @param length1 Length of the first data to be hashed. + * @param data2 The buffer containing the second data to hash. + * @param offset2 Offset into the second data buffer of the first byte to hash. + * @param length2 Length of the second data to be hashed. + * @param output The buffer to receive the output hash value. + * @param outputOffset Offset into the output buffer to place the hash value. + * @param outputLength The length of the hash output. + * + * The output buffer can be same as either of the input buffers. + */ + private void hashTwo(byte[] data1, int offset1, int length1, byte[] data2, int offset2, int length2, byte[] output, + int outputOffset, int outputLength) { + hash.reset(); + hash.update(data1, offset1, length1); + hash.update(data2, offset2, length2); + try { + hash.digest(output, outputOffset, outputLength); + } catch (DigestException e) { + Arrays.fill(output, outputOffset, outputLength, (byte) 0); + } + } + + /** + * Computes a HMAC value using key and data values. + * + * @param key The buffer that contains the key. + * @param keyOffset The offset of the key in the key buffer. + * @param keyLength The length of the key in bytes. + * @param data The buffer that contains the data. + * @param dataOffset The offset of the data in the data buffer. + * @param dataLength The length of the data in bytes. + * @param output The output buffer to place the HMAC value in. + * @param outputOffset Offset into the output buffer for the HMAC value. + * @param outputLength The length of the HMAC output. + */ + private void hmac(byte[] key, int keyOffset, int keyLength, byte[] data, int dataOffset, int dataLength, + byte[] output, int outputOffset, int outputLength) { + // In all of the algorithms of interest to us, the block length + // is twice the size of the hash length. + int hashLength = hash.getDigestLength(); + int blockLength = hashLength * 2; + byte[] block = new byte[blockLength]; + int index; + try { + if (keyLength <= blockLength) { + System.arraycopy(key, keyOffset, block, 0, keyLength); + Arrays.fill(block, keyLength, blockLength, (byte) 0); + } else { + hash.reset(); + hash.update(key, keyOffset, keyLength); + hash.digest(block, 0, hashLength); + Arrays.fill(block, hashLength, blockLength, (byte) 0); + } + for (index = 0; index < blockLength; ++index) + block[index] ^= (byte) 0x36; + hash.reset(); + hash.update(block, 0, blockLength); + hash.update(data, dataOffset, dataLength); + hash.digest(output, outputOffset, hashLength); + for (index = 0; index < blockLength; ++index) + block[index] ^= (byte) (0x36 ^ 0x5C); + hash.reset(); + hash.update(block, 0, blockLength); + hash.update(output, outputOffset, hashLength); + hash.digest(output, outputOffset, outputLength); + } catch (DigestException e) { + Arrays.fill(output, outputOffset, outputLength, (byte) 0); + } finally { + Noise.destroy(block); + } + } + + /** + * Computes a HKDF value. + * + * @param key The buffer that contains the key. + * @param keyOffset The offset of the key in the key buffer. + * @param keyLength The length of the key in bytes. + * @param data The buffer that contains the data. + * @param dataOffset The offset of the data in the data buffer. + * @param dataLength The length of the data in bytes. + * @param output1 The first output buffer. + * @param output1Offset Offset into the first output buffer. + * @param output1Length Length of the first output which can be + * less than the hash length. + * @param output2 The second output buffer. + * @param output2Offset Offset into the second output buffer. + * @param output2Length Length of the second output which can be + * less than the hash length. + */ + private void hkdf(byte[] key, int keyOffset, int keyLength, byte[] data, int dataOffset, int dataLength, + byte[] output1, int output1Offset, int output1Length, byte[] output2, int output2Offset, + int output2Length) { + hkdf(key, keyOffset, keyLength, data, dataOffset, dataLength, output1, output1Offset, output1Length, output2, + output2Offset, output2Length, null, 0, 0); + } + + private void hkdf(byte[] key, int keyOffset, int keyLength, byte[] data, int dataOffset, int dataLength, + byte[] output1, int output1Offset, int output1Length, byte[] output2, int output2Offset, int output2Length, + byte[] output3, int output3Offset, int output3Length) { + int hashLength = hash.getDigestLength(); + byte[] tempKey = new byte[hashLength]; + byte[] tempHash = new byte[hashLength + 1]; + try { + hmac(key, keyOffset, keyLength, data, dataOffset, dataLength, tempKey, 0, hashLength); + tempHash[0] = (byte) 0x01; + hmac(tempKey, 0, hashLength, tempHash, 0, 1, tempHash, 0, hashLength); + System.arraycopy(tempHash, 0, output1, output1Offset, output1Length); + tempHash[hashLength] = (byte) 0x02; + hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0, hashLength); + System.arraycopy(tempHash, 0, output2, output2Offset, output2Length); + if (output3 != null) { + tempHash[hashLength] = (byte) 0x03; + hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0, hashLength); + System.arraycopy(tempHash, 0, output3, output3Offset, output3Length); + } + } finally { + Noise.destroy(tempKey); + Noise.destroy(tempHash); + } + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/package-info.java b/src/main/java/com/southernstorm/noise/protocol/package-info.java new file mode 100644 index 0000000..ef85f00 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/package-info.java @@ -0,0 +1,7 @@ + +/** + * Provides classes for communicating via the Noise protocol. + * + * Reference: http://noiseprotocol.org + */ +package com.southernstorm.noise.protocol; diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/ClimateEnumHelper.java b/src/main/java/no/seime/openhab/binding/esphome/internal/ClimateEnumHelper.java deleted file mode 100644 index 9a7b276..0000000 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/ClimateEnumHelper.java +++ /dev/null @@ -1,44 +0,0 @@ -package no.seime.openhab.binding.esphome.internal; - -import io.esphome.api.ClimateFanMode; -import io.esphome.api.ClimateMode; -import io.esphome.api.ClimatePreset; -import io.esphome.api.ClimateSwingMode; - -public class ClimateEnumHelper { - public static String stripEnumPrefix(ClimateSwingMode mode) { - String toRemove = "CLIMATE_SWING"; - return mode.toString().substring(toRemove.length() + 1); - } - - public static String stripEnumPrefix(ClimateFanMode mode) { - String toRemove = "CLIMATE_FAN"; - return mode.toString().substring(toRemove.length() + 1); - } - - public static String stripEnumPrefix(ClimateMode climateMode) { - String toRemove = "CLIMATE_MODE"; - return climateMode.toString().substring(toRemove.length() + 1); - } - - public static String stripEnumPrefix(ClimatePreset climatePreset) { - String toRemove = "CLIMATE_PRESET"; - return climatePreset.toString().substring(toRemove.length() + 1); - } - - public static ClimateFanMode toFanMode(String fanMode) { - return ClimateFanMode.valueOf("CLIMATE_FAN_" + fanMode.toUpperCase()); - } - - public static ClimatePreset toClimatePreset(String climatePreset) { - return ClimatePreset.valueOf("CLIMATE_PRESET_" + climatePreset.toUpperCase()); - } - - public static ClimateMode toClimateMode(String mode) { - return ClimateMode.valueOf("CLIMATE_MODE_" + mode.toUpperCase()); - } - - public static ClimateSwingMode toClimateSwingMode(String mode) { - return ClimateSwingMode.valueOf("CLIMATE_SWING_" + mode.toUpperCase()); - } -} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/PacketListener.java b/src/main/java/no/seime/openhab/binding/esphome/internal/CommunicationListener.java similarity index 78% rename from src/main/java/no/seime/openhab/binding/esphome/internal/PacketListener.java rename to src/main/java/no/seime/openhab/binding/esphome/internal/CommunicationListener.java index 6f15f46..705ca6e 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/PacketListener.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/CommunicationListener.java @@ -18,14 +18,17 @@ import com.google.protobuf.GeneratedMessageV3; +import no.seime.openhab.binding.esphome.internal.comm.CommunicationError; import no.seime.openhab.binding.esphome.internal.comm.ProtocolAPIError; @NonNullByDefault -public interface PacketListener { +public interface CommunicationListener { void onPacket(GeneratedMessageV3 message) throws ProtocolAPIError, IOException; void onEndOfStream(); - void onParseError(); + void onParseError(CommunicationError error); + + void onConnect() throws ProtocolAPIError; } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java b/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java index a991c10..5952862 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java @@ -31,4 +31,12 @@ public class ESPHomeConfiguration { public int pingInterval = 10; public int maxPingTimeouts = 4; + + @Nullable + public String encryptionKey; + @Nullable + public String server; + + @Nullable + public String logPrefix; } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/AbstractFrameHelper.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/AbstractFrameHelper.java new file mode 100644 index 0000000..3c0618b --- /dev/null +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/AbstractFrameHelper.java @@ -0,0 +1,125 @@ +package no.seime.openhab.binding.esphome.internal.comm; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.protobuf.GeneratedMessageV3; + +import no.seime.openhab.binding.esphome.internal.CommunicationListener; + +public abstract class AbstractFrameHelper { + + public static final int PROTOCOL_PLAINTEXT = 0x00; + public static final int PROTOCOL_ENCRYPTED = 0x01; + + protected final Logger logger = LoggerFactory.getLogger(AbstractFrameHelper.class); + private final MessageTypeToClassConverter messageTypeToClassConverter = new MessageTypeToClassConverter(); + protected CommunicationListener listener; + protected ByteBuffer buffer = ByteBuffer.allocate(1024); + protected ESPHomeConnection connection; + + protected static byte[] concatArrays(byte[] length, byte[] additionalLength) { + byte[] result = new byte[length.length + additionalLength.length]; + System.arraycopy(length, 0, result, 0, length.length); + System.arraycopy(additionalLength, 0, result, length.length, additionalLength.length); + return result; + } + + /** + * Initiate connection to the ESPHome device. When connection is ready for use, call onConnect on the listener. + * + * @param espHomeAddress + * @throws ProtocolException + */ + public abstract void connect(InetSocketAddress espHomeAddress) throws ProtocolException; + + /** + * Encode the given message as a ready to send frame + * + * @param message message to encode + * @return byte buffer with the encoded frame + * @throws ProtocolAPIError + */ + public abstract ByteBuffer encodeFrame(GeneratedMessageV3 message) throws ProtocolAPIError; + + public void setPacketListener(CommunicationListener listener) { + this.listener = listener; + } + + public void close() { + connection.close(); + } + + protected void processBuffer() throws ProtocolException { + buffer.limit(buffer.position()); + buffer.position(0); + if (buffer.remaining() > 2) { + byte[] headerData = readBytes(3); + headerReceived(headerData); + } else { + buffer.position(buffer.limit()); + buffer.limit(buffer.capacity()); + } + } + + /** + * Called when the header of a new packet has been received + * + * @param headerData first 3 bytes of the available data + * @throws ProtocolException + */ + protected abstract void headerReceived(byte[] headerData) throws ProtocolException; + + protected byte[] readBytes(int numBytes) { + if (buffer.remaining() < numBytes) { + return new byte[0]; + } + byte[] data = new byte[numBytes]; + buffer.get(data); + return data; + } + + protected void decodeProtoMessage(int messageType, byte[] bytes) { + logger.debug("Received packet of type {} with data {}", messageType, bytes); + + try { + Method parseMethod = messageTypeToClassConverter.getMethod(messageType); + if (parseMethod != null) { + GeneratedMessageV3 invoke = (GeneratedMessageV3) parseMethod.invoke(null, bytes); + if (invoke != null) { + listener.onPacket(invoke); + } else { + logger.warn("Received null packet of type {}", parseMethod); + } + } + } catch (ProtocolAPIError | IllegalAccessException | InvocationTargetException | IOException e) { + logger.warn("Error parsing packet", e); + listener.onParseError(CommunicationError.PACKET_ERROR); + } + } + + public void processReceivedData(ByteBuffer newDataBuffer) throws ProtocolException, IOException { + // Copy new data into buffer + newDataBuffer.flip(); + buffer.put(newDataBuffer); + processBuffer(); + } + + public void endOfStream() { + listener.onEndOfStream(); + } + + public void onParseError(CommunicationError error) { + listener.onParseError(error); + } + + public void send(GeneratedMessageV3 message) throws ProtocolAPIError { + connection.send(encodeFrame(message)); + } +} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/CommunicationError.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/CommunicationError.java new file mode 100644 index 0000000..2d7d6a3 --- /dev/null +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/CommunicationError.java @@ -0,0 +1,22 @@ +package no.seime.openhab.binding.esphome.internal.comm; + +public enum CommunicationError { + DEVICE_REQUIRES_ENCRYPTION("Device is configured with encrypted api endpoint, but binding isn't using encryption."), + DEVICE_REQUIRES_PLAINTEXT("Device is configured with plaintext api endpoint, but binding is using encryption."), + PACKET_ERROR("Error parsing packet"), + ENCRYPTION_KEY_INVALID("Invalid api encryption key"), + INVALID_PROTOCOL_PREAMBLE( + "Invalid protocol preamble - this indicates a new major protocol change has arrived, but this binding does not support it yet"), + DEVICE_NAME_MISMATCH("ESPHome device reported a different name than configured for the thing"); + + private final String text; + + CommunicationError(String text) { + + this.text = text; + } + + public String toString() { + return name() + ": " + text; + } +} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ConnectionSelector.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ConnectionSelector.java index 9fe7f3f..596215f 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ConnectionSelector.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ConnectionSelector.java @@ -18,10 +18,9 @@ public class ConnectionSelector { private final Logger logger = LoggerFactory.getLogger(ConnectionSelector.class); - private Selector selector; - + private final Selector selector; + private final Map connectionMap = new ConcurrentHashMap<>(); private boolean keepRunning = true; - private boolean selectorOpen; public ConnectionSelector() throws IOException { @@ -29,8 +28,6 @@ public ConnectionSelector() throws IOException { selectorOpen = true; } - private Map connectionMap = new ConcurrentHashMap<>(); - public void start() { Thread selectorThread = new Thread(() -> { @@ -63,7 +60,7 @@ public void start() { } private void processKey(SelectionKey readyKey) { - StreamHandler streamHandler = (StreamHandler) readyKey.attachment(); + AbstractFrameHelper frameHelper = (AbstractFrameHelper) readyKey.attachment(); logger.trace("Processing key {}", readyKey); // Tests whether this key's channel is ready to accept a new socket connection try { @@ -72,9 +69,9 @@ private void processKey(SelectionKey readyKey) { ByteBuffer buffer = ByteBuffer.allocate(128); int read = channel.read(buffer); if (read == -1) { - streamHandler.endOfStream(); + frameHelper.endOfStream(); } else { - processReceivedData(streamHandler, buffer, channel); + processReceivedData(frameHelper, buffer, channel); } } else { @@ -82,18 +79,18 @@ private void processKey(SelectionKey readyKey) { } } catch (IOException e) { logger.debug("Socket exception", e); - streamHandler.endOfStream(); + frameHelper.endOfStream(); } } - private void processReceivedData(StreamHandler streamHandler, ByteBuffer buffer, SocketChannel channel) + private void processReceivedData(AbstractFrameHelper frameHelper, ByteBuffer buffer, SocketChannel channel) throws IOException { try { logger.trace("Received data"); - streamHandler.processReceivedData(buffer); + frameHelper.processReceivedData(buffer); } catch (Exception e) { channel.close(); - streamHandler.onParseError(e); + frameHelper.onParseError(CommunicationError.PACKET_ERROR); } } @@ -110,11 +107,11 @@ public void stop() { } } - public void register(SocketChannel socketChannel, StreamHandler packetStreamReader) { - connectionMap.put(socketChannel, packetStreamReader); + public void register(SocketChannel socketChannel, AbstractFrameHelper frameHelper) { + connectionMap.put(socketChannel, frameHelper); try { SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ); - key.attach(packetStreamReader); + key.attach(frameHelper); selector.wakeup(); } catch (IOException e) { logger.warn("Error while registering channel", e); diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ESPHomeConnection.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ESPHomeConnection.java index e65a23f..b644541 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ESPHomeConnection.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/ESPHomeConnection.java @@ -20,56 +20,54 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.protobuf.GeneratedMessageV3; - public class ESPHomeConnection { private final Logger logger = LoggerFactory.getLogger(ESPHomeConnection.class); private SocketChannel socketChannel; - private StreamHandler streamHandler; + private final AbstractFrameHelper frameHelper; private final ConnectionSelector connectionSelector; - private String hostname; + private final String logPrefix; - public ESPHomeConnection(ConnectionSelector connectionSelector, PlainTextStreamHandler streamHandler, - String hostname) { - this.streamHandler = streamHandler; + public ESPHomeConnection(ConnectionSelector connectionSelector, AbstractFrameHelper frameHelper, String logPrefix) { + this.frameHelper = frameHelper; this.connectionSelector = connectionSelector; - this.hostname = hostname; + this.logPrefix = logPrefix; } - public synchronized void send(GeneratedMessageV3 message) throws ProtocolAPIError { + public synchronized void send(ByteBuffer buffer) throws ProtocolAPIError { try { - logger.debug("[{}] Sending message: {}", hostname, message.getClass().getSimpleName()); - byte[] serializedMessage = streamHandler.encodeFrame(message); - ByteBuffer buffer = ByteBuffer.wrap(serializedMessage); while (buffer.hasRemaining()) { - logger.trace("Writing data"); + logger.trace("[{}] Writing data", logPrefix); socketChannel.write(buffer); } + } catch (IOException e) { - throw new ProtocolAPIError(String.format("[%s] Error sending message: %s ", hostname, e)); + throw new ProtocolAPIError(String.format("[%s] Error sending message: %s ", logPrefix, e)); } } - public void connect(InetSocketAddress address) throws ProtocolAPIError { + public void connect(InetSocketAddress espDeviceAddress) throws ProtocolAPIError { try { - socketChannel = SocketChannel.open(address); + socketChannel = SocketChannel.open(espDeviceAddress); socketChannel.configureBlocking(false); - connectionSelector.register(socketChannel, streamHandler); + connectionSelector.register(socketChannel, frameHelper); - logger.info("[{}] Opening socket to {} at port {}.", hostname, hostname, address.getPort()); + logger.info("[{}] Opening socket to {} at port {}.", logPrefix, espDeviceAddress.getHostName(), + espDeviceAddress.getPort()); } catch (Exception e) { - throw new ProtocolAPIError("Failed to connect to '" + hostname + "' port " + address.getPort(), e); + throw new ProtocolAPIError( + "Failed to connect to '" + espDeviceAddress.getHostName() + "' port " + espDeviceAddress.getPort(), + e); } } public void close() { - logger.info("[{}] Disconnecting socket.", hostname); + logger.info("[{}] Disconnecting socket.", logPrefix); try { if (socketChannel != null) { connectionSelector.unregister(socketChannel); @@ -77,7 +75,7 @@ public void close() { socketChannel = null; } } catch (IOException e) { - logger.debug("[{}] Error closing connection", hostname, e); + logger.debug("[{}] Error closing connection", logPrefix, e); } } } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/EncryptedFrameHelper.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/EncryptedFrameHelper.java new file mode 100644 index 0000000..1682e79 --- /dev/null +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/EncryptedFrameHelper.java @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2023 Contributors to the Seime Openhab Addons project + *

+ * See the NOTICE file(s) distributed with this work for additional + * information. + *

+ * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + *

+ * SPDX-License-Identifier: EPL-2.0 + */ +package no.seime.openhab.binding.esphome.internal.comm; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Base64; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +import org.eclipse.jdt.annotation.Nullable; + +import com.google.protobuf.GeneratedMessageV3; +import com.southernstorm.noise.protocol.CipherStatePair; +import com.southernstorm.noise.protocol.HandshakeState; + +import io.esphome.api.ApiOptions; +import no.seime.openhab.binding.esphome.internal.CommunicationListener; + +public class EncryptedFrameHelper extends AbstractFrameHelper { + private final static String NOISE_PROTOCOL = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; + private final String encryptionKeyBase64; + private final String expectedServername; + private HandshakeState client; + private CipherStatePair cipherStatePair; + private NoiseProtocolState state; + + public EncryptedFrameHelper(ConnectionSelector connectionSelector, CommunicationListener listener, + String encryptionKeyBase64, @Nullable String expectedServername, String logPrefix) { + this.encryptionKeyBase64 = encryptionKeyBase64; + this.expectedServername = expectedServername; + this.listener = listener; + + connection = new ESPHomeConnection(connectionSelector, this, logPrefix); + } + + @Override + public void connect(InetSocketAddress espHomeAddress) throws ProtocolException { + try { + client = new HandshakeState(NOISE_PROTOCOL, HandshakeState.INITIATOR); + + // Set preshared key + byte[] key = Base64.getDecoder().decode(encryptionKeyBase64); + assert key.length == 32; + client.setPreSharedKey(key, 0, key.length); + + // Set prologue + byte[] prologue = "NoiseAPIInit".getBytes(StandardCharsets.US_ASCII); + byte[] prologuePadded = new byte[prologue.length + 2]; // 2 nulls at the end + System.arraycopy(prologue, 0, prologuePadded, 0, prologue.length); + client.setPrologue(prologuePadded, 0, prologuePadded.length); + + client.start(); + + connection.connect(espHomeAddress); + state = NoiseProtocolState.HELLO; + + connection.send(createFrame(new byte[0])); + + } catch (NoSuchAlgorithmException e) { + throw new ProtocolAPIError("Error initializing encryption", e); + } + } + + protected void headerReceived(byte[] headerData) throws ProtocolException { + try { + if (headerData[0] != PROTOCOL_ENCRYPTED) { + if (headerData[0] == PROTOCOL_PLAINTEXT) { + listener.onParseError(CommunicationError.DEVICE_REQUIRES_PLAINTEXT); + return; + } else { + listener.onParseError(CommunicationError.INVALID_PROTOCOL_PREAMBLE); + return; + } + } + + // Unwrap outer frame + int protoPacketLength = bytesToShort(Arrays.copyOfRange(headerData, 1, 3)); + if (buffer.remaining() >= protoPacketLength) { + byte[] packetData = readBytes(protoPacketLength); + + switch (state) { + case HELLO: + handleHello(packetData); + break; + case HANDSHAKE: + handleHandshake(packetData); + break; + case READY: + handleReady(packetData); + break; + } + + buffer.compact(); + processBuffer(); + } else { + buffer.position(buffer.limit()); + buffer.limit(buffer.capacity()); + } + } catch (ShortBufferException e) { + throw new ProtocolAPIError(e.getMessage()); + } + } + + private void handleHello(byte[] packetData) throws ProtocolAPIError, ShortBufferException { + if (packetData[0] != PROTOCOL_ENCRYPTED) { + listener.onParseError(CommunicationError.DEVICE_REQUIRES_PLAINTEXT); + } else { + // Verify server name + byte[] serverName = Arrays.copyOfRange(packetData, 1, packetData.length - 1); + String server = new String(serverName, StandardCharsets.US_ASCII); + + if (expectedServername != null && !server.equals(expectedServername)) { + listener.onParseError(CommunicationError.DEVICE_NAME_MISMATCH); + return; + } + + final byte[] noiseHandshakeBuffer = new byte[64]; + final int noiseHandshakeLength; + + // Client handshake written to buffer + noiseHandshakeLength = client.writeMessage(noiseHandshakeBuffer, 0, new byte[0], 0, 0); + + // Prepend with empty byte in array + byte[] payload = new byte[noiseHandshakeLength + 1]; + System.arraycopy(noiseHandshakeBuffer, 0, payload, 1, noiseHandshakeLength); + + ByteBuffer frame = createFrame(payload); + state = NoiseProtocolState.HANDSHAKE; + connection.send(frame); + } + } + + private ByteBuffer createFrame(byte[] payload) { + int frameLength = payload.length; + ByteBuffer buffer = ByteBuffer.allocate(frameLength + 3); + buffer.put((byte) 1); + buffer.putShort((short) frameLength); + buffer.put(payload); + buffer.flip(); + + return buffer; + } + + private void handleHandshake(byte[] packetData) throws ProtocolException { + if (packetData[0] != 0) { + byte[] explanation = Arrays.copyOfRange(packetData, 1, packetData.length); + listener.onParseError(CommunicationError.ENCRYPTION_KEY_INVALID); + return; + } else { + try { + byte[] handshakeRsp = Arrays.copyOfRange(packetData, 1, packetData.length); + byte[] payload = new byte[64]; + client.readMessage(handshakeRsp, 0, handshakeRsp.length, payload, 0); + + cipherStatePair = client.split(); + state = NoiseProtocolState.READY; + listener.onConnect(); + } catch (ShortBufferException | BadPaddingException e) { + throw new ProtocolAPIError(e.getMessage()); + } + } + } + + private void handleReady(byte[] packetData) { + try { + byte[] decrypted = decryptPacket(packetData); + int messageType = (decrypted[0] << 8) | decrypted[1]; + byte[] messageData = Arrays.copyOfRange(decrypted, 4, decrypted.length); + decodeProtoMessage(messageType, messageData); + } catch (Exception e) { + listener.onParseError(CommunicationError.PACKET_ERROR); + } + } + + public ByteBuffer encodeFrame(GeneratedMessageV3 message) throws ProtocolAPIError { + try { + byte[] protoBytes = message.toByteArray(); + int type = message.getDescriptorForType().getOptions().getExtension(ApiOptions.id); + byte[] typeAndLength = new byte[] { (byte) (type >> 8 & 0xFF), (byte) (type & 0xFF), + (byte) (protoBytes.length >> 8 & 0xFF), (byte) (protoBytes.length & 0xFF) }; + byte[] frameUnencrypted = concatArrays(typeAndLength, protoBytes); + byte[] frameEncrypted = encryptPacket(frameUnencrypted); + + return createFrame(frameEncrypted); + + } catch (Exception e) { + throw new ProtocolAPIError(e.getMessage()); + } + } + + private byte[] encryptPacket(byte[] msg) throws Exception { + byte[] encrypted = new byte[msg.length + 128]; + final int cipherTextLength = cipherStatePair.getSender().encryptWithAd(null, msg, 0, encrypted, 0, msg.length); + + byte[] result = new byte[cipherTextLength]; + System.arraycopy(encrypted, 0, result, 0, cipherTextLength); + return result; + } + + private byte[] decryptPacket(byte[] msg) throws Exception { + byte[] decrypted = new byte[msg.length + 128]; + final int cipherTextLength = cipherStatePair.getReceiver().decryptWithAd(null, msg, 0, decrypted, 0, + msg.length); + + byte[] result = new byte[cipherTextLength]; + System.arraycopy(decrypted, 0, result, 0, cipherTextLength); + return result; + } + + private static short bytesToShort(final byte[] data) { + short value = (short) (data[0] & 0xff); + value <<= 8; + value |= data[1] & 0xff; + return value; + } + + private enum NoiseProtocolState { + HELLO, + HANDSHAKE, + READY + } +} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextStreamHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextFrameHelper.java similarity index 52% rename from src/main/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextStreamHandler.java rename to src/main/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextFrameHelper.java index bb108a9..25caab9 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextStreamHandler.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextFrameHelper.java @@ -14,57 +14,39 @@ import static no.seime.openhab.binding.esphome.internal.comm.VarIntConverter.bytesToInt; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.Arrays; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.protobuf.GeneratedMessageV3; -import no.seime.openhab.binding.esphome.internal.PacketListener; - -public class PlainTextStreamHandler implements StreamHandler { - - public static final int PREAMBLE = 0x00; - public static final int ENCRYPTION_REQUIRED = 0x01; - public static final int VAR_INT_MARKER = 0x80; - private final Logger logger = LoggerFactory.getLogger(PlainTextStreamHandler.class); - - private final PacketListener listener; +import no.seime.openhab.binding.esphome.internal.CommunicationListener; - private ByteBuffer buffer = ByteBuffer.allocate(1024); +public class PlainTextFrameHelper extends AbstractFrameHelper { - private MessageTypeToClassConverter messageTypeToClassConverter = new MessageTypeToClassConverter(); + private static final int VAR_INT_MARKER = 0x80; - public PlainTextStreamHandler(PacketListener listener) { + public PlainTextFrameHelper(ConnectionSelector connectionSelector, CommunicationListener listener, + String logPrefix) { this.listener = listener; + connection = new ESPHomeConnection(connectionSelector, this, logPrefix); } - private void processBuffer() throws ProtocolException { - buffer.limit(buffer.position()); - buffer.position(0); - if (buffer.remaining() > 2) { - byte[] headerData = readBytes(3); - headerReceived(headerData); - } else { - buffer.position(buffer.limit()); - buffer.limit(buffer.capacity()); - } + @Override + public void connect(InetSocketAddress espHomeAddress) throws ProtocolException { + connection.connect(espHomeAddress); + listener.onConnect(); } - public void headerReceived(byte[] headerData) throws ProtocolException { - if (headerData[0] != PREAMBLE) { - if (headerData[0] == ENCRYPTION_REQUIRED) { - handleAndClose(new RequiresEncryptionAPIError("Connection requires encryption")); + protected void headerReceived(byte[] headerData) throws ProtocolException { + if (headerData[0] != PROTOCOL_PLAINTEXT) { + if (headerData[0] == PROTOCOL_ENCRYPTED) { + listener.onParseError(CommunicationError.DEVICE_REQUIRES_ENCRYPTION); + return; + } else { + listener.onParseError(CommunicationError.INVALID_PROTOCOL_PREAMBLE); return; } - - handleAndClose(new ProtocolAPIError(String.format("Invalid preamble %02x", headerData[0]))); - return; } byte[] encodedProtoPacketLenghtBuffer; @@ -127,58 +109,7 @@ public void headerReceived(byte[] headerData) throws ProtocolException { } } - private byte[] readBytes(int numBytes) { - if (buffer.remaining() < numBytes) { - return new byte[0]; - } - byte[] data = new byte[numBytes]; - buffer.get(data); - return data; - } - - private void decodeProtoMessage(int messageType, byte[] bytes) { - logger.debug("Received packet of type {} with data {}", messageType, bytes); - - try { - Method parseMethod = messageTypeToClassConverter.getMethod(messageType); - if (parseMethod != null) { - GeneratedMessageV3 invoke = (GeneratedMessageV3) parseMethod.invoke(null, bytes); - if (invoke != null) { - listener.onPacket(invoke); - } else { - logger.warn("Received null packet of type {}", parseMethod); - } - } - } catch (ProtocolAPIError | IllegalAccessException | InvocationTargetException | IOException e) { - logger.warn("Error parsing packet", e); - listener.onParseError(); - } - } - - private byte[] concatArrays(byte[] length, byte[] additionalLength) { - byte[] result = new byte[length.length + additionalLength.length]; - System.arraycopy(length, PREAMBLE, result, PREAMBLE, length.length); - System.arraycopy(additionalLength, PREAMBLE, result, length.length, additionalLength.length); - return result; - } - - private void handleAndClose(ProtocolException connectionRequiresEncryption) throws ProtocolException { - // close socket - listener.onParseError(); - throw connectionRequiresEncryption; - } - - public void processReceivedData(ByteBuffer buffer) throws ProtocolException, IOException { - // Copy new data into buffer - byte[] newData = new byte[buffer.position()]; - buffer.flip(); - buffer.get(newData); - this.buffer.put(newData, 0, newData.length); - - processBuffer(); - } - - public byte[] encodeFrame(GeneratedMessageV3 message) { + public ByteBuffer encodeFrame(GeneratedMessageV3 message) { byte[] protoBytes = message.toByteArray(); byte[] idVarUint = VarIntConverter .intToBytes(message.getDescriptorForType().getOptions().getExtension(io.esphome.api.ApiOptions.id)); @@ -189,17 +120,6 @@ public byte[] encodeFrame(GeneratedMessageV3 message) { System.arraycopy(idVarUint, 0, frame, protoBytesLengthVarUint.length + 1, idVarUint.length); System.arraycopy(protoBytes, 0, frame, idVarUint.length + protoBytesLengthVarUint.length + 1, protoBytes.length); - return frame; - } - - @Override - public void endOfStream() { - listener.onEndOfStream(); - } - - @Override - public void onParseError(Exception e) { - logger.error("Error parsing packet", e); - listener.onParseError(); + return ByteBuffer.wrap(frame); } } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/RequiresEncryptionAPIError.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/RequiresEncryptionAPIError.java deleted file mode 100644 index cffeb64..0000000 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/RequiresEncryptionAPIError.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2023 Contributors to the Seime Openhab Addons project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package no.seime.openhab.binding.esphome.internal.comm; - -public class RequiresEncryptionAPIError extends ProtocolException { - public RequiresEncryptionAPIError(String message) { - super(message); - } -} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/StreamHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/comm/StreamHandler.java deleted file mode 100644 index 025b573..0000000 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/comm/StreamHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package no.seime.openhab.binding.esphome.internal.comm; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import com.google.protobuf.GeneratedMessageV3; - -public interface StreamHandler { - void processReceivedData(ByteBuffer buffer) throws ProtocolException, IOException; - - byte[] encodeFrame(GeneratedMessageV3 message); - - void endOfStream(); - - void onParseError(Exception e); -} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java index 143d776..4f7560b 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java @@ -15,22 +15,14 @@ import java.math.BigDecimal; import java.net.InetSocketAddress; import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.*; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.type.ChannelType; import org.openhab.core.types.Command; @@ -44,24 +36,10 @@ import io.esphome.api.*; import no.seime.openhab.binding.esphome.internal.BindingConstants; +import no.seime.openhab.binding.esphome.internal.CommunicationListener; import no.seime.openhab.binding.esphome.internal.ESPHomeConfiguration; -import no.seime.openhab.binding.esphome.internal.PacketListener; -import no.seime.openhab.binding.esphome.internal.comm.ConnectionSelector; -import no.seime.openhab.binding.esphome.internal.comm.ESPHomeConnection; -import no.seime.openhab.binding.esphome.internal.comm.PlainTextStreamHandler; -import no.seime.openhab.binding.esphome.internal.comm.ProtocolAPIError; -import no.seime.openhab.binding.esphome.internal.comm.ProtocolException; -import no.seime.openhab.binding.esphome.internal.message.AbstractMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.BinarySensorMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.ButtonMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.ClimateMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.CoverMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.LightMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.NumberMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.SelectMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.SensorMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.SwitchMessageHandler; -import no.seime.openhab.binding.esphome.internal.message.TextSensorMessageHandler; +import no.seime.openhab.binding.esphome.internal.comm.*; +import no.seime.openhab.binding.esphome.internal.message.*; /** * The {@link ESPHomeHandler} is responsible for handling commands, which are @@ -70,7 +48,7 @@ * @author Arne Seime - Initial contribution */ @NonNullByDefault -public class ESPHomeHandler extends BaseThingHandler implements PacketListener { +public class ESPHomeHandler extends BaseThingHandler implements CommunicationListener { public static final int CONNECT_TIMEOUT = 20; private static final int API_VERSION_MAJOR = 1; @@ -80,7 +58,8 @@ public class ESPHomeHandler extends BaseThingHandler implements PacketListener { private final ConnectionSelector connectionSelector; private final ESPChannelTypeProvider dynamicChannelTypeProvider; private @Nullable ESPHomeConfiguration config; - private @Nullable ESPHomeConnection connection; + + private @Nullable AbstractFrameHelper frameHelper; @Nullable private ScheduledFuture pingWatchdogFuture; private Instant lastPong = Instant.now(); @@ -95,11 +74,15 @@ public class ESPHomeHandler extends BaseThingHandler implements PacketListener { private boolean disposed = false; private boolean interrogated; + @Nullable + private String logPrefix = null; + public ESPHomeHandler(Thing thing, ConnectionSelector connectionSelector, ESPChannelTypeProvider dynamicChannelTypeProvider) { super(thing); this.connectionSelector = connectionSelector; this.dynamicChannelTypeProvider = dynamicChannelTypeProvider; + logPrefix = thing.getUID().getId(); // Register message handlers for each type of message pairs registerMessageHandler("Select", new SelectMessageHandler(this), ListEntitiesSelectResponse.class, @@ -138,6 +121,11 @@ public void initialize() { logger.debug("[{}] Initializing ESPHome handler", thing.getUID()); config = getConfigAs(ESPHomeConfiguration.class); + // Use configured logprefix instead of default thingId + if (config.logPrefix != null) { + logPrefix = config.logPrefix; + } + if (config.hostname != null && !config.hostname.isEmpty()) { scheduleReconnect(0); } else { @@ -155,20 +143,25 @@ private void connect() { try { dynamicChannels.clear(); - logger.info("[{}] Trying to connect to {}:{}", config.hostname, config.hostname, config.port); + logger.info("[{}] Trying to connect to {}:{}", logPrefix, config.hostname, config.port); updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, String.format("Connecting to %s:%d", config.hostname, config.port)); - connection = new ESPHomeConnection(connectionSelector, new PlainTextStreamHandler(this), config.hostname); - connection.connect(new InetSocketAddress(config.hostname, config.port)); + boolean useEncryption = config.encryptionKey != null; + if (!useEncryption) { + logger.warn( + "[{}] Using unencrypted connection. This is deprecated and will be removed in the future. Please use encryption.", + logPrefix); + } + + frameHelper = useEncryption + ? new EncryptedFrameHelper(connectionSelector, this, config.encryptionKey, config.server, logPrefix) + : new PlainTextFrameHelper(connectionSelector, this, logPrefix); - HelloRequest helloRequest = HelloRequest.newBuilder().setClientInfo("openHAB") - .setApiVersionMajor(API_VERSION_MAJOR).setApiVersionMinor(API_VERSION_MINOR).build(); - connectionState = ConnectionState.HELLO_SENT; - connection.send(helloRequest); + frameHelper.connect(new InetSocketAddress(config.hostname, config.port)); } catch (ProtocolException e) { - logger.warn("[{}] Error initial connection", config.hostname, e); + logger.warn("[{}] Error initial connection", logPrefix, e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); if (!disposed) { // Don't reconnect if we've been disposed scheduleReconnect(CONNECT_TIMEOUT * 2); @@ -178,27 +171,27 @@ private void connect() { @Override public void dispose() { + disposed = true; setUndefToAllChannels(); cancelReconnectFuture(); - if (connection != null) { + if (frameHelper != null) { cancelPingWatchdog(); if (connectionState == ConnectionState.CONNECTED) { try { - connection.send(DisconnectRequest.getDefaultInstance()); + frameHelper.send(DisconnectRequest.getDefaultInstance()); } catch (ProtocolAPIError e) { // Quietly ignore } } else { - connection.close(); + frameHelper.close(); } } - disposed = true; super.dispose(); } public void sendMessage(GeneratedMessageV3 message) throws ProtocolAPIError { - connection.send(message); + frameHelper.send(message); } private void setUndefToAllChannels() { @@ -210,15 +203,15 @@ private void setUndefToAllChannels() { public synchronized void handleCommand(ChannelUID channelUID, Command command) { if (connectionState != ConnectionState.CONNECTED) { - logger.warn("[{}] Not connected, ignoring command {}", config.hostname, command); + logger.warn("[{}] Not connected, ignoring command {}", logPrefix, command); return; } if (command == RefreshType.REFRESH) { try { - connection.send(SubscribeStatesRequest.getDefaultInstance()); + frameHelper.send(SubscribeStatesRequest.getDefaultInstance()); } catch (ProtocolAPIError e) { - logger.error("[{}] Error sending command {} to channel {}: {}", config.hostname, command, channelUID, + logger.error("[{}] Error sending command {} to channel {}: {}", logPrefix, command, channelUID, e.getMessage()); } return; @@ -230,30 +223,39 @@ public synchronized void handleCommand(ChannelUID channelUID, Command command) { try { String commandClass = (String) channel.getConfiguration().get(BindingConstants.COMMAND_CLASS); if (commandClass == null) { - logger.warn("[{}] No command class for channel {}", config.hostname, channelUID); + logger.warn("[{}] No command class for channel {}", logPrefix, channelUID); return; } AbstractMessageHandler abstractMessageHandler = commandTypeToHandlerMap .get(commandClass); if (abstractMessageHandler == null) { - logger.warn("[{}] No message handler for command class {}", config.hostname, commandClass); + logger.warn("[{}] No message handler for command class {}", logPrefix, commandClass); } else { int key = ((BigDecimal) channel.getConfiguration().get(BindingConstants.COMMAND_KEY)).intValue(); abstractMessageHandler.handleCommand(channel, command, key); } } catch (Exception e) { - logger.error("[{}] Error sending command {} to channel {}: {}", config.hostname, command, channelUID, + logger.error("[{}] Error sending command {} to channel {}: {}", logPrefix, command, channelUID, e.getMessage(), e); } }); } + @Override + public void onConnect() throws ProtocolAPIError { + logger.debug("[{}] Connection established", logPrefix); + HelloRequest helloRequest = HelloRequest.newBuilder().setClientInfo("openHAB") + .setApiVersionMajor(API_VERSION_MAJOR).setApiVersionMinor(API_VERSION_MINOR).build(); + connectionState = ConnectionState.HELLO_SENT; + frameHelper.send(helloRequest); + } + @Override public void onPacket(@NonNull GeneratedMessageV3 message) throws ProtocolAPIError { switch (connectionState) { - case UNINITIALIZED -> logger.warn("[{}] Received packet while uninitialized.", config.hostname); + case UNINITIALIZED -> logger.warn("[{}] Received packet while uninitialized.", logPrefix); case HELLO_SENT -> handleHelloResponse(message); case LOGIN_SENT -> handleLoginResponse(message); case CONNECTED -> handleConnected(message); @@ -262,27 +264,45 @@ public void onPacket(@NonNull GeneratedMessageV3 message) throws ProtocolAPIErro @Override public void onEndOfStream() { - updateStatus(ThingStatus.OFFLINE); - setUndefToAllChannels(); - connection.close(); - cancelPingWatchdog(); - connectionState = ConnectionState.UNINITIALIZED; - scheduleReconnect(CONNECT_TIMEOUT * 2); + if (!disposed) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "ESPHome device closed connection."); + setUndefToAllChannels(); + frameHelper.close(); + cancelPingWatchdog(); + connectionState = ConnectionState.UNINITIALIZED; + scheduleReconnect(CONNECT_TIMEOUT * 2); + } } @Override - public void onParseError() { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Parse error. This could be due to api encryption being used by ESPHome device. Update your ESPHome device to use plaintext password until this is implemented in the binding."); - setUndefToAllChannels(); - cancelPingWatchdog(); - connection.close(); - connectionState = ConnectionState.UNINITIALIZED; - scheduleReconnect(CONNECT_TIMEOUT * 2); + public void onParseError(CommunicationError error) { + if (!disposed) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.toString()); + setUndefToAllChannels(); + cancelPingWatchdog(); + frameHelper.close(); + connectionState = ConnectionState.UNINITIALIZED; + scheduleReconnect(CONNECT_TIMEOUT * 2); + } + } + + private void remoteDisconnect() { + if (!disposed) { + int reconnectDelaySeconds = CONNECT_TIMEOUT; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, String.format( + "ESPHome device requested disconnect. Will reconnect in %d seconds", reconnectDelaySeconds)); + + frameHelper.close(); + setUndefToAllChannels(); + connectionState = ConnectionState.UNINITIALIZED; + cancelPingWatchdog(); + scheduleReconnect(reconnectDelaySeconds); + } } private void handleConnected(GeneratedMessageV3 message) throws ProtocolAPIError { - logger.debug("[{}] Received message {}", config.hostname, message); + logger.debug("[{}] Received message {}", logPrefix, message); if (message instanceof DeviceInfoResponse rsp) { Map props = new HashMap<>(); props.put("esphome_version", rsp.getEsphomeVersion()); @@ -294,21 +314,21 @@ private void handleConnected(GeneratedMessageV3 message) throws ProtocolAPIError updateThing(editThing().withProperties(props).build()); } else if (message instanceof ListEntitiesDoneResponse) { updateThing(editThing().withChannels(dynamicChannels).build()); - logger.debug("[{}] Device interrogation complete, done updating thing channels", config.hostname); + logger.debug("[{}] Device interrogation complete, done updating thing channels", logPrefix); interrogated = true; - connection.send(SubscribeStatesRequest.getDefaultInstance()); + frameHelper.send(SubscribeStatesRequest.getDefaultInstance()); } else if (message instanceof PingRequest) { - logger.debug("[{}] Responding to ping request", config.hostname); - connection.send(PingResponse.getDefaultInstance()); + logger.debug("[{}] Responding to ping request", logPrefix); + frameHelper.send(PingResponse.getDefaultInstance()); } else if (message instanceof PingResponse) { - logger.debug("[{}] Received ping response", config.hostname); + logger.debug("[{}] Received ping response", logPrefix); lastPong = Instant.now(); } else if (message instanceof DisconnectRequest) { - connection.send(DisconnectResponse.getDefaultInstance()); + frameHelper.send(DisconnectResponse.getDefaultInstance()); remoteDisconnect(); } else if (message instanceof DisconnectResponse) { - connection.close(); + frameHelper.close(); } else { // Regular messages handled by message handlers AbstractMessageHandler abstractMessageHandler = classToHandlerMap @@ -317,49 +337,38 @@ private void handleConnected(GeneratedMessageV3 message) throws ProtocolAPIError abstractMessageHandler.handleMessage(message); } else { logger.warn("[{}] Unhandled message of type {}. This is lack of support in the binding. Content: '{}'.", - config.hostname, message.getClass().getName(), message); + logPrefix, message.getClass().getName(), message); } } } - private void remoteDisconnect() { - connection.close(); - setUndefToAllChannels(); - connectionState = ConnectionState.UNINITIALIZED; - int reconnectDelaySeconds = CONNECT_TIMEOUT; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, String - .format("ESPHome device requested disconnect. Will reconnect in %d seconds", reconnectDelaySeconds)); - cancelPingWatchdog(); - scheduleReconnect(reconnectDelaySeconds); - } - private void handleLoginResponse(GeneratedMessageV3 message) throws ProtocolAPIError { if (message instanceof ConnectResponse connectResponse) { - logger.debug("[{}] Received login response {}", config.hostname, connectResponse); + logger.debug("[{}] Received login response {}", logPrefix, connectResponse); if (connectResponse.getInvalidPassword()) { - logger.error("[{}] Invalid password", config.hostname); - connection.close(); + logger.error("[{}] Invalid password", logPrefix); + frameHelper.close(); connectionState = ConnectionState.UNINITIALIZED; updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid password"); return; } connectionState = ConnectionState.CONNECTED; updateStatus(ThingStatus.ONLINE); - logger.debug("[{}] Device login complete, starting device interrogation", config.hostname); + logger.debug("[{}] Device login complete, starting device interrogation", logPrefix); // Reset last pong lastPong = Instant.now(); pingWatchdogFuture = scheduler.scheduleAtFixedRate(() -> { - if (lastPong.plusSeconds(config.maxPingTimeouts * config.pingInterval).isBefore(Instant.now())) { + if (lastPong.plusSeconds((long) config.maxPingTimeouts * config.pingInterval).isBefore(Instant.now())) { logger.warn( - "[{}] Ping responses lacking Waited {} times {} seconds, total of {}. Assuming connection lost and disconnecting", - config.hostname, config.maxPingTimeouts, config.pingInterval, + "[{}] Ping responses lacking. Waited {} times {} seconds, total of {}. Assuming connection lost and disconnecting", + logPrefix, config.maxPingTimeouts, config.pingInterval, config.maxPingTimeouts * config.pingInterval); pingWatchdogFuture.cancel(false); - connection.close(); + frameHelper.close(); connectionState = ConnectionState.UNINITIALIZED; updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, String.format("ESPHome did not respond to ping requests. %d pings sent with %d s delay", @@ -369,16 +378,16 @@ private void handleLoginResponse(GeneratedMessageV3 message) throws ProtocolAPIE } else { try { - logger.debug("[{}] Sending ping", config.hostname); - connection.send(PingRequest.getDefaultInstance()); + logger.debug("[{}] Sending ping", logPrefix); + frameHelper.send(PingRequest.getDefaultInstance()); } catch (ProtocolAPIError e) { - logger.warn("[{}] Error sending ping request", config.hostname, e); + logger.warn("[{}] Error sending ping request", logPrefix, e); } } }, config.pingInterval, config.pingInterval, TimeUnit.SECONDS); - connection.send(DeviceInfoRequest.getDefaultInstance()); - connection.send(ListEntitiesRequest.getDefaultInstance()); + frameHelper.send(DeviceInfoRequest.getDefaultInstance()); + frameHelper.send(ListEntitiesRequest.getDefaultInstance()); } } @@ -390,16 +399,16 @@ public void updateState(ChannelUID channelUID, State state) { private void handleHelloResponse(GeneratedMessageV3 message) throws ProtocolAPIError { if (message instanceof HelloResponse helloResponse) { - logger.debug("[{}] Received hello response {}", config.hostname, helloResponse); - logger.info("[{}] Connected. Device '{}' running '{}' on protocol version '{}.{}'", config.hostname, + logger.debug("[{}] Received hello response {}", logPrefix, helloResponse); + logger.info("[{}] Connected. Device '{}' running '{}' on protocol version '{}.{}'", logPrefix, helloResponse.getName(), helloResponse.getServerInfo(), helloResponse.getApiVersionMajor(), helloResponse.getApiVersionMinor()); connectionState = ConnectionState.LOGIN_SENT; if (config.password != null && !config.password.isEmpty()) { - connection.send(ConnectRequest.newBuilder().setPassword(config.password).build()); + frameHelper.send(ConnectRequest.newBuilder().setPassword(config.password).build()); } else { - connection.send(ConnectRequest.getDefaultInstance()); + frameHelper.send(ConnectRequest.getDefaultInstance()); } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java index 5412828..71a3c97 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java @@ -47,7 +47,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } - private ConnectionSelector connectionSelector; + private final ConnectionSelector connectionSelector; private final ESPChannelTypeProvider dynamicChannelTypeProvider; diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/message/ClimateMessageHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/message/ClimateMessageHandler.java index 871d7a2..3528e75 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/message/ClimateMessageHandler.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/message/ClimateMessageHandler.java @@ -26,11 +26,8 @@ import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; -import io.esphome.api.ClimateCommandRequest; -import io.esphome.api.ClimateStateResponse; -import io.esphome.api.ListEntitiesClimateResponse; +import io.esphome.api.*; import no.seime.openhab.binding.esphome.internal.BindingConstants; -import no.seime.openhab.binding.esphome.internal.ClimateEnumHelper; import no.seime.openhab.binding.esphome.internal.comm.ProtocolAPIError; import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler; @@ -149,10 +146,11 @@ public void buildChannels(ListEntitiesClimateResponse rsp) { "Target temperature", itemTypeTemperature, Collections.emptyList(), "%.1f %unit%", Set.of(SEMANTIC_TYPE_SETPOINT, "Temperature"), false, "temperature", rsp.getVisualTargetTemperatureStep() == 0f ? null - : new BigDecimal(rsp.getVisualTargetTemperatureStep()), + : BigDecimal.valueOf(rsp.getVisualTargetTemperatureStep()), rsp.getVisualMinTemperature() == 0f ? null - : rsp.getVisualMaxTemperature() == 0f ? null : new BigDecimal(rsp.getVisualMinTemperature()), - new BigDecimal(rsp.getVisualMaxTemperature())); + : rsp.getVisualMaxTemperature() == 0f ? null + : BigDecimal.valueOf(rsp.getVisualMinTemperature()), + BigDecimal.valueOf(rsp.getVisualMaxTemperature())); Channel channelTargetTemperature = ChannelBuilder .create(createChannelUID(cleanedComponentName, CHANNEL_TARGET_TEMPERATURE)) @@ -167,11 +165,11 @@ public void buildChannels(ListEntitiesClimateResponse rsp) { "Current temperature", itemTypeTemperature, Collections.emptyList(), "%.1f %unit%", Set.of("Measurement", "Temperature"), true, "temperature", rsp.getVisualCurrentTemperatureStep() == 0f ? null - : new BigDecimal(rsp.getVisualCurrentTemperatureStep()), + : BigDecimal.valueOf(rsp.getVisualCurrentTemperatureStep()), rsp.getVisualMinTemperature() == 0f ? null : rsp.getVisualMaxTemperature() == 0f ? null - : new BigDecimal(rsp.getVisualMinTemperature()), - new BigDecimal(rsp.getVisualMaxTemperature())); + : BigDecimal.valueOf(rsp.getVisualMinTemperature()), + BigDecimal.valueOf(rsp.getVisualMaxTemperature())); Channel channel = ChannelBuilder.create(createChannelUID(cleanedComponentName, CHANNEL_CURRENT_TEMPERATURE)) .withLabel(createLabel(rsp.getName(), "Current temperature")).withKind(ChannelKind.STATE) @@ -272,4 +270,42 @@ public void handleState(ClimateStateResponse rsp) { findChannelByKeyAndField(rsp.getKey(), CHANNEL_SWING_MODE).ifPresent(channel -> handler .updateState(channel.getUID(), new StringType(ClimateEnumHelper.stripEnumPrefix(rsp.getSwingMode())))); } + + public static class ClimateEnumHelper { + public static String stripEnumPrefix(ClimateSwingMode mode) { + String toRemove = "CLIMATE_SWING"; + return mode.toString().substring(toRemove.length() + 1); + } + + public static String stripEnumPrefix(ClimateFanMode mode) { + String toRemove = "CLIMATE_FAN"; + return mode.toString().substring(toRemove.length() + 1); + } + + public static String stripEnumPrefix(ClimateMode climateMode) { + String toRemove = "CLIMATE_MODE"; + return climateMode.toString().substring(toRemove.length() + 1); + } + + public static String stripEnumPrefix(ClimatePreset climatePreset) { + String toRemove = "CLIMATE_PRESET"; + return climatePreset.toString().substring(toRemove.length() + 1); + } + + public static ClimateFanMode toFanMode(String fanMode) { + return ClimateFanMode.valueOf("CLIMATE_FAN_" + fanMode.toUpperCase()); + } + + public static ClimatePreset toClimatePreset(String climatePreset) { + return ClimatePreset.valueOf("CLIMATE_PRESET_" + climatePreset.toUpperCase()); + } + + public static ClimateMode toClimateMode(String mode) { + return ClimateMode.valueOf("CLIMATE_MODE_" + mode.toUpperCase()); + } + + public static ClimateSwingMode toClimateSwingMode(String mode) { + return ClimateSwingMode.valueOf("CLIMATE_SWING_" + mode.toUpperCase()); + } + } } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/message/LightMessageHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/message/LightMessageHandler.java index a48b166..0182d64 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/message/LightMessageHandler.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/message/LightMessageHandler.java @@ -167,7 +167,7 @@ public void handleState(LightStateResponse rsp) { new int[] { (int) rsp.getRed() * 255, (int) rsp.getGreen() * 255, (int) rsp.getBlue() * 255 }); // If off, set brightness to 0 - if (rsp.getState() == false) { + if (!rsp.getState()) { hsbType = new HSBType(hsbType.getHue(), hsbType.getSaturation(), new PercentType(0)); } else { // Adjust brightness diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/message/NumberMessageHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/message/NumberMessageHandler.java index 3d7f5a5..25939a8 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/message/NumberMessageHandler.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/message/NumberMessageHandler.java @@ -77,9 +77,9 @@ public void buildChannels(ListEntitiesNumberResponse rsp) { ChannelType channelType = addChannelType(rsp.getUniqueId(), rsp.getName(), itemType, Collections.emptyList(), "%." + accurracyDecimals + "f " + (unitOfMeasurement.equals("%") ? "%unit%" : unitOfMeasurement), tags, - false, icon, rsp.getStep() != 0f ? new BigDecimal(rsp.getStep()) : null, - rsp.getMinValue() != 0f ? new BigDecimal(rsp.getMinValue()) : null, - rsp.getMaxValue() != 0f ? new BigDecimal(rsp.getMaxValue()) : null); + false, icon, rsp.getStep() != 0f ? BigDecimal.valueOf(rsp.getStep()) : null, + rsp.getMinValue() != 0f ? BigDecimal.valueOf(rsp.getMinValue()) : null, + rsp.getMaxValue() != 0f ? BigDecimal.valueOf(rsp.getMaxValue()) : null); Channel channel = ChannelBuilder.create(new ChannelUID(handler.getThing().getUID(), rsp.getObjectId())) .withLabel(rsp.getName()).withKind(ChannelKind.STATE).withType(channelType.getUID()) diff --git a/src/main/resources/OH-INF/thing/thing-types.xml b/src/main/resources/OH-INF/thing/thing-types.xml index 157c55d..89d6bf8 100644 --- a/src/main/resources/OH-INF/thing/thing-types.xml +++ b/src/main/resources/OH-INF/thing/thing-types.xml @@ -9,9 +9,6 @@ ESP32 / ESP8266 device flashed with ESPHome. See esphome.io - - - hostname @@ -26,11 +23,24 @@ 6053 IP Port address of the device + + + password + + Specified in the api: -> encryption: -> key: section of the esphome device config. See + https://esphome.io/components/api#configuration-variables + password - - Password to access the device + + DEPRECATED: Password to access the device. Instead, use encryptionKey for secured communications. + + + + Used to verify that we're talking to the correct device + + 10 @@ -44,6 +54,12 @@ connection is assumed broken, and reconnect starts true + + + Useful when debugging communication with a particular device (and you have many) + true + + diff --git a/src/test/java/com/southernstorm/json/JsonReader.java b/src/test/java/com/southernstorm/json/JsonReader.java new file mode 100644 index 0000000..6911821 --- /dev/null +++ b/src/test/java/com/southernstorm/json/JsonReader.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2013 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.json; + +import java.io.IOException; +import java.io.Reader; + +/** + * Recursive-descent parser for JSON streams. + * + * Intentionally compatible with android.util.JsonReader. + */ +public class JsonReader { + + private Reader in; + private JsonToken token; + private boolean lenient; + private boolean booleanValue; + private String stringValue; + private int ungetCh; + + public JsonReader(Reader in) { + this.in = in; + this.token = JsonToken.READ_FIRST; + this.lenient = false; + this.ungetCh = -2; + } + + public void beginArray() throws IOException { + expectNext(JsonToken.BEGIN_ARRAY, "JSON begin array expected"); + } + + public void beginObject() throws IOException { + expectNext(JsonToken.BEGIN_OBJECT, "JSON begin object expected"); + } + + public void close() throws IOException { + in.close(); + } + + public void endArray() throws IOException { + expectNext(JsonToken.END_ARRAY, "JSON end array expected"); + } + + public void endObject() throws IOException { + expectNext(JsonToken.END_OBJECT, "JSON end array expected"); + } + + public boolean hasNext() throws IOException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + while (this.token == JsonToken.COMMA) + nextToken(); // Very lenient - multiple separating commas allowed. + return this.token != JsonToken.END_ARRAY && this.token != JsonToken.END_OBJECT; + } + + public boolean isLenient() { + return this.lenient; + } + + public void setLenient(boolean lenient) { + // Lenient mode as described in the Android documentation is not implemented. + this.lenient = lenient; + } + + public boolean nextBoolean() throws IOException, IllegalStateException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + if (this.token != JsonToken.BOOLEAN) + throw new IllegalStateException("JSON boolean value expected"); + boolean value = booleanValue; + nextToken(); + return value; + } + + public double nextDouble() throws IOException, IllegalStateException, NumberFormatException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + double value; + if (this.token == JsonToken.STRING || token == JsonToken.NUMBER) { + value = Double.parseDouble(stringValue); + } else { + throw new IllegalStateException("JSON double value expected"); + } + nextToken(); + return value; + } + + public int nextInt() throws IOException, IllegalStateException, NumberFormatException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + int value; + if (this.token == JsonToken.STRING || this.token == JsonToken.NUMBER) { + value = Integer.parseInt(stringValue); + } else { + throw new IllegalStateException("JSON int value expected"); + } + nextToken(); + return value; + } + + public long nextLong() throws IOException, IllegalStateException, NumberFormatException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + long value; + if (this.token == JsonToken.STRING || this.token == JsonToken.NUMBER) { + value = Long.parseLong(stringValue); + } else { + throw new MalformedJsonException("JSON long value expected"); + } + nextToken(); + return value; + } + + public String nextName() throws IOException, IllegalStateException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + if (this.token != JsonToken.NAME) + throw new IllegalStateException("JSON property name expected"); + String value = stringValue; + nextToken(); + return value; + } + + public void nextNull() throws IOException, IllegalStateException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + if (this.token != JsonToken.NULL) + throw new IllegalStateException("JSON null value expected"); + nextToken(); + } + + public String nextString() throws IOException, IllegalStateException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + String value; + if (this.token == JsonToken.STRING || this.token == JsonToken.NUMBER) + value = stringValue; + else if (this.token == JsonToken.BOOLEAN) + value = Boolean.toString(booleanValue); + else if (this.token == JsonToken.NULL) + value = null; + else + throw new IllegalStateException("JSON string value expected"); + nextToken(); + return value; + } + + public JsonToken peek() throws IOException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + return this.token; + } + + public void skipValue() throws IOException { + switch (peek()) { + case BEGIN_ARRAY: + beginArray(); + while (hasNext()) + skipValue(); + endArray(); + break; + case BEGIN_OBJECT: + beginObject(); + while (hasNext()) { + nextName(); + skipValue(); + } + endObject(); + break; + case END_DOCUMENT: + break; + default: + nextToken(); + break; + } + } + + private void parseNumber(int ch) throws IOException { + StringBuilder builder = new StringBuilder(); + builder.append((char) ch); + ch = in.read(); + while ((ch >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == 'e' || ch == 'E' || ch == '.') { + builder.append((char) ch); + ch = in.read(); + } + ungetCh = ch; + stringValue = builder.toString(); + } + + private void parseString() throws IOException { + StringBuilder builder = new StringBuilder(); + int ch = in.read(); + outer: while (ch != '"') { + if (ch == -1) { + ungetCh = ch; + break; + } else if (ch == '\\') { + ch = in.read(); + if (ch == 'b') + builder.append('\u0008'); + else if (ch == 'f') + builder.append('\u000C'); + else if (ch == 'n') + builder.append('\n'); + else if (ch == 'r') + builder.append('\r'); + else if (ch == 't') + builder.append('\t'); + else if (ch != 'u') + builder.append((char) ch); + else if (ch == -1) { + ungetCh = ch; + break; + } else { + int value = 0; + int digits = 0; + while (digits < 4) { + ch = in.read(); + if (ch >= '0' && ch <= '9') + value = value * 16 + (ch - '0'); + else if (ch >= 'A' && ch <= 'F') + value = value * 16 + (ch - 'A' + 10); + else if (ch >= 'a' && ch <= 'f') + value = value * 16 + (ch - 'a' + 10); + else { + builder.append((char) value); + continue outer; + } + ++digits; + } + builder.append((char) value); + } + } else { + builder.append((char) ch); + } + ch = in.read(); + } + stringValue = builder.toString(); + } + + private boolean checkForColon() throws IOException { + int ch = ungetCh; + if (ch == -2) + ch = in.read(); + while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') + ch = in.read(); + if (ch == ':') { + ungetCh = -2; + return true; + } else { + ungetCh = ch; + return false; + } + } + + private void checkNamedToken(String name) throws IOException { + int index = 1; + int ch = in.read(); + for (; index < name.length(); ++index) { + if (ch != name.charAt(index)) + break; + ch = in.read(); + } + if (index < name.length() || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') + || ch == '_') { + throw new MalformedJsonException("Invalid keyword, expected: '" + name + "'"); + } + ungetCh = ch; + } + + private void nextToken() throws IOException { + int ch = ungetCh; + if (ch == -2) + ch = in.read(); + while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') + ch = in.read(); + ungetCh = -2; + switch (ch) { + case -1: + token = JsonToken.END_DOCUMENT; + ungetCh = -1; + break; + case '{': + token = JsonToken.BEGIN_OBJECT; + break; + case '}': + token = JsonToken.END_OBJECT; + break; + case '[': + token = JsonToken.BEGIN_ARRAY; + break; + case ']': + token = JsonToken.END_ARRAY; + break; + case ',': + token = JsonToken.COMMA; + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + parseNumber(ch); + token = JsonToken.NUMBER; + break; + case '"': + parseString(); + if (checkForColon()) + token = JsonToken.NAME; + else + token = JsonToken.STRING; + break; + case 'n': + checkNamedToken("null"); + token = JsonToken.NULL; + break; + case 't': + checkNamedToken("true"); + token = JsonToken.BOOLEAN; + booleanValue = true; + break; + case 'f': + checkNamedToken("false"); + token = JsonToken.BOOLEAN; + booleanValue = false; + break; + default: + throw new MalformedJsonException("Invalid character in JSON stream: '" + (char) ch + "'"); + } + } + + private void expectNext(JsonToken token, String message) throws IOException { + if (this.token == JsonToken.READ_FIRST) + nextToken(); + if (this.token != token) + throw new MalformedJsonException(message); + nextToken(); + } +} diff --git a/src/test/java/com/southernstorm/json/JsonToken.java b/src/test/java/com/southernstorm/json/JsonToken.java new file mode 100644 index 0000000..02070ad --- /dev/null +++ b/src/test/java/com/southernstorm/json/JsonToken.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.json; + +/** + * Types of JSON tokens. + * + * Intentionally compatible with android.util.JsonToken. + */ +public enum JsonToken { + BEGIN_ARRAY, + BEGIN_OBJECT, + BOOLEAN, + END_ARRAY, + END_DOCUMENT, + END_OBJECT, + NAME, + NULL, + NUMBER, + STRING, + + // Internal to this implementation of JsonReader - not part of the public interface. + READ_FIRST, + COMMA +} diff --git a/src/test/java/com/southernstorm/json/MalformedJsonException.java b/src/test/java/com/southernstorm/json/MalformedJsonException.java new file mode 100644 index 0000000..372e094 --- /dev/null +++ b/src/test/java/com/southernstorm/json/MalformedJsonException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.json; + +import java.io.IOException; + +public class MalformedJsonException extends IOException { + + private static final long serialVersionUID = -1645564375062756336L; + + public MalformedJsonException(String detailMessage) { + super(detailMessage); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/CipherStateTests.java b/src/test/java/com/southernstorm/noise/tests/CipherStateTests.java new file mode 100644 index 0000000..589678d --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/CipherStateTests.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +import org.junit.Test; + +import com.southernstorm.noise.protocol.CipherState; +import com.southernstorm.noise.protocol.Noise; + +/** + * Perform tests on the cipher algorithms used by Noise. + */ +public class CipherStateTests { + + private void testCipher(String name, int keyLen, int macLen, String key, long nonce, String ad, String plaintext, + String ciphertext, String mac, boolean forceFallbacks) { + byte[] keyBytes = TestUtils.stringToData(key); + byte[] adBytes = TestUtils.stringToData(ad); + byte[] plaintextBytes = TestUtils.stringToData(plaintext); + byte[] ciphertextBytes; + byte[] buffer; + if (ciphertext.length() > 0) { + ciphertextBytes = TestUtils.stringToData(ciphertext + mac.substring(2)); + } else { + ciphertextBytes = TestUtils.stringToData(mac); + } + + // Create the cipher object and check its properties. + CipherState cipher = null; + Noise.setForceFallbacks(forceFallbacks); + try { + cipher = Noise.createCipher(name); + } catch (NoSuchAlgorithmException e) { + fail(name + " cipher is not supported"); + } + assertEquals(name, cipher.getCipherName()); + assertEquals(keyLen, cipher.getKeyLength()); + assertEquals(0, cipher.getMACLength()); // Key has not been set yet. + + // Try to encrypt. Because the key is not set yet, this will + // return the plaintext as-is. + try { + buffer = new byte[plaintextBytes.length]; + Arrays.fill(buffer, (byte) 0xAA); + assertEquals(plaintextBytes.length, + cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length)); + assertArrayEquals(plaintextBytes, buffer); + } catch (ShortBufferException e) { + fail("Buffer should have been big enough"); + } + + // Try to decrypt. Will return the ciphertext and MAC as-is. + buffer = new byte[ciphertextBytes.length]; + Arrays.fill(buffer, (byte) 0xAA); + try { + assertEquals(ciphertextBytes.length, + cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length)); + } catch (BadPaddingException e) { + fail(); + } catch (ShortBufferException e) { + fail(); + } + + // Set the key and fast-forward the nonce. + cipher.initializeKey(keyBytes, 0); + cipher.setNonce(nonce); + assertEquals(macLen, cipher.getMACLength()); + + // Encrypt the data. + try { + buffer = new byte[ciphertextBytes.length]; + Arrays.fill(buffer, (byte) 0xAA); + assertEquals(ciphertextBytes.length, + cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length)); + assertArrayEquals(ciphertextBytes, buffer); + } catch (ShortBufferException e) { + fail("Buffer should have been big enough"); + } + + // Try to decrypt. The MAC check should fail because the internal + // nonce was incremented and no longer matches the parameter. + try { + cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length); + fail(); + } catch (BadPaddingException e) { + // Success! + } catch (ShortBufferException e) { + fail(); + } + + // Fast-forward the nonce to just before the rollover. We will be able + // to encrypt one more block, and then the next request will be rejected. + cipher.setNonce(-2L); + try { + buffer = new byte[ciphertextBytes.length]; + Arrays.fill(buffer, (byte) 0xAA); + cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length); + try { + cipher.encryptWithAd(adBytes, plaintextBytes, 0, buffer, 0, plaintextBytes.length); + fail(); + } catch (IllegalStateException e) { + // Success! + } + } catch (ShortBufferException e) { + fail("Buffer should have been big enough"); + } + + // Reset the key and then we can reset the nonce. + cipher.initializeKey(keyBytes, 0); + cipher.setNonce(nonce); + assertEquals(macLen, cipher.getMACLength()); + + // Decrypt the test ciphertext and MAC. + try { + buffer = new byte[plaintextBytes.length]; + Arrays.fill(buffer, (byte) 0xAA); + assertEquals(plaintextBytes.length, + cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length)); + assertArrayEquals(plaintextBytes, buffer); + } catch (BadPaddingException e) { + fail(); + } catch (ShortBufferException e) { + fail(); + } + + // Fast-forward the nonce to just before the rollover. We will be able + // to decrypt one more block, and then the next request will be rejected. + cipher.setNonce(-2L); + try { + buffer = new byte[plaintextBytes.length]; + Arrays.fill(buffer, (byte) 0xAA); + try { + cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length); + fail(); + } catch (BadPaddingException e) { + // Success! + } + try { + cipher.decryptWithAd(adBytes, ciphertextBytes, 0, buffer, 0, ciphertextBytes.length); + fail(); + } catch (IllegalStateException e) { + // Success! + } catch (BadPaddingException e) { + fail(); + } + } catch (ShortBufferException e) { + fail("Buffer should have been big enough"); + } + } + + private void testCipher(String name, int keyLen, int macLen, String key, long nonce, String ad, String plaintext, + String ciphertext, String mac) { + testCipher(name, keyLen, macLen, key, nonce, ad, plaintext, ciphertext, mac, true); + testCipher(name, keyLen, macLen, key, nonce, ad, plaintext, ciphertext, mac, false); + } + + @Test + public void AESGCM() { + /* + * Test vectors for AES in GCM mode from Appendix B of: + * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf + * We can only use a few of the vectors because most of the IV's in the + * revised specification don't match what we need here + */ + + // AESGCM - gcm-revised-spec.pdf, test case #13. + testCipher("AESGCM", 32, 16, "0x0000000000000000000000000000000000000000000000000000000000000000", 0, "", "", + "", "0x530f8afbc74536b9a963b4f1c4cb738b"); + + // AESGCM - gcm-revised-spec.pdf, test case #14. + testCipher("AESGCM", 32, 16, "0x0000000000000000000000000000000000000000000000000000000000000000", 0, "", + "0x00000000000000000000000000000000", "0xcea7403d4d606b6e074ec5d3baf39d18", + "0xd0d1c8a799996bf0265b98b5d48ab919"); + } + + @Test + public void ChaChaPoly() { + // ChaChaPoly test vectors from Appendix A.5 of RFC 7539. + testCipher("ChaChaPoly", 32, 16, "0x1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + 0x0807060504030201L, "0xf33388860000000000004e91", + "0x496e7465726e65742d4472616674732061726520647261667420646f63756d65" + + "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + + "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + + "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + + "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + + "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + + "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + + "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + "726573732e2fe2809d", + "0x64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + + "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + + "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + + "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + + "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + + "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + + "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + + "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + "a6ad5cb4022b02709b", + "0xeead9d67890cbb22392336fea1851f38"); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/Curve25519Tests.java b/src/test/java/com/southernstorm/noise/tests/Curve25519Tests.java new file mode 100644 index 0000000..e7aeb04 --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/Curve25519Tests.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import com.southernstorm.noise.crypto.Curve25519; + +public class Curve25519Tests { + + @Test + public void curve25519() { + // Test vectors from section 6.1 of RFC 7748. + byte[] alicePrivate = TestUtils + .stringToData("0x77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); + byte[] alicePublic = TestUtils + .stringToData("0x8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); + byte[] bobPrivate = TestUtils + .stringToData("0x5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"); + byte[] bobPublic = TestUtils.stringToData("0xde9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); + byte[] sharedSecret = TestUtils + .stringToData("0x4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); + byte[] output = new byte[32]; + + // Test derivation of public keys from private keys. + Arrays.fill(output, (byte) 0xAA); + Curve25519.eval(output, 0, alicePrivate, null); + assertArrayEquals(alicePublic, output); + Arrays.fill(output, (byte) 0xAA); + Curve25519.eval(output, 0, bobPrivate, null); + assertArrayEquals(bobPublic, output); + + // Test creation of the shared secret in both directions. + Arrays.fill(output, (byte) 0xAA); + Curve25519.eval(output, 0, alicePrivate, bobPublic); + assertArrayEquals(sharedSecret, output); + Arrays.fill(output, (byte) 0xAA); + Curve25519.eval(output, 0, bobPrivate, alicePublic); + assertArrayEquals(sharedSecret, output); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/Curve448Tests.java b/src/test/java/com/southernstorm/noise/tests/Curve448Tests.java new file mode 100644 index 0000000..87604a3 --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/Curve448Tests.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import com.southernstorm.noise.crypto.Curve448; + +public class Curve448Tests { + + @Test + public void curve448() { + // Test vectors from section 6.2 of RFC 7748. + byte[] alicePrivate = TestUtils.stringToData( + "0x9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b"); + byte[] alicePublic = TestUtils.stringToData( + "0x9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0"); + byte[] bobPrivate = TestUtils.stringToData( + "0x1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d"); + byte[] bobPublic = TestUtils.stringToData( + "0x3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609"); + byte[] sharedSecret = TestUtils.stringToData( + "0x07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d"); + byte[] output = new byte[56]; + + // Test derivation of public keys from private keys. + Arrays.fill(output, (byte) 0xAA); + Curve448.eval(output, 0, alicePrivate, null); + assertArrayEquals(alicePublic, output); + Arrays.fill(output, (byte) 0xAA); + Curve448.eval(output, 0, bobPrivate, null); + assertArrayEquals(bobPublic, output); + + // Test creation of the shared secret in both directions. + Arrays.fill(output, (byte) 0xAA); + Curve448.eval(output, 0, alicePrivate, bobPublic); + assertArrayEquals(sharedSecret, output); + Arrays.fill(output, (byte) 0xAA); + Curve448.eval(output, 0, bobPrivate, alicePublic); + assertArrayEquals(sharedSecret, output); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/GHASHTests.java b/src/test/java/com/southernstorm/noise/tests/GHASHTests.java new file mode 100644 index 0000000..b8ce96d --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/GHASHTests.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import com.southernstorm.noise.crypto.GHASH; + +public class GHASHTests { + + private void testGHASH(String key, String data, String hash) { + byte[] keyBytes = TestUtils.stringToData(key); + byte[] dataBytes = TestUtils.stringToData(data); + byte[] hashBytes = TestUtils.stringToData(hash); + byte[] tag = new byte[16]; + + GHASH ghash = new GHASH(); + ghash.reset(keyBytes, 0); + ghash.update(dataBytes, 0, dataBytes.length); + Arrays.fill(tag, (byte) 0xAA); + ghash.finish(tag, 0, 16); + assertArrayEquals(hashBytes, tag); + + ghash.reset(); + ghash.update(dataBytes, 0, dataBytes.length / 3); + ghash.update(dataBytes, dataBytes.length / 3, dataBytes.length - (dataBytes.length / 3)); + Arrays.fill(tag, (byte) 0xAA); + ghash.finish(tag, 0, 16); + assertArrayEquals(hashBytes, tag); + } + + @Test + public void ghash() { + // Test vectors from Appendix B of: + // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf + + testGHASH("0x66e94bd4ef8a2c3b884cfa59ca342b2e", "0x00000000000000000000000000000000", + "0x00000000000000000000000000000000"); + + testGHASH("0x66e94bd4ef8a2c3b884cfa59ca342b2e", + "0x0388dace60b6a392f328c2b971b2fe7800000000000000000000000000000080", + "0xf38cbb1ad69223dcc3457ae5b6b0f885"); + + testGHASH("0xb83b533708bf535d0aa6e52980d53b78", + "0x42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f598500000000000000000000000000000200", + "0x7f1b32b81b820d02614f8895ac1d4eac"); + + testGHASH("0xb83b533708bf535d0aa6e52980d53b78", + "0xfeedfacedeadbeeffeedfacedeadbeefabaddad200000000000000000000000042831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e0910000000000000000000000a000000000000001e0", + "0x698e57f70e6ecc7fd9463b7260a9ae5f"); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/HashTests.java b/src/test/java/com/southernstorm/noise/tests/HashTests.java new file mode 100644 index 0000000..bc45392 --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/HashTests.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.junit.Test; + +import com.southernstorm.noise.protocol.Noise; + +/** + * Perform tests on the hash algorithms used by Noise. + */ +public class HashTests { + + private void testHash(MessageDigest digest, String input, String hash) { + byte[] inputBytes = TestUtils.stringToData(input); + byte[] hashBytes = TestUtils.stringToData(hash); + byte[] result = new byte[digest.getDigestLength()]; + + // Hash the entire input in one request. + digest.reset(); + digest.update(inputBytes); + try { + digest.digest(result, 0, result.length); + } catch (DigestException e) { + fail("digest failed"); + } + assertArrayEquals(hashBytes, result); + + // Hash the input in pieces to test split requests. + digest.reset(); + digest.update(inputBytes, 0, inputBytes.length / 2); + digest.update(inputBytes, inputBytes.length / 2, inputBytes.length - (inputBytes.length / 2)); + try { + digest.digest(result, 0, result.length); + } catch (DigestException e) { + fail("digest failed"); + } + assertArrayEquals(hashBytes, result); + } + + private void testHash(String name, String input, String hash) { + MessageDigest digest = null; + + Noise.setForceFallbacks(true); + try { + digest = Noise.createHash(name); + } catch (NoSuchAlgorithmException e) { + fail("No crypto provider for " + name); + } finally { + Noise.setForceFallbacks(false); + } + testHash(digest, input, hash); + + Noise.setForceFallbacks(false); + try { + digest = Noise.createHash(name); + } catch (NoSuchAlgorithmException e) { + fail("No crypto provider for " + name); + } + testHash(digest, input, hash); + } + + @Test + public void blake2b() { + testHash("BLAKE2b", "", + "0x786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"); + testHash("BLAKE2b", "abc", + "0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); + testHash("BLAKE2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "0x7285ff3e8bd768d69be62b3bf18765a325917fa9744ac2f582a20850bc2b1141ed1b3e4528595acc90772bdf2d37dc8a47130b44f33a02e8730e5ad8e166e888"); + testHash("BLAKE2b", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "0xce741ac5930fe346811175c5227bb7bfcd47f42612fae46c0809514f9e0e3a11ee1773287147cdeaeedff50709aa716341fe65240f4ad6777d6bfaf9726e5e52"); + } + + @Test + public void blake2s() { + testHash("BLAKE2s", "", "0x69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9"); + testHash("BLAKE2s", "abc", "0x508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982"); + testHash("BLAKE2s", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "0x6f4df5116a6f332edab1d9e10ee87df6557beab6259d7663f3bcd5722c13f189"); + testHash("BLAKE2s", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "0x358dd2ed0780d4054e76cb6f3a5bce2841e8e2f547431d4d09db21b66d941fc7"); + } + + @Test + public void sha256() { + testHash("SHA256", "", "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + testHash("SHA256", "abc", "0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + testHash("SHA256", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "0x248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); + } + + @Test + public void sha512() { + testHash("SHA512", "", + "0xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); + testHash("SHA512", "abc", + "0xddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); + testHash("SHA512", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "0x8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/Poly1305Tests.java b/src/test/java/com/southernstorm/noise/tests/Poly1305Tests.java new file mode 100644 index 0000000..470f85e --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/Poly1305Tests.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import com.southernstorm.noise.crypto.Poly1305; + +/** + * Perform tests on the Poly1305 implementation in isolation from ChaChaPoly. + */ +public class Poly1305Tests { + + private void testPoly1305(String key, String data, String hash) { + byte[] keyBytes = TestUtils.stringToData(key); + byte[] dataBytes = TestUtils.stringToData(data); + byte[] hashBytes = TestUtils.stringToData(hash); + byte[] token = new byte[16]; + + // Authenticate the data in one hit. + Poly1305 poly = new Poly1305(); + poly.reset(keyBytes, 0); + poly.update(dataBytes, 0, dataBytes.length); + poly.finish(token, 0); + assertArrayEquals(hashBytes, token); + + // Break the data up into chunks to test multiple calls to update(). + Arrays.fill(token, (byte) 0xDD); + poly.reset(keyBytes, 0); + poly.update(dataBytes, 0, dataBytes.length / 2); + poly.update(dataBytes, dataBytes.length / 2, dataBytes.length - (dataBytes.length / 2)); + poly.finish(token, 0); + assertArrayEquals(hashBytes, token); + } + + @Test + public void poly1305() { + // Test vectors from the Poly1305 specification. + testPoly1305("0x851fc40c3467ac0be05cc20404f3f700580b3b0f9447bb1e69d095b5928b6dbc", "0xf3f6", + "0xf4c633c3044fc145f84f335cb81953de"); + testPoly1305("0xa0f3080000f46400d0c7e9076c834403dd3fab2251f11ac759f0887129cc2ee7", "", + "0xdd3fab2251f11ac759f0887129cc2ee7"); + testPoly1305("0x48443d0bb0d21109c89a100b5ce2c20883149c69b561dd88298a1798b10716ef", + "0x663cea190ffb83d89593f3f476b6bc24d7e679107ea26adb8caf6652d0656136", + "0x0ee1c16bb73f0f4fd19881753c01cdbe"); + testPoly1305("0x12976a08c4426d0ce8a82407c4f4820780f8c20aa71202d1e29179cbcb555a57", + "0xab0812724a7f1e342742cbed374d94d136c6b8795d45b3819830f2c04491faf0990c62e48b8018b2c3e4a0fa3134cb67fa83e158c994d961c4cb21095c1bf9", + "0x5154ad0d2cb26e01274fc51148491f1b"); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java b/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java new file mode 100644 index 0000000..d1b6a0b --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/ProtocolTest.java @@ -0,0 +1,27 @@ +package com.southernstorm.noise.tests; + +import static org.junit.Assert.assertNotNull; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +import org.junit.Test; + +import com.southernstorm.noise.protocol.HandshakeState; + +public class ProtocolTest { + @Test + public void testCreateHandshakeState() throws NoSuchAlgorithmException { + HandshakeState client = new HandshakeState("Noise_NNpsk0_25519_ChaChaPoly_SHA256", HandshakeState.INITIATOR); + assertNotNull(client); + + byte[] key = Base64.getDecoder().decode("LOaZwNhb6Ct5o5jRHIVQElRz4Lq25a4vEQ8TGTQT4hw="); + assert key.length == 32; + + client.setPreSharedKey(key, 0, key.length); + byte[] prologue = "NoiseAPIInit\0\0".getBytes(StandardCharsets.US_ASCII); + client.setPrologue(prologue, 0, prologue.length); + client.start(); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/RijndaelAESTests.java b/src/test/java/com/southernstorm/noise/tests/RijndaelAESTests.java new file mode 100644 index 0000000..e70833e --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/RijndaelAESTests.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import com.southernstorm.noise.crypto.RijndaelAES; + +/** + * AES test cases to verify the fallback RijndaelAES implementation. + */ +public class RijndaelAESTests { + + private void testECB(String key, String plaintext, String ciphertext) { + byte[] keyBytes = TestUtils.stringToData(key); + byte[] plaintextBytes = TestUtils.stringToData(plaintext); + byte[] ciphertextBytes = TestUtils.stringToData(ciphertext); + byte[] block = new byte[16]; + + RijndaelAES aes = new RijndaelAES(); + + Arrays.fill(block, (byte) 0xAA); + aes.setupEnc(keyBytes, 0, keyBytes.length * 8); + aes.encrypt(plaintextBytes, 0, block, 0); + assertArrayEquals(ciphertextBytes, block); + + Arrays.fill(block, (byte) 0xAA); + aes.setupDec(keyBytes, 0, keyBytes.length * 8); + aes.decrypt(ciphertextBytes, 0, block, 0); + assertArrayEquals(plaintextBytes, block); + + aes.destroy(); + } + + @Test + public void rijndael() { + // ECB test vectors from the FIPS specification. + testECB("0x000102030405060708090A0B0C0D0E0F", "0x00112233445566778899AABBCCDDEEFF", + "0x69C4E0D86A7B0430D8CDB78070B4C55A"); + testECB("0x000102030405060708090A0B0C0D0E0F1011121314151617", "0x00112233445566778899AABBCCDDEEFF", + "0xDDA97CA4864CDFE06EAF70A0EC0D7191"); + testECB("0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "0x00112233445566778899AABBCCDDEEFF", "0x8EA2B7CA516745BFEAFC49904B496089"); + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/TestUtils.java b/src/test/java/com/southernstorm/noise/tests/TestUtils.java new file mode 100644 index 0000000..36e615e --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/TestUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import java.io.UnsupportedEncodingException; + +import javax.xml.bind.DatatypeConverter; + +public class TestUtils { + + /** + * Convert a string into a binary byte array. + * + * @param data The string data to convert. + * @return The binary version of the data. + * + * If the string starts with "0x", then the remainder of the string is + * interpreted as hexadecimal. Otherwise the string is interpreted + * as a literal string (e.g. "abc") and converted with the UTF-8 encoding. + */ + public static byte[] stringToData(String data) { + if (data.startsWith("0x")) { + return DatatypeConverter.parseHexBinary(data.substring(2)); + } else { + try { + return data.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + return new byte[0]; + } + } + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java b/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java new file mode 100644 index 0000000..da99e87 --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/UnitVectorTests.java @@ -0,0 +1,32 @@ +package com.southernstorm.noise.tests; + +import java.io.InputStream; +import java.net.URL; + +import org.junit.Assert; +import org.junit.Test; + +public class UnitVectorTests { + + private static final String testVectorsCommit = "5d0a74760320e5486ced302e36ccad91606aac43"; + + @Test + public void testBasicVector() throws Exception { + try (InputStream stream = new URL("https://raw.githubusercontent.com/rweather/noise-c/" + testVectorsCommit + + "/tests/vector/noise-c-basic.txt").openStream()) { + VectorTests vectorTests = new VectorTests(); + vectorTests.processInputStream(stream); + Assert.assertEquals(0, vectorTests.getFailed()); + } + } + + @Test + public void testCacophonyVector() throws Exception { + try (InputStream stream = new URL( + "https://raw.githubusercontent.com/centromere/cacophony/master/vectors/cacophony.txt").openStream()) { + VectorTests vectorTests = new VectorTests(); + vectorTests.processInputStream(stream); + Assert.assertEquals(0, vectorTests.getFailed()); + } + } +} diff --git a/src/test/java/com/southernstorm/noise/tests/VectorTests.java b/src/test/java/com/southernstorm/noise/tests/VectorTests.java new file mode 100644 index 0000000..af22bc2 --- /dev/null +++ b/src/test/java/com/southernstorm/noise/tests/VectorTests.java @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.tests; + +import static org.junit.Assert.*; + +import java.io.*; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; +import javax.xml.bind.DatatypeConverter; + +import com.southernstorm.json.JsonReader; +import com.southernstorm.noise.protocol.CipherState; +import com.southernstorm.noise.protocol.CipherStatePair; +import com.southernstorm.noise.protocol.HandshakeState; + +/** + * Executes Noise vector tests in JSON format. + */ +public class VectorTests { + + private int total; + private int failed; + private int skipped; + + public VectorTests() { + total = 0; + failed = 0; + skipped = 0; + } + + /** + * Information about a handshake or transport message. + */ + private class TestMessage { + public byte[] payload; + public byte[] ciphertext; + } + + /** + * Information about a Noise test vector that was parsed from a JSON stream. + */ + private class TestVector { + public String name; + public String pattern; + public String dh; + public String hybrid; + public String cipher; + public String hash; + public String fallback_pattern; + public byte[] init_prologue; + public byte[] init_ephemeral; + public byte[] init_hybrid; + public byte[] init_static; + public byte[] init_remote_static; + public byte[] init_psk; + public byte[] init_ssk; + public byte[] resp_prologue; + public byte[] resp_ephemeral; + public byte[] resp_hybrid; + public byte[] resp_static; + public byte[] resp_remote_static; + public byte[] resp_psk; + public byte[] resp_ssk; + public byte[] handshake_hash; + public boolean failure_expected; + public boolean fallback_expected; + public TestMessage[] messages; + + public void addMessage(TestMessage msg) { + TestMessage[] newMessages; + if (messages != null) { + newMessages = new TestMessage[messages.length + 1]; + System.arraycopy(messages, 0, newMessages, 0, messages.length); + newMessages[messages.length] = msg; + } else { + newMessages = new TestMessage[1]; + newMessages[0] = msg; + } + messages = newMessages; + } + } + + private void assertSubArrayEquals(String msg, byte[] expected, byte[] actual) { + for (int index = 0; index < expected.length; ++index) { + assertEquals(msg + "[" + Integer.toString(index) + "]", expected[index], actual[index]); + } + } + + /** + * Runs a Noise test vector. + * + * @param vec The test vector. + * @param initiator Handshake object for the initiator. + * @param responder Handshake object for the responder. + */ + private void runTest(TestVector vec, HandshakeState initiator, HandshakeState responder) + throws ShortBufferException, BadPaddingException, NoSuchAlgorithmException { + // Set all keys and special values that we need. + if (vec.init_prologue != null) { + initiator.setPrologue(vec.init_prologue, 0, vec.init_prologue.length); + } + if (vec.init_static != null) { + initiator.getLocalKeyPair().setPrivateKey(vec.init_static, 0); + } + if (vec.init_remote_static != null) { + initiator.getRemotePublicKey().setPublicKey(vec.init_remote_static, 0); + } + if (vec.init_hybrid != null) { + initiator.getFixedHybridKey().setPrivateKey(vec.init_hybrid, 0); + } + if (vec.init_ephemeral != null) { + initiator.getFixedEphemeralKey().setPrivateKey(vec.init_ephemeral, 0); + } + if (vec.init_psk != null) { + initiator.setPreSharedKey(vec.init_psk, 0, vec.init_psk.length); + } + if (vec.resp_prologue != null) { + responder.setPrologue(vec.resp_prologue, 0, vec.resp_prologue.length); + } + if (vec.resp_static != null) { + responder.getLocalKeyPair().setPrivateKey(vec.resp_static, 0); + } + if (vec.resp_remote_static != null) { + responder.getRemotePublicKey().setPublicKey(vec.resp_remote_static, 0); + } + if (vec.resp_ephemeral != null) { + // Note: The test data contains responder ephemeral keys for one-way + // patterns which doesn't actually make sense. Ignore those keys. + if (vec.pattern.length() != 1) { + responder.getFixedEphemeralKey().setPrivateKey(vec.resp_ephemeral, 0); + } + } + if (vec.resp_hybrid != null) { + responder.getFixedHybridKey().setPrivateKey(vec.resp_hybrid, 0); + } + if (vec.resp_psk != null) { + responder.setPreSharedKey(vec.resp_psk, 0, vec.resp_psk.length); + } + + // Start both sides of the handshake. + assertEquals(HandshakeState.NO_ACTION, initiator.getAction()); + assertEquals(HandshakeState.NO_ACTION, responder.getAction()); + initiator.start(); + responder.start(); + assertEquals(HandshakeState.WRITE_MESSAGE, initiator.getAction()); + assertEquals(HandshakeState.READ_MESSAGE, responder.getAction()); + + // Work through the messages one by one until both sides "split". + int role = HandshakeState.INITIATOR; + int index = 0; + HandshakeState send, recv; + boolean isOneWay = (vec.pattern.length() == 1); + boolean fallback = vec.fallback_expected; + byte[] message = new byte[8192]; + byte[] plaintext = new byte[8192]; + for (; index < vec.messages.length; ++index) { + if (initiator.getAction() == HandshakeState.SPLIT && responder.getAction() == HandshakeState.SPLIT) { + break; + } + if (role == HandshakeState.INITIATOR) { + // Send on the initiator, receive on the responder. + send = initiator; + recv = responder; + if (!isOneWay) { + role = HandshakeState.RESPONDER; + } + } else { + // Send on the responder, receive on the initiator. + send = responder; + recv = initiator; + role = HandshakeState.INITIATOR; + } + assertEquals(HandshakeState.WRITE_MESSAGE, send.getAction()); + assertEquals(HandshakeState.READ_MESSAGE, recv.getAction()); + TestMessage msg = vec.messages[index]; + int len = send.writeMessage(message, 0, msg.payload, 0, msg.payload.length); + assertEquals(msg.ciphertext.length, len); + assertSubArrayEquals(Integer.toString(index) + ": ciphertext", msg.ciphertext, message); + if (fallback) { + // Perform a read on the responder, which will fail. + try { + recv.readMessage(message, 0, len, plaintext, 0); + fail("read should have triggered fallback"); + } catch (BadPaddingException e) { + // Success! + } + + // Look up the pattern to fall back to. + String pattern = vec.fallback_pattern; + if (pattern == null) { + pattern = "XXfallback"; + } + + // Initiate fallback on both sides. + initiator.fallback(pattern); + responder.fallback(pattern); + + // Restart the protocols. + initiator.start(); + responder.start(); + + // Only need to fallback once. + fallback = false; + } else { + int plen = recv.readMessage(message, 0, len, plaintext, 0); + assertEquals(msg.payload.length, plen); + assertSubArrayEquals(Integer.toString(index) + ": payload", msg.payload, plaintext); + } + } + if (vec.fallback_expected) { + // The roles will have reversed during the handshake. + assertEquals(HandshakeState.RESPONDER, initiator.getRole()); + assertEquals(HandshakeState.INITIATOR, responder.getRole()); + } else { + assertEquals(HandshakeState.INITIATOR, initiator.getRole()); + assertEquals(HandshakeState.RESPONDER, responder.getRole()); + } + + // Handshake finished. Check the handshake hash values. + if (vec.handshake_hash != null) { + assertArrayEquals(vec.handshake_hash, initiator.getHandshakeHash()); + assertArrayEquals(vec.handshake_hash, responder.getHandshakeHash()); + } + + // Split the two sides to get the transport ciphers. + CipherStatePair initPair; + CipherStatePair respPair; + assertEquals(HandshakeState.SPLIT, initiator.getAction()); + assertEquals(HandshakeState.SPLIT, responder.getAction()); + if (vec.init_ssk != null) { + initPair = initiator.split(vec.init_ssk, 0, vec.init_ssk.length); + } else { + initPair = initiator.split(); + } + if (vec.resp_ssk != null) { + respPair = responder.split(vec.resp_ssk, 0, vec.resp_ssk.length); + } else { + respPair = responder.split(); + } + assertEquals(HandshakeState.COMPLETE, initiator.getAction()); + assertEquals(HandshakeState.COMPLETE, responder.getAction()); + + // Now handle the data transport. + CipherState csend, crecv; + for (; index < vec.messages.length; ++index) { + TestMessage msg = vec.messages[index]; + if (role == HandshakeState.INITIATOR) { + // Send on the initiator, receive on the responder. + csend = initPair.getSender(); + crecv = respPair.getReceiver(); + if (!isOneWay) { + role = HandshakeState.RESPONDER; + } + } else { + // Send on the responder, receive on the initiator. + csend = respPair.getSender(); + crecv = initPair.getReceiver(); + role = HandshakeState.INITIATOR; + } + int len = csend.encryptWithAd(null, msg.payload, 0, message, 0, msg.payload.length); + assertEquals(msg.ciphertext.length, len); + assertSubArrayEquals(Integer.toString(index) + ": ciphertext", msg.ciphertext, message); + int plen = crecv.decryptWithAd(null, message, 0, plaintext, 0, len); + assertEquals(msg.payload.length, plen); + assertSubArrayEquals(Integer.toString(index) + ": payload", msg.payload, plaintext); + } + + // Clean up. + initiator.destroy(); + responder.destroy(); + initPair.destroy(); + respPair.destroy(); + } + + /** + * Processes a single test vector from an input stream. + * + * @param reader The JSON reader for the input stream. + * + * The reader is positioned on the first field of the vector object. + */ + private void processVector(JsonReader reader) throws IOException { + boolean res = true; + // Parse the contents of the test vector. + TestVector vec = new TestVector(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("name") || name.equals("protocol_name")) { + vec.name = reader.nextString(); + } else if (name.equals("pattern")) { + vec.pattern = reader.nextString(); + } else if (name.equals("dh")) { + vec.dh = reader.nextString(); + } else if (name.equals("hybrid")) { + vec.hybrid = reader.nextString(); + } else if (name.equals("cipher")) { + vec.cipher = reader.nextString(); + } else if (name.equals("hash")) { + vec.hash = reader.nextString(); + } else if (name.equals("fallback_pattern")) { + vec.fallback_pattern = reader.nextString(); + } else if (name.equals("init_prologue")) { + vec.init_prologue = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("init_ephemeral")) { + vec.init_ephemeral = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("init_hybrid_ephemeral")) { + vec.init_hybrid = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("init_static")) { + vec.init_static = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("init_remote_static")) { + vec.init_remote_static = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("init_psks")) { + reader.beginArray(); + vec.init_psk = DatatypeConverter.parseHexBinary(reader.nextString()); + reader.endArray(); + } else if (name.equals("init_psk")) { + vec.init_psk = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("init_ssk")) { + vec.init_ssk = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_prologue")) { + vec.resp_prologue = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_ephemeral")) { + vec.resp_ephemeral = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_hybrid_ephemeral")) { + vec.resp_hybrid = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_static")) { + vec.resp_static = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_remote_static")) { + vec.resp_remote_static = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_psk")) { + vec.resp_psk = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("resp_psks")) { + reader.beginArray(); + vec.resp_psk = DatatypeConverter.parseHexBinary(reader.nextString()); + reader.endArray(); + } else if (name.equals("resp_ssk")) { + vec.resp_ssk = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("handshake_hash")) { + vec.handshake_hash = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("fail")) { + vec.failure_expected = reader.nextBoolean(); + } else if (name.equals("fallback")) { + vec.fallback_expected = reader.nextBoolean(); + } else if (name.equals("messages")) { + reader.beginArray(); + while (reader.hasNext()) { + TestMessage msg = new TestMessage(); + reader.beginObject(); + while (reader.hasNext()) { + name = reader.nextName(); + if (name.equals("payload")) { + msg.payload = DatatypeConverter.parseHexBinary(reader.nextString()); + } else if (name.equals("ciphertext")) { + msg.ciphertext = DatatypeConverter.parseHexBinary(reader.nextString()); + } else { + reader.skipValue(); + } + } + vec.addMessage(msg); + reader.endObject(); + } + reader.endArray(); + } else { + reader.skipValue(); + } + } + + // Format the complete protocol name. + String protocolName = "Noise"; + if (vec.init_psk != null || vec.resp_psk != null) { + protocolName += "PSK"; + } + String dh = vec.dh; + if (vec.hybrid != null) { + dh = dh + "+" + vec.hybrid; + } + protocolName += "_" + vec.pattern + "_" + dh + "_" + vec.cipher + "_" + vec.hash; + if (vec.name == null) { + vec.name = protocolName; + } else { + protocolName = vec.name; + } + + if (vec.pattern == null) { + vec.pattern = protocolName.split("_")[1]; + } + + // Execute the test vector. + ++total; + System.out.print(vec.name); + System.out.print(" ... "); + System.out.flush(); + try { + // TODO: Why are these special cases, what needs to be fixed? + if (protocolName.indexOf("_Xpsk1_") > -1 || protocolName.indexOf("_Kpsk0_") > -1 + || protocolName.indexOf("_Npsk0_") > -1) { + throw new NoSuchAlgorithmException("Unsupported for now " + protocolName); + } + HandshakeState initiator = new HandshakeState(protocolName, HandshakeState.INITIATOR); + HandshakeState responder = new HandshakeState(protocolName, HandshakeState.RESPONDER); + assertEquals(HandshakeState.INITIATOR, initiator.getRole()); + assertEquals(HandshakeState.RESPONDER, responder.getRole()); + assertEquals(protocolName, initiator.getProtocolName()); + assertEquals(protocolName, responder.getProtocolName()); + runTest(vec, initiator, responder); + if (!vec.failure_expected) { + System.out.println("ok"); + } else { + System.out.println("failure expected"); + ++failed; + } + } catch (NoSuchAlgorithmException | IllegalArgumentException e) { + System.out.println("unsupported " + e.getMessage()); + ++skipped; + } catch (AssertionError e) { + System.out.println(e.getMessage()); + // e.printStackTrace(System.out); + ++failed; + } catch (Exception e) { + if (!vec.failure_expected) { + System.out.println("failed " + e.getMessage()); + // e.printStackTrace(System.out); + ++failed; + } else { + System.out.println("ok"); + } + } + } + + public void processFile(String filename) throws IOException { + try { + try (FileInputStream fileStream = new FileInputStream(filename)) { + System.out.print(filename + ": "); + processInputStream(fileStream); + } + } catch (FileNotFoundException e) { + System.err.println(filename + ": File not found"); + } + } + + public void processInputStream(InputStream jsonInputStream) throws IOException { + try (Reader streamReader = new BufferedReader(new InputStreamReader(jsonInputStream))) { + processReader(streamReader); + } + } + + public void processReader(Reader jsonStream) throws IOException { + total = 0; + skipped = 0; + failed = 0; + JsonReader reader = new JsonReader(jsonStream); + try { + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("vectors")) { + reader.beginArray(); + while (reader.hasNext() /* && total < 50 */) { + reader.beginObject(); + processVector(reader); + reader.endObject(); + } + reader.endArray(); + } else { + reader.skipValue(); + } + } + reader.endObject(); + } catch (IOException e) { + System.err.println("Exception while parsing JSON: " + e.toString()); + e.printStackTrace(); + } finally { + reader.close(); + } + System.out.print(total); + System.out.print(" tests, "); + System.out.print(skipped); + System.out.print(" skipped, "); + System.out.print(failed); + System.out.println(" failed"); + } + + public int getTotal() { + return total; + } + + public int getFailed() { + return failed; + } + + public int getSkipped() { + return skipped; + } + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + System.out.println("Usage: VectorTests file1 file2 ..."); + return; + } + VectorTests app = new VectorTests(); + for (String filename : args) { + app.processFile(filename); + } + } +} diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeEmulator.java b/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeEmulator.java index bd6149e..a7634f8 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeEmulator.java +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeEmulator.java @@ -15,15 +15,17 @@ import com.google.protobuf.GeneratedMessageV3; -import no.seime.openhab.binding.esphome.internal.comm.PlainTextStreamHandler; +import no.seime.openhab.binding.esphome.internal.comm.AbstractFrameHelper; +import no.seime.openhab.binding.esphome.internal.comm.CommunicationError; +import no.seime.openhab.binding.esphome.internal.comm.ProtocolAPIError; import no.seime.openhab.binding.esphome.internal.comm.ProtocolException; public class ESPHomeEmulator { private final Logger logger = LoggerFactory.getLogger(ESPHomeEmulator.class); - private InetSocketAddress listenAddress; - private PacketListener packetListener; + private final InetSocketAddress listenAddress; + private final AbstractFrameHelper frameHelper; private boolean keepRunning = true; private boolean ready = false; @@ -36,16 +38,13 @@ public boolean isReady() { private SocketChannel channel; - private PlainTextStreamHandler streamHandler; - - public ESPHomeEmulator(InetSocketAddress listenAddress) { + public ESPHomeEmulator(InetSocketAddress listenAddress, AbstractFrameHelper frameHelper) { this.listenAddress = listenAddress; + this.frameHelper = frameHelper; } public void start() { - streamHandler = new PlainTextStreamHandler(packetListener); - Thread serverThread = new Thread(() -> { try { @@ -81,10 +80,10 @@ public void start() { int numBytes = channel.read(buffer); if (numBytes > 0) { try { - streamHandler.processReceivedData(buffer); + frameHelper.processReceivedData(buffer); } catch (ProtocolException e) { channel.close(); - streamHandler.onParseError(e); + frameHelper.onParseError(CommunicationError.PACKET_ERROR); } } else { logger.debug("No data"); @@ -110,8 +109,8 @@ public void stop() throws IOException { selector.close(); } - public void sendPacket(GeneratedMessageV3 message) throws IOException { - ByteBuffer buffer = ByteBuffer.wrap(streamHandler.encodeFrame(message)); + public void sendPacket(GeneratedMessageV3 message) throws IOException, ProtocolAPIError { + ByteBuffer buffer = frameHelper.encodeFrame(message); while (buffer.hasRemaining()) { logger.trace("Writing data"); @@ -119,7 +118,7 @@ public void sendPacket(GeneratedMessageV3 message) throws IOException { } } - public void setPacketListener(PacketListener packetListener) { - this.packetListener = packetListener; + public void setPacketListener(CommunicationListener communicationListener) { + frameHelper.setPacketListener(communicationListener); } } diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerLiveTest.java b/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerLiveTest.java new file mode 100644 index 0000000..56efbb4 --- /dev/null +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerLiveTest.java @@ -0,0 +1,83 @@ +package no.seime.openhab.binding.esphome.internal; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.internal.ThingImpl; + +import no.seime.openhab.binding.esphome.internal.comm.ConnectionSelector; +import no.seime.openhab.binding.esphome.internal.handler.ESPChannelTypeProvider; +import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler; + +/** + * + * @author Arne Seime - Initial contribution + */ + +@ExtendWith(MockitoExtension.class) +class ESPHomeHandlerLiveTest { + + private @Mock Configuration configuration; + private @Mock ESPChannelTypeProvider channelTypeProvider; + + private Thing thing; + + private ESPHomeHandler deviceHandler; + + private ThingHandlerCallback thingHandlerCallback; + + ESPHomeConfiguration deviceConfiguration; + + ConnectionSelector selector; + + @BeforeEach + public void setUp() throws Exception { + + deviceConfiguration = new ESPHomeConfiguration(); + deviceConfiguration.hostname = "localhost"; + deviceConfiguration.port = 6053; + // deviceConfiguration.password = "MyPassword"; + deviceConfiguration.encryptionKey = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA="; + deviceConfiguration.server = "virtual"; + when(configuration.as(ESPHomeConfiguration.class)).thenReturn(deviceConfiguration); + + selector = new ConnectionSelector(); + selector.start(); + + thing = createThing(); + deviceHandler = Mockito.spy(new ESPHomeHandler(thing, selector, channelTypeProvider)); + thingHandlerCallback = Mockito.mock(ThingHandlerCallback.class); + deviceHandler.setCallback(thingHandlerCallback); + } + + @AfterEach + public void shutdown() { + selector.stop(); + deviceHandler.dispose(); + } + + // @Test + public void testConnectToDeviceOnLocalhost() { + deviceHandler.initialize(); + await().until(() -> deviceHandler.isInterrogated()); + assertEquals(1, deviceHandler.getDynamicChannels().size()); + deviceHandler.dispose(); + } + + private ThingImpl createThing() { + ThingImpl thing = new ThingImpl(BindingConstants.THING_TYPE_DEVICE, "device"); + + thing.setConfiguration(configuration); + return thing; + } +} diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerTest.java b/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerTest.java index 7bfd105..8bf9e43 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerTest.java +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/ESPHomeHandlerTest.java @@ -22,7 +22,8 @@ import org.openhab.core.thing.internal.ThingImpl; import no.seime.openhab.binding.esphome.internal.comm.ConnectionSelector; -import no.seime.openhab.binding.esphome.internal.comm.LogReadingPacketListener; +import no.seime.openhab.binding.esphome.internal.comm.LogReadingCommunicationListener; +import no.seime.openhab.binding.esphome.internal.comm.PlainTextFrameHelper; import no.seime.openhab.binding.esphome.internal.handler.ESPChannelTypeProvider; import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler; @@ -74,9 +75,11 @@ public void shutdown() { void testInitializeEverythingPresenceSensor() throws IOException, InvocationTargetException, IllegalAccessException { - ESPHomeEmulator emulator = new ESPHomeEmulator(new InetSocketAddress("localhost", 10000)); - emulator.setPacketListener( - new LogReadingPacketListener(emulator, new File("src/test/resources/logfiles/presence_sensor.log"))); + ESPHomeEmulator emulator = new ESPHomeEmulator(new InetSocketAddress("localhost", 10000), + new PlainTextFrameHelper(null, null, "emulator")); + + emulator.setPacketListener(new LogReadingCommunicationListener(emulator, + new File("src/test/resources/logfiles/presence_sensor.log"))); emulator.start(); deviceHandler.initialize(); diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/EnumHelperTest.java b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/EnumHelperTest.java index b5f751a..acd3ac5 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/EnumHelperTest.java +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/EnumHelperTest.java @@ -5,13 +5,13 @@ import org.junit.jupiter.api.Test; import io.esphome.api.ClimateMode; -import no.seime.openhab.binding.esphome.internal.ClimateEnumHelper; +import no.seime.openhab.binding.esphome.internal.message.ClimateMessageHandler; public class EnumHelperTest { @Test void stripEnum() { - assertEquals("COOL", ClimateEnumHelper.stripEnumPrefix(ClimateMode.CLIMATE_MODE_COOL)); + assertEquals("COOL", ClimateMessageHandler.ClimateEnumHelper.stripEnumPrefix(ClimateMode.CLIMATE_MODE_COOL)); } } diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogParser.java b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogParser.java index 535a1c2..d540c13 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogParser.java +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogParser.java @@ -15,7 +15,7 @@ public class LogParser { - private MessageTypeToClassConverter messageTypeToClassConverter = new MessageTypeToClassConverter(); + private final MessageTypeToClassConverter messageTypeToClassConverter = new MessageTypeToClassConverter(); public List parseLog(File log) throws IOException, InvocationTargetException, IllegalAccessException { @@ -51,10 +51,7 @@ private GeneratedMessageV3 parseMessage(String messageType, String messageData) if (parseMethod != null) { GeneratedMessageV3 invoke = (GeneratedMessageV3) parseMethod.invoke(null, fromString(messageData)); - if (invoke != null) { - return invoke; - - } + return invoke; } return null; } @@ -65,7 +62,7 @@ private static byte[] fromString(String string) { } String[] strings = string.replace("[", "").replace("]", "").split(", "); - byte result[] = new byte[strings.length]; + byte[] result = new byte[strings.length]; for (int i = 0; i < result.length; i++) { result[i] = (byte) Integer.parseInt(strings[i]); } diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogReadingPacketListener.java b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogReadingCommunicationListener.java similarity index 75% rename from src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogReadingPacketListener.java rename to src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogReadingCommunicationListener.java index c336624..aad5310 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogReadingPacketListener.java +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/LogReadingCommunicationListener.java @@ -10,30 +10,18 @@ import com.google.protobuf.GeneratedMessageV3; -import io.esphome.api.ConnectRequest; -import io.esphome.api.ConnectResponse; -import io.esphome.api.DeviceInfoRequest; -import io.esphome.api.DeviceInfoResponse; -import io.esphome.api.DisconnectRequest; -import io.esphome.api.DisconnectResponse; -import io.esphome.api.HelloRequest; -import io.esphome.api.HelloResponse; -import io.esphome.api.ListEntitiesDoneResponse; -import io.esphome.api.ListEntitiesRequest; -import io.esphome.api.PingRequest; -import io.esphome.api.PingResponse; -import io.esphome.api.SubscribeStatesRequest; +import io.esphome.api.*; +import no.seime.openhab.binding.esphome.internal.CommunicationListener; import no.seime.openhab.binding.esphome.internal.ESPHomeEmulator; -import no.seime.openhab.binding.esphome.internal.PacketListener; -public class LogReadingPacketListener implements PacketListener { +public class LogReadingCommunicationListener implements CommunicationListener { - private final Logger logger = LoggerFactory.getLogger(LogReadingPacketListener.class); + private final Logger logger = LoggerFactory.getLogger(LogReadingCommunicationListener.class); private final ESPHomeEmulator emulator; List responseMessages; - public LogReadingPacketListener(ESPHomeEmulator emulator, File logFile) + public LogReadingCommunicationListener(ESPHomeEmulator emulator, File logFile) throws IOException, InvocationTargetException, IllegalAccessException { this.emulator = emulator; LogParser logParser = new LogParser(); @@ -41,7 +29,7 @@ public LogReadingPacketListener(ESPHomeEmulator emulator, File logFile) } @Override - public void onPacket(GeneratedMessageV3 message) throws IOException { + public void onPacket(GeneratedMessageV3 message) throws IOException, ProtocolAPIError { if (message instanceof HelloRequest) { emulator.sendPacket(responseMessages.stream().filter(e -> e instanceof HelloResponse).findFirst().get()); } else if (message instanceof DeviceInfoRequest) { @@ -59,7 +47,7 @@ public void onPacket(GeneratedMessageV3 message) throws IOException { try { logger.debug("Sending list entities response {}", m); emulator.sendPacket(m); - } catch (IOException e) { + } catch (IOException | ProtocolAPIError e) { throw new RuntimeException(e); } }); @@ -71,7 +59,7 @@ public void onPacket(GeneratedMessageV3 message) throws IOException { .forEach(m -> { try { emulator.sendPacket(m); - } catch (IOException e) { + } catch (IOException | ProtocolAPIError e) { throw new RuntimeException(e); } }); @@ -84,6 +72,10 @@ public void onEndOfStream() { } @Override - public void onParseError() { + public void onParseError(CommunicationError error) { + } + + @Override + public void onConnect() { } } diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextFrameHelperTest.java b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextFrameHelperTest.java new file mode 100644 index 0000000..8034e38 --- /dev/null +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextFrameHelperTest.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2023 Contributors to the Seime Openhab Addons project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package no.seime.openhab.binding.esphome.internal.comm; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import com.google.protobuf.GeneratedMessageV3; + +import io.esphome.api.HelloRequest; +import io.esphome.api.HelloResponse; +import no.seime.openhab.binding.esphome.internal.CommunicationListener; +import no.seime.openhab.binding.esphome.internal.ESPHomeEmulator; + +public class PlainTextFrameHelperTest { + + boolean responseReceived = false; + + private final ConnectionSelector connectionSelector = new ConnectionSelector(); + + private final InetSocketAddress serverAddress = new InetSocketAddress("localhost", + new Random().nextInt(10000) + 10000); + + public PlainTextFrameHelperTest() throws IOException { + } + + @Test + void testParsePacket() throws IOException { + + final ESPHomeEmulator espHomeDevice = getEspHomeEmulator(); + + try { + + // Send a packet to a server and receive a response. Verify that both messages are parsed correctly. + + espHomeDevice.start(); + await().until(() -> espHomeDevice.isReady()); + + connectionSelector.start(); + + PlainTextFrameHelper serverConnection = new PlainTextFrameHelper(connectionSelector, null, "openhab"); + serverConnection.setPacketListener(new CommunicationListener() { + @Override + public void onPacket(GeneratedMessageV3 message) { + System.out.println("Received packet: " + message); + assertInstanceOf(HelloResponse.class, message); + responseReceived = true; + } + + @Override + public void onEndOfStream() { + fail(); + } + + @Override + public void onParseError(CommunicationError error) { + fail(error.toString()); + } + + @Override + public void onConnect() throws ProtocolAPIError { + HelloRequest helloRequest = HelloRequest.newBuilder().setClientInfo("openHAB").setApiVersionMajor(1) + .setApiVersionMinor(7).build(); + serverConnection.send(helloRequest); + } + }); + + serverConnection.connect(serverAddress); + + // Send hello request from client to server + + await().until(() -> responseReceived); + } catch (Exception e) { + fail(e); + + } finally { + espHomeDevice.stop(); + connectionSelector.stop(); + } + } + + private ESPHomeEmulator getEspHomeEmulator() { + final ESPHomeEmulator espHomeDevice; + + espHomeDevice = new ESPHomeEmulator(serverAddress, new PlainTextFrameHelper(null, null, "emulator")); + espHomeDevice.setPacketListener(new CommunicationListener() { + @Override + public void onPacket(GeneratedMessageV3 message) throws IOException, ProtocolAPIError { + System.out.println("Received packet: " + message); + assertInstanceOf(HelloRequest.class, message); + + // Respond with hello response + HelloResponse helloResponse = HelloResponse.newBuilder().setApiVersionMajor(1).setApiVersionMinor(7) + .setServerInfo("ESPHome 1.7.3").build(); + espHomeDevice.sendPacket(helloResponse); + } + + @Override + public void onEndOfStream() { + fail(); + } + + @Override + public void onParseError(CommunicationError error) { + fail(error.toString()); + } + + @Override + public void onConnect() { + } + }); + return espHomeDevice; + } +} diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextStreamHandlerTest.java b/src/test/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextStreamHandlerTest.java deleted file mode 100644 index 2df6019..0000000 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/comm/PlainTextStreamHandlerTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2023 Contributors to the Seime Openhab Addons project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package no.seime.openhab.binding.esphome.internal.comm; - -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.Random; - -import org.junit.jupiter.api.Test; - -import com.google.protobuf.GeneratedMessageV3; - -import io.esphome.api.HelloRequest; -import io.esphome.api.HelloResponse; -import no.seime.openhab.binding.esphome.internal.ESPHomeEmulator; -import no.seime.openhab.binding.esphome.internal.PacketListener; - -public class PlainTextStreamHandlerTest { - - boolean responseReceived = false; - - private ConnectionSelector connectionSelector = new ConnectionSelector(); - - private InetSocketAddress serverAddress = new InetSocketAddress("localhost", new Random().nextInt(10000) + 10000); - - public PlainTextStreamHandlerTest() throws IOException { - } - - @Test - void testParsePacket() throws IOException { - - final ESPHomeEmulator espHomeDevice; - - espHomeDevice = new ESPHomeEmulator(serverAddress); - espHomeDevice.setPacketListener(new PacketListener() { - @Override - public void onPacket(GeneratedMessageV3 message) throws IOException { - System.out.println("Received packet: " + message); - - // Respond with hello response - HelloResponse helloResponse = HelloResponse.newBuilder().setApiVersionMajor(1).setApiVersionMinor(7) - .setServerInfo("ESPHome 1.7.3").build(); - espHomeDevice.sendPacket(helloResponse); - } - - @Override - public void onEndOfStream() { - fail(); - } - - @Override - public void onParseError() { - fail(); - } - }); - - try { - - // Send a packet to a server and receive a response. Verify that both messages are parsed correctly. - - espHomeDevice.start(); - await().until(() -> espHomeDevice.isReady()); - - connectionSelector.start(); - ESPHomeConnection clientConnection = new ESPHomeConnection(connectionSelector, - new PlainTextStreamHandler(new PacketListener() { - @Override - public void onPacket(GeneratedMessageV3 message) { - System.out.println("Received packet: " + message); - assertTrue(message instanceof HelloResponse); - responseReceived = true; - } - - @Override - public void onEndOfStream() { - fail(); - } - - @Override - public void onParseError() { - fail(); - } - }), "localhost"); - - clientConnection.connect(serverAddress); - - // Send hello request from client to server - HelloRequest helloRequest = HelloRequest.newBuilder().setClientInfo("openHAB").setApiVersionMajor(1) - .setApiVersionMinor(7).build(); - clientConnection.send(helloRequest); - - await().until(() -> responseReceived); - } catch (Exception e) { - fail(e); - - } finally { - espHomeDevice.stop(); - connectionSelector.stop(); - } - } -}