diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 05675554..fcf277e6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -226,5 +226,21 @@
android:name=".activities.FCastGuideActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/LittleEndianDataInputStream.kt b/app/src/main/java/com/futo/platformplayer/LittleEndianDataInputStream.kt
new file mode 100644
index 00000000..c09738bb
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/LittleEndianDataInputStream.kt
@@ -0,0 +1,192 @@
+package com.futo.platformplayer
+
+import com.google.common.base.Preconditions
+import com.google.common.io.ByteStreams
+import com.google.common.primitives.Ints
+import com.google.common.primitives.Longs
+import java.io.DataInput
+import java.io.DataInputStream
+import java.io.EOFException
+import java.io.FilterInputStream
+import java.io.IOException
+import java.io.InputStream
+
+class LittleEndianDataInputStream
+/**
+ * Creates a `LittleEndianDataInputStream` that wraps the given stream.
+ *
+ * @param in the stream to delegate to
+ */
+ (`in`: InputStream?) : FilterInputStream(Preconditions.checkNotNull(`in`)), DataInput {
+ /** This method will throw an [UnsupportedOperationException]. */
+ override fun readLine(): String {
+ throw UnsupportedOperationException("readLine is not supported")
+ }
+
+ @Throws(IOException::class)
+ override fun readFully(b: ByteArray) {
+ ByteStreams.readFully(this, b)
+ }
+
+ @Throws(IOException::class)
+ override fun readFully(b: ByteArray, off: Int, len: Int) {
+ ByteStreams.readFully(this, b, off, len)
+ }
+
+ @Throws(IOException::class)
+ override fun skipBytes(n: Int): Int {
+ return `in`.skip(n.toLong()).toInt()
+ }
+
+ @Throws(IOException::class)
+ override fun readUnsignedByte(): Int {
+ val b1 = `in`.read()
+ if (0 > b1) {
+ throw EOFException()
+ }
+
+ return b1
+ }
+
+ /**
+ * Reads an unsigned `short` as specified by [DataInputStream.readUnsignedShort],
+ * except using little-endian byte order.
+ *
+ * @return the next two bytes of the input stream, interpreted as an unsigned 16-bit integer in
+ * little-endian byte order
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun readUnsignedShort(): Int {
+ val b1 = readAndCheckByte()
+ val b2 = readAndCheckByte()
+
+ return Ints.fromBytes(0.toByte(), 0.toByte(), b2, b1)
+ }
+
+ /**
+ * Reads an integer as specified by [DataInputStream.readInt], except using little-endian
+ * byte order.
+ *
+ * @return the next four bytes of the input stream, interpreted as an `int` in little-endian
+ * byte order
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun readInt(): Int {
+ val b1 = readAndCheckByte()
+ val b2 = readAndCheckByte()
+ val b3 = readAndCheckByte()
+ val b4 = readAndCheckByte()
+
+ return Ints.fromBytes(b4, b3, b2, b1)
+ }
+
+ /**
+ * Reads a `long` as specified by [DataInputStream.readLong], except using
+ * little-endian byte order.
+ *
+ * @return the next eight bytes of the input stream, interpreted as a `long` in
+ * little-endian byte order
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun readLong(): Long {
+ val b1 = readAndCheckByte()
+ val b2 = readAndCheckByte()
+ val b3 = readAndCheckByte()
+ val b4 = readAndCheckByte()
+ val b5 = readAndCheckByte()
+ val b6 = readAndCheckByte()
+ val b7 = readAndCheckByte()
+ val b8 = readAndCheckByte()
+
+ return Longs.fromBytes(b8, b7, b6, b5, b4, b3, b2, b1)
+ }
+
+ /**
+ * Reads a `float` as specified by [DataInputStream.readFloat], except using
+ * little-endian byte order.
+ *
+ * @return the next four bytes of the input stream, interpreted as a `float` in
+ * little-endian byte order
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun readFloat(): Float {
+ return java.lang.Float.intBitsToFloat(readInt())
+ }
+
+ /**
+ * Reads a `double` as specified by [DataInputStream.readDouble], except using
+ * little-endian byte order.
+ *
+ * @return the next eight bytes of the input stream, interpreted as a `double` in
+ * little-endian byte order
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun readDouble(): Double {
+ return java.lang.Double.longBitsToDouble(readLong())
+ }
+
+ @Throws(IOException::class)
+ override fun readUTF(): String {
+ return DataInputStream(`in`).readUTF()
+ }
+
+ /**
+ * Reads a `short` as specified by [DataInputStream.readShort], except using
+ * little-endian byte order.
+ *
+ * @return the next two bytes of the input stream, interpreted as a `short` in little-endian
+ * byte order.
+ * @throws IOException if an I/O error occurs.
+ */
+ @Throws(IOException::class)
+ override fun readShort(): Short {
+ return readUnsignedShort().toShort()
+ }
+
+ /**
+ * Reads a char as specified by [DataInputStream.readChar], except using little-endian
+ * byte order.
+ *
+ * @return the next two bytes of the input stream, interpreted as a `char` in little-endian
+ * byte order
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun readChar(): Char {
+ return readUnsignedShort().toChar()
+ }
+
+ @Throws(IOException::class)
+ override fun readByte(): Byte {
+ return readUnsignedByte().toByte()
+ }
+
+ @Throws(IOException::class)
+ override fun readBoolean(): Boolean {
+ return readUnsignedByte() != 0
+ }
+
+ /**
+ * Reads a byte from the input stream checking that the end of file (EOF) has not been
+ * encountered.
+ *
+ * @return byte read from input
+ * @throws IOException if an error is encountered while reading
+ * @throws EOFException if the end of file (EOF) is encountered.
+ */
+ @Throws(IOException::class, EOFException::class)
+ private fun readAndCheckByte(): Byte {
+ val b1 = `in`.read()
+
+ if (-1 == b1) {
+ throw EOFException()
+ }
+
+ return b1.toByte()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/LittleEndianDataOutputStream.kt b/app/src/main/java/com/futo/platformplayer/LittleEndianDataOutputStream.kt
new file mode 100644
index 00000000..d84388d7
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/LittleEndianDataOutputStream.kt
@@ -0,0 +1,144 @@
+package com.futo.platformplayer
+
+import com.google.common.base.Preconditions
+import com.google.common.primitives.Longs
+import java.io.*
+
+class LittleEndianDataOutputStream
+ /**
+ * Creates a `LittleEndianDataOutputStream` that wraps the given stream.
+ *
+ * @param out the stream to delegate to
+ */
+ (out: OutputStream?) : FilterOutputStream(DataOutputStream(Preconditions.checkNotNull(out))),
+ DataOutput {
+ @Throws(IOException::class)
+ override fun write(b: ByteArray, off: Int, len: Int) {
+ // Override slow FilterOutputStream impl
+ out.write(b, off, len)
+ }
+
+ @Throws(IOException::class)
+ override fun writeBoolean(v: Boolean) {
+ (out as DataOutputStream).writeBoolean(v)
+ }
+
+ @Throws(IOException::class)
+ override fun writeByte(v: Int) {
+ (out as DataOutputStream).writeByte(v)
+ }
+
+ @Deprecated(
+ """The semantics of {@code writeBytes(String s)} are considered dangerous. Please use
+ {@link #writeUTF(String s)}, {@link #writeChars(String s)} or another write method instead."""
+ )
+ @Throws(
+ IOException::class
+ )
+ override fun writeBytes(s: String) {
+ (out as DataOutputStream).writeBytes(s)
+ }
+
+ /**
+ * Writes a char as specified by [DataOutputStream.writeChar], except using
+ * little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeChar(v: Int) {
+ writeShort(v)
+ }
+
+ /**
+ * Writes a `String` as specified by [DataOutputStream.writeChars], except
+ * each character is written using little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeChars(s: String) {
+ for (i in 0 until s.length) {
+ writeChar(s[i].code)
+ }
+ }
+
+ /**
+ * Writes a `double` as specified by [DataOutputStream.writeDouble], except
+ * using little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeDouble(v: Double) {
+ writeLong(java.lang.Double.doubleToLongBits(v))
+ }
+
+ /**
+ * Writes a `float` as specified by [DataOutputStream.writeFloat], except using
+ * little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeFloat(v: Float) {
+ writeInt(java.lang.Float.floatToIntBits(v))
+ }
+
+ /**
+ * Writes an `int` as specified by [DataOutputStream.writeInt], except using
+ * little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeInt(v: Int) {
+ val bytes = byteArrayOf(
+ (0xFF and v).toByte(),
+ (0xFF and (v shr 8)).toByte(),
+ (0xFF and (v shr 16)).toByte(),
+ (0xFF and (v shr 24)).toByte()
+ )
+ out.write(bytes)
+ }
+
+ /**
+ * Writes a `long` as specified by [DataOutputStream.writeLong], except using
+ * little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeLong(v: Long) {
+ val bytes = Longs.toByteArray(java.lang.Long.reverseBytes(v))
+ write(bytes, 0, bytes.size)
+ }
+
+ /**
+ * Writes a `short` as specified by [DataOutputStream.writeShort], except using
+ * little-endian byte order.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Throws(IOException::class)
+ override fun writeShort(v: Int) {
+ val bytes = byteArrayOf(
+ (0xFF and v).toByte(),
+ (0xFF and (v shr 8)).toByte()
+ )
+ out.write(bytes)
+ }
+
+ @Throws(IOException::class)
+ override fun writeUTF(str: String) {
+ (out as DataOutputStream).writeUTF(str)
+ }
+
+ // Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior:
+ // it silently ignores any exception thrown by flush(). Instead, just close the delegate stream.
+ // It should flush itself if necessary.
+ @Throws(IOException::class)
+ override fun close() {
+ out.close()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt
index 784c2c36..38168b5f 100644
--- a/app/src/main/java/com/futo/platformplayer/Settings.kt
+++ b/app/src/main/java/com/futo/platformplayer/Settings.kt
@@ -14,6 +14,7 @@ import com.futo.platformplayer.activities.ManageTabsActivity
import com.futo.platformplayer.activities.PolycentricHomeActivity
import com.futo.platformplayer.activities.PolycentricProfileActivity
import com.futo.platformplayer.activities.SettingsActivity
+import com.futo.platformplayer.activities.SyncHomeActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
@@ -63,6 +64,15 @@ class Settings : FragmentedStorageFileJson() {
@Transient
val onTabsChanged = Event0();
+ @FormField(R.string.sync_grayjay, FieldForm.BUTTON, R.string.sync_grayjay_description, -8)
+ @FormFieldButton(R.drawable.ic_update)
+ fun syncGrayjay() {
+ SettingsActivity.getActivity()?.let {
+ it.startActivity(Intent(it, SyncHomeActivity::class.java))
+ }
+ }
+
+
@FormField(R.string.manage_polycentric_identity, FieldForm.BUTTON, R.string.manage_your_polycentric_identity, -7)
@FormFieldButton(R.drawable.ic_person)
fun managePolycentricIdentity() {
@@ -901,7 +911,24 @@ class Settings : FragmentedStorageFileJson() {
var pan: Boolean = true;
}
- @FormField(R.string.info, FieldForm.GROUP, -1, 20)
+ @FormField(R.string.synchronization, FieldForm.GROUP, -1, 20)
+ var synchronization = Synchronization();
+ @Serializable
+ class Synchronization {
+ @FormField(R.string.enabled, FieldForm.TOGGLE, R.string.enabled_description, 1)
+ var enabled: Boolean = true;
+
+ @FormField(R.string.broadcast, FieldForm.TOGGLE, R.string.broadcast_description, 1)
+ var broadcast: Boolean = true;
+
+ @FormField(R.string.connect_discovered, FieldForm.TOGGLE, R.string.connect_discovered_description, 2)
+ var connectDiscovered: Boolean = true;
+
+ @FormField(R.string.connect_last, FieldForm.TOGGLE, R.string.connect_last_description, 3)
+ var connectLast: Boolean = true;
+ }
+
+ @FormField(R.string.info, FieldForm.GROUP, -1, 21)
var info = Info();
@Serializable
class Info {
diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt
index 6e2dc7b8..3eea4f26 100644
--- a/app/src/main/java/com/futo/platformplayer/Utility.kt
+++ b/app/src/main/java/com/futo/platformplayer/Utility.kt
@@ -26,10 +26,12 @@ import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.PlatformVideoWithTime
import com.futo.platformplayer.others.PlatformLinkMovementMethod
+import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
+import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.ThreadLocalRandom
diff --git a/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt
new file mode 100644
index 00000000..cde05c0d
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt
@@ -0,0 +1,138 @@
+package com.futo.platformplayer.activities
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageButton
+import android.widget.LinearLayout
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.futo.platformplayer.R
+import com.futo.platformplayer.setNavigationBarColorAndIcons
+import com.futo.platformplayer.states.StateApp
+import com.futo.platformplayer.states.StateSync
+import com.futo.platformplayer.sync.LinkType
+import com.futo.platformplayer.sync.SyncSession
+import com.futo.platformplayer.views.sync.SyncDeviceView
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class SyncHomeActivity : AppCompatActivity() {
+ private lateinit var _layoutDevices: LinearLayout
+ private lateinit var _layoutEmpty: LinearLayout
+ private val _viewMap: MutableMap = mutableMapOf()
+
+ override fun attachBaseContext(newBase: Context?) {
+ super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_sync_home)
+ setNavigationBarColorAndIcons()
+
+ _layoutDevices = findViewById(R.id.layout_devices)
+ _layoutEmpty = findViewById(R.id.layout_empty)
+
+ findViewById(R.id.button_back).setOnClickListener {
+ finish()
+ }
+
+ findViewById(R.id.button_link_new_device).setOnClickListener {
+ startActivity(Intent(this@SyncHomeActivity, SyncPairActivity::class.java))
+ }
+
+ findViewById(R.id.button_show_pairing_code).setOnClickListener {
+ startActivity(Intent(this@SyncHomeActivity, SyncShowPairingCodeActivity::class.java))
+ }
+
+ initializeDevices()
+
+ StateSync.instance.deviceUpdatedOrAdded.subscribe(this) { publicKey, session ->
+ lifecycleScope.launch(Dispatchers.Main) {
+ val view = _viewMap[publicKey]
+ if (!session.isAuthorized) {
+ if (view != null) {
+ _layoutDevices.removeView(view)
+ _viewMap.remove(publicKey)
+ }
+ return@launch
+ }
+
+ if (view == null) {
+ val syncDeviceView = SyncDeviceView(this@SyncHomeActivity)
+ syncDeviceView.onRemove.subscribe {
+ StateApp.instance.scopeOrNull?.launch {
+ StateSync.instance.delete(publicKey)
+ }
+ }
+ val v = updateDeviceView(syncDeviceView, publicKey, session)
+ _layoutDevices.addView(v, 0)
+ _viewMap[publicKey] = v
+ } else {
+ updateDeviceView(view, publicKey, session)
+ }
+
+ updateEmptyVisibility()
+ }
+ }
+
+ StateSync.instance.deviceRemoved.subscribe(this) {
+ lifecycleScope.launch(Dispatchers.Main) {
+ val view = _viewMap[it]
+ if (view != null) {
+ _layoutDevices.removeView(view)
+ _viewMap.remove(it)
+ }
+
+ updateEmptyVisibility()
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ StateSync.instance.deviceUpdatedOrAdded.remove(this)
+ StateSync.instance.deviceRemoved.remove(this)
+ }
+
+ private fun updateDeviceView(syncDeviceView: SyncDeviceView, publicKey: String, session: SyncSession?): SyncDeviceView {
+ val connected = session?.connected ?: false
+ syncDeviceView.setLinkType(if (connected) LinkType.Local else LinkType.None)
+ .setName(publicKey)
+ .setStatus(if (connected) "Connected" else "Disconnected")
+ return syncDeviceView
+ }
+
+ private fun updateEmptyVisibility() {
+ if (_viewMap.isNotEmpty()) {
+ _layoutEmpty.visibility = View.GONE
+ } else {
+ _layoutEmpty.visibility = View.VISIBLE
+ }
+ }
+
+ private fun initializeDevices() {
+ _layoutDevices.removeAllViews()
+
+ for (publicKey in StateSync.instance.getAll()) {
+ val syncDeviceView = SyncDeviceView(this)
+ syncDeviceView.onRemove.subscribe {
+ StateApp.instance.scopeOrNull?.launch {
+ StateSync.instance.delete(publicKey)
+ }
+ }
+ val view = updateDeviceView(syncDeviceView, publicKey, StateSync.instance.getSession(publicKey))
+ _layoutDevices.addView(view)
+ _viewMap[publicKey] = view
+ }
+
+ updateEmptyVisibility()
+ }
+
+ companion object {
+ private const val TAG = "SyncHomeActivity"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/activities/SyncPairActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SyncPairActivity.kt
new file mode 100644
index 00000000..ca3ce443
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/activities/SyncPairActivity.kt
@@ -0,0 +1,150 @@
+package com.futo.platformplayer.activities
+
+import android.content.Context
+import android.os.Bundle
+import android.util.Base64
+import android.util.Log
+import android.view.View
+import android.widget.EditText
+import android.widget.ImageButton
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.futo.platformplayer.R
+import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.setNavigationBarColorAndIcons
+import com.futo.platformplayer.states.StateApp
+import com.futo.platformplayer.states.StateSync
+import com.futo.platformplayer.sync.SyncDeviceInfo
+import com.futo.platformplayer.sync.SyncSocketSession
+import com.google.zxing.integration.android.IntentIntegrator
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.json.Json
+import java.nio.ByteBuffer
+import java.util.Random
+
+class SyncPairActivity : AppCompatActivity() {
+ private lateinit var _editCode: EditText
+
+ private lateinit var _layoutPairing: LinearLayout
+ private lateinit var _textPairingStatus: TextView
+
+ private lateinit var _layoutPairingSuccess: LinearLayout
+
+ private lateinit var _layoutPairingError: LinearLayout
+ private lateinit var _textError: TextView
+
+ private val _qrCodeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ val scanResult = IntentIntegrator.parseActivityResult(result.resultCode, result.data)
+ scanResult?.let {
+ if (it.contents != null) {
+ _editCode.text.clear()
+ _editCode.text.append(it.contents)
+ pair(it.contents)
+ }
+ }
+ }
+
+ override fun attachBaseContext(newBase: Context?) {
+ super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_sync_pair)
+ setNavigationBarColorAndIcons()
+
+ _editCode = findViewById(R.id.edit_code)
+ _layoutPairing = findViewById(R.id.layout_pairing)
+ _textPairingStatus = findViewById(R.id.text_pairing_status)
+ _layoutPairingSuccess = findViewById(R.id.layout_pairing_success)
+ _layoutPairingError = findViewById(R.id.layout_pairing_error)
+ _textError = findViewById(R.id.text_error)
+
+ findViewById(R.id.button_back).setOnClickListener {
+ finish()
+ }
+
+ findViewById(R.id.button_scan_qr).setOnClickListener {
+ val integrator = IntentIntegrator(this)
+ integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
+ integrator.setPrompt(getString(R.string.scan_a_qr_code))
+ integrator.setOrientationLocked(true);
+ integrator.setCameraId(0)
+ integrator.setBeepEnabled(false)
+ integrator.setBarcodeImageEnabled(true)
+ integrator.setCaptureActivity(QRCaptureActivity::class.java);
+ _qrCodeResultLauncher.launch(integrator.createScanIntent())
+ }
+
+ findViewById(R.id.button_link_new_device).setOnClickListener {
+ pair(_editCode.text.toString())
+ }
+
+ _layoutPairingSuccess.setOnClickListener {
+ _layoutPairingSuccess.visibility = View.GONE
+ }
+ _layoutPairingError.setOnClickListener {
+ _layoutPairingError.visibility = View.GONE
+ }
+ _layoutPairingSuccess.visibility = View.GONE
+ _layoutPairingError.visibility = View.GONE
+ }
+
+ fun pair(url: String) {
+ try {
+ _layoutPairing.visibility = View.VISIBLE
+ _textPairingStatus.text = "Parsing text..."
+
+ if (!url.startsWith("grayjay://sync/")) {
+ throw Exception("Not a valid URL: $url")
+ }
+
+ val deviceInfo: SyncDeviceInfo = Json.decodeFromString(Base64.decode(url.substring("grayjay://sync/".length), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).decodeToString())
+ if (StateSync.instance.isAuthorized(deviceInfo.publicKey)) {
+ throw Exception("This device is already paired")
+ }
+
+ _textPairingStatus.text = "Connecting..."
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ try {
+ StateSync.instance.connect(deviceInfo) { session, complete, message ->
+ lifecycleScope.launch(Dispatchers.Main) {
+ if (complete) {
+ _layoutPairingSuccess.visibility = View.VISIBLE
+ _layoutPairing.visibility = View.GONE
+ } else {
+ _textPairingStatus.text = message
+ }
+ }
+ }
+ } catch (e: Throwable) {
+ withContext(Dispatchers.Main) {
+ _layoutPairingError.visibility = View.VISIBLE
+ _textError.text = e.message
+ _layoutPairing.visibility = View.GONE
+ Logger.e(TAG, "Failed to pair", e)
+ }
+ }
+ }
+ } catch(e: Throwable) {
+ _layoutPairingError.visibility = View.VISIBLE
+ _textError.text = e.message
+ _layoutPairing.visibility = View.GONE
+ Logger.e(TAG, "Failed to pair", e)
+ } finally {
+ _layoutPairing.visibility = View.GONE
+ }
+ }
+
+ companion object {
+ private const val TAG = "SyncPairActivity"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/activities/SyncShowPairingCodeActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SyncShowPairingCodeActivity.kt
new file mode 100644
index 00000000..748d47e8
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/activities/SyncShowPairingCodeActivity.kt
@@ -0,0 +1,143 @@
+package com.futo.platformplayer.activities
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Base64
+import android.util.Log
+import android.util.TypedValue
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.futo.platformplayer.R
+import com.futo.platformplayer.UIDialogs
+import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.setNavigationBarColorAndIcons
+import com.futo.platformplayer.states.StateApp
+import com.futo.platformplayer.states.StateSync
+import com.futo.platformplayer.sync.SyncDeviceInfo
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.MultiFormatWriter
+import com.google.zxing.common.BitMatrix
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import java.net.NetworkInterface
+
+class SyncShowPairingCodeActivity : AppCompatActivity() {
+ private lateinit var _textCode: TextView
+ private lateinit var _imageQR: ImageView
+ private lateinit var _textQR: TextView
+ private var _code: String? = null
+
+ override fun attachBaseContext(newBase: Context?) {
+ super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ activity = null
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ activity = this
+
+ setContentView(R.layout.activity_sync_show_pairing_code)
+ setNavigationBarColorAndIcons()
+
+ _textCode = findViewById(R.id.text_code)
+ _imageQR = findViewById(R.id.image_qr)
+ _textQR = findViewById(R.id.text_scan_qr)
+
+ findViewById(R.id.button_back).setOnClickListener {
+ finish()
+ }
+
+ findViewById(R.id.button_copy).setOnClickListener {
+ val code = _code ?: return@setOnClickListener
+ val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager;
+ val clip = ClipData.newPlainText(getString(R.string.copied_text), code);
+ clipboard.setPrimaryClip(clip);
+ UIDialogs.toast(this, "Copied to clipboard")
+ }
+
+ val ips = getIPs()
+ val selfDeviceInfo = SyncDeviceInfo(StateSync.instance.publicKey!!, ips.toTypedArray(), StateSync.PORT)
+ val json = Json.encodeToString(selfDeviceInfo)
+ val base64 = Base64.encodeToString(json.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
+ val url = "grayjay://sync/${base64}"
+ setCode(url)
+ }
+
+ fun setCode(code: String?) {
+ _code = code
+
+ _textCode.text = code
+
+ if (code == null) {
+ _imageQR.visibility = View.INVISIBLE
+ _textQR.visibility = View.INVISIBLE
+ return
+ }
+
+ try {
+ val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics).toInt()
+ val qrCodeBitmap = generateQRCode(code, dimension, dimension)
+ _imageQR.setImageBitmap(qrCodeBitmap)
+ _imageQR.visibility = View.VISIBLE
+ _textQR.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Logger.e(TAG, getString(R.string.failed_to_generate_qr_code), e)
+ _imageQR.visibility = View.INVISIBLE
+ _textQR.visibility = View.INVISIBLE
+ }
+ }
+
+ private fun generateQRCode(content: String, width: Int, height: Int): Bitmap {
+ val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height);
+ return bitMatrixToBitmap(bitMatrix);
+ }
+
+ private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap {
+ val width = matrix.width;
+ val height = matrix.height;
+ val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+
+ for (x in 0 until width) {
+ for (y in 0 until height) {
+ bmp.setPixel(x, y, if (matrix[x, y]) Color.BLACK else Color.WHITE);
+ }
+ }
+ return bmp;
+ }
+
+ private fun getIPs(): List {
+ val ips = arrayListOf()
+ for (intf in NetworkInterface.getNetworkInterfaces()) {
+ for (addr in intf.inetAddresses) {
+ if (addr.isLoopbackAddress) {
+ continue
+ }
+
+ if (addr.address.size != 4) {
+ continue
+ }
+
+ addr.hostAddress?.let { ips.add(it) }
+ }
+ }
+ return ips
+ }
+
+ companion object {
+ private const val TAG = "SyncShowPairingCodeActivity"
+ var activity: SyncShowPairingCodeActivity? = null
+ private set
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/mdns/DnsWriter.kt b/app/src/main/java/com/futo/platformplayer/mdns/DnsWriter.kt
index 5b2c1f5c..48a04580 100644
--- a/app/src/main/java/com/futo/platformplayer/mdns/DnsWriter.kt
+++ b/app/src/main/java/com/futo/platformplayer/mdns/DnsWriter.kt
@@ -74,7 +74,7 @@ class DnsWriter {
namePositions[nameAtOffset] = nameStartPos
}
}
- write(0.toByte()) // End of domain name
+ write(0.toByte())
}
}
diff --git a/app/src/main/java/com/futo/platformplayer/mdns/ServiceRecordAggregator.kt b/app/src/main/java/com/futo/platformplayer/mdns/ServiceRecordAggregator.kt
index 5ff21a2c..a5337dc5 100644
--- a/app/src/main/java/com/futo/platformplayer/mdns/ServiceRecordAggregator.kt
+++ b/app/src/main/java/com/futo/platformplayer/mdns/ServiceRecordAggregator.kt
@@ -1,5 +1,6 @@
package com.futo.platformplayer.mdns
+import com.futo.platformplayer.logging.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -67,7 +68,7 @@ class ServiceRecordAggregator {
_currentServices.addAll(newServices)
}
- onServicesUpdated?.invoke(_currentServices)
+ onServicesUpdated?.invoke(_currentServices.toList())
delay(5000)
}
}
@@ -91,14 +92,12 @@ class ServiceRecordAggregator {
/*val builder = StringBuilder()
builder.appendLine("Received records:")
- srvRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: (Port: ${it.second.port}, Target: ${it.second.target}, Priority: ${it.second.priority}, Weight: ${it.second.weight})") }
- ptrRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.domainName}") }
- txtRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.texts.joinToString(", ")}") }
- aRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
- aaaaRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
- synchronized(lockObject) {
- // Save to file if necessary
- }*/
+ srvRecords.forEach { builder.appendLine("SRV ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: (Port: ${it.second.port}, Target: ${it.second.target}, Priority: ${it.second.priority}, Weight: ${it.second.weight})") }
+ ptrRecords.forEach { builder.appendLine("PTR ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.domainName}") }
+ txtRecords.forEach { builder.appendLine("TXT ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.texts.joinToString(", ")}") }
+ aRecords.forEach { builder.appendLine("A ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
+ aaaaRecords.forEach { builder.appendLine("AAAA ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
+ Logger.i(TAG, "$builder")*/
val currentServices: MutableList
synchronized(this._currentServices) {
@@ -106,18 +105,18 @@ class ServiceRecordAggregator {
val cachedPtrRecord = _cachedPtrRecords.getOrPut(record.first.name) { mutableListOf() }
val newPtrRecord = CachedDnsPtrRecord(Date(System.currentTimeMillis() + record.first.timeToLive.toLong() * 1000L), record.second.domainName)
cachedPtrRecord.replaceOrAdd(newPtrRecord) { it.target == record.second.domainName }
+ }
- aRecords.forEach { aRecord ->
- val cachedARecord = _cachedAddressRecords.getOrPut(aRecord.first.name) { mutableListOf() }
- val newARecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aRecord.first.timeToLive.toLong() * 1000L), aRecord.second.address)
- cachedARecord.replaceOrAdd(newARecord) { it.address == newARecord.address }
- }
+ aRecords.forEach { aRecord ->
+ val cachedARecord = _cachedAddressRecords.getOrPut(aRecord.first.name) { mutableListOf() }
+ val newARecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aRecord.first.timeToLive.toLong() * 1000L), aRecord.second.address)
+ cachedARecord.replaceOrAdd(newARecord) { it.address == newARecord.address }
+ }
- aaaaRecords.forEach { aaaaRecord ->
- val cachedAaaaRecord = _cachedAddressRecords.getOrPut(aaaaRecord.first.name) { mutableListOf() }
- val newAaaaRecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aaaaRecord.first.timeToLive.toLong() * 1000L), aaaaRecord.second.address)
- cachedAaaaRecord.replaceOrAdd(newAaaaRecord) { it.address == newAaaaRecord.address }
- }
+ aaaaRecords.forEach { aaaaRecord ->
+ val cachedAaaaRecord = _cachedAddressRecords.getOrPut(aaaaRecord.first.name) { mutableListOf() }
+ val newAaaaRecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aaaaRecord.first.timeToLive.toLong() * 1000L), aaaaRecord.second.address)
+ cachedAaaaRecord.replaceOrAdd(newAaaaRecord) { it.address == newAaaaRecord.address }
}
txtRecords.forEach { txtRecord ->
@@ -216,4 +215,8 @@ class ServiceRecordAggregator {
add(newElement)
}
}
+
+ private companion object {
+ private const val TAG = "ServiceRecordAggregator"
+ }
}
diff --git a/app/src/main/java/com/futo/platformplayer/noise/crypto/Blake2bMessageDigest.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/Blake2bMessageDigest.java
new file mode 100644
index 00000000..4aaf76ad
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/Blake2bMessageDigest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.futo.platformplayer.noise.crypto;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/Blake2sMessageDigest.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/Blake2sMessageDigest.java
new file mode 100644
index 00000000..ff165b43
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/Blake2sMessageDigest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.futo.platformplayer.noise.crypto;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/ChaChaCore.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/ChaChaCore.java
new file mode 100644
index 00000000..1a859c05
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/ChaChaCore.java
@@ -0,0 +1,200 @@
+/*
+ * 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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/Curve25519.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/Curve25519.java
new file mode 100644
index 00000000..60db79f5
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/Curve25519.java
@@ -0,0 +1,531 @@
+/*
+ * 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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/Curve448.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/Curve448.java
new file mode 100644
index 00000000..4be1f56f
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/Curve448.java
@@ -0,0 +1,622 @@
+/*
+ * 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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/GHASH.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/GHASH.java
new file mode 100644
index 00000000..f05aad70
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/GHASH.java
@@ -0,0 +1,204 @@
+/*
+ * 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.futo.platformplayer.noise.crypto;
+
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/NewHope.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/NewHope.java
new file mode 100644
index 00000000..0fa6b465
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/NewHope.java
@@ -0,0 +1,1605 @@
+/*
+ * 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.futo.platformplayer.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> 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>> 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 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<>> 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<>> (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/app/src/main/java/com/futo/platformplayer/noise/crypto/NewHopeTor.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/NewHopeTor.java
new file mode 100644
index 00000000..d8a9e644
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/NewHopeTor.java
@@ -0,0 +1,982 @@
+/*
+ * 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.futo.platformplayer.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> 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/app/src/main/java/com/futo/platformplayer/noise/crypto/Poly1305.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/Poly1305.java
new file mode 100644
index 00000000..160a9b01
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/Poly1305.java
@@ -0,0 +1,327 @@
+/*
+ * 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.futo.platformplayer.noise.crypto;
+
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/RijndaelAES.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/RijndaelAES.java
new file mode 100644
index 00000000..7d27e844
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/RijndaelAES.java
@@ -0,0 +1,1099 @@
+// 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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/SHA256MessageDigest.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/SHA256MessageDigest.java
new file mode 100644
index 00000000..25226a62
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/SHA256MessageDigest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.futo.platformplayer.noise.crypto;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/crypto/SHA512MessageDigest.java b/app/src/main/java/com/futo/platformplayer/noise/crypto/SHA512MessageDigest.java
new file mode 100644
index 00000000..1eeceda6
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/crypto/SHA512MessageDigest.java
@@ -0,0 +1,265 @@
+/*
+ * 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.futo.platformplayer.noise.crypto;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/AESGCMFallbackCipherState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/AESGCMFallbackCipherState.java
new file mode 100644
index 00000000..8ddd841f
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/AESGCMFallbackCipherState.java
@@ -0,0 +1,265 @@
+/*
+ * 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.futo.platformplayer.noise.protocol;
+
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.ShortBufferException;
+
+import com.futo.platformplayer.noise.crypto.GHASH;
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/AESGCMOnCtrCipherState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/AESGCMOnCtrCipherState.java
new file mode 100644
index 00000000..93da629d
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/AESGCMOnCtrCipherState.java
@@ -0,0 +1,335 @@
+/*
+ * 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.futo.platformplayer.noise.protocol;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/ChaChaPolyCipherState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/ChaChaPolyCipherState.java
new file mode 100644
index 00000000..2df26df2
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/ChaChaPolyCipherState.java
@@ -0,0 +1,290 @@
+/*
+ * 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.futo.platformplayer.noise.protocol;
+
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.ShortBufferException;
+
+import com.futo.platformplayer.noise.crypto.ChaChaCore;
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/CipherState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/CipherState.java
new file mode 100644
index 00000000..d8d43c11
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/CipherState.java
@@ -0,0 +1,162 @@
+/*
+ * 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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/CipherStatePair.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/CipherStatePair.java
new file mode 100644
index 00000000..7b393214
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/CipherStatePair.java
@@ -0,0 +1,106 @@
+/*
+ * 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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/Curve25519DHState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/Curve25519DHState.java
new file mode 100644
index 00000000..f27742ca
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/Curve25519DHState.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.futo.platformplayer.noise.protocol;
+
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/Curve448DHState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/Curve448DHState.java
new file mode 100644
index 00000000..5b16bf59
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/Curve448DHState.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.futo.platformplayer.noise.protocol;
+
+import java.util.Arrays;
+
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/DHState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/DHState.java
new file mode 100644
index 00000000..fff5c1c7
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/DHStateHybrid.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/DHStateHybrid.java
new file mode 100644
index 00000000..f4d7cec0
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/Destroyable.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/Destroyable.java
new file mode 100644
index 00000000..7e98c729
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/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.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/HandshakeState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/HandshakeState.java
new file mode 100644
index 00000000..45c1ef50
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/HandshakeState.java
@@ -0,0 +1,1260 @@
+/*
+ * 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.futo.platformplayer.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[] preSharedKey;
+ private byte[] prologue;
+
+ /**
+ * 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");
+ pattern = Pattern.lookup(patternId);
+ if (pattern == null)
+ throw new IllegalArgumentException("Handshake pattern is not recognized");
+ short flags = pattern[0];
+ int extraReqs = 0;
+ if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0 && patternId.length() > 1)
+ extraReqs |= FALLBACK_POSSIBLE;
+ 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 (preSharedKey != 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 preSharedKey != 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 (preSharedKey != null) {
+ Noise.destroy(preSharedKey);
+ preSharedKey = null;
+ }
+ preSharedKey = 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 (preSharedKey == 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.
+ if (preSharedKey != null)
+ symmetric.mixPreSharedKey(preSharedKey);
+
+ // 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 (preSharedKey != 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 (preSharedKey != 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 (preSharedKey != 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;
+
+ 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 (preSharedKey != 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;
+
+ 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 (preSharedKey != null)
+ Noise.destroy(preSharedKey);
+ 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/app/src/main/java/com/futo/platformplayer/noise/protocol/NewHopeDHState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/NewHopeDHState.java
new file mode 100644
index 00000000..4d31f323
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/NewHopeDHState.java
@@ -0,0 +1,340 @@
+/*
+ * 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.futo.platformplayer.noise.protocol;
+
+import java.util.Arrays;
+
+import com.futo.platformplayer.noise.crypto.NewHope;
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/Noise.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/Noise.java
new file mode 100644
index 00000000..e0dff321
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/Noise.java
@@ -0,0 +1,237 @@
+/*
+ * 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.futo.platformplayer.noise.protocol;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+
+import com.futo.platformplayer.noise.crypto.Blake2bMessageDigest;
+import com.futo.platformplayer.noise.crypto.Blake2sMessageDigest;
+import com.futo.platformplayer.noise.crypto.SHA256MessageDigest;
+import com.futo.platformplayer.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/app/src/main/java/com/futo/platformplayer/noise/protocol/Pattern.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/Pattern.java
new file mode 100644
index 00000000..237f901b
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/Pattern.java
@@ -0,0 +1,835 @@
+/*
+ * 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.futo.platformplayer.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 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;
+
+ 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)
+ {
+ 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;
+ }
+
+ /**
+ * 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/app/src/main/java/com/futo/platformplayer/noise/protocol/SymmetricState.java b/app/src/main/java/com/futo/platformplayer/noise/protocol/SymmetricState.java
new file mode 100644
index 00000000..45617f46
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/noise/protocol/SymmetricState.java
@@ -0,0 +1,484 @@
+/*
+ * 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.futo.platformplayer.noise.protocol;
+
+import java.io.UnsupportedEncodingException;
+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;
+ try {
+ protocolNameBytes = protocolName.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // If UTF-8 is not supported, then we are definitely in trouble!
+ throw new UnsupportedOperationException("UTF-8 encoding is not supported");
+ }
+
+ 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 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)
+ {
+ 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);
+ } finally {
+ Noise.destroy(tempKey);
+ Noise.destroy(tempHash);
+ }
+ }
+}
diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt
index cea2c090..52103822 100644
--- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt
+++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt
@@ -412,6 +412,10 @@ class StateApp {
StateTelemetry.instance.upload();
}
+ if (Settings.instance.synchronization.enabled) {
+ StateSync.instance.start()
+ }
+
Logger.onLogSubmitted.subscribe {
scopeOrNull?.launch(Dispatchers.Main) {
try {
diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt
new file mode 100644
index 00000000..82c67ab2
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt
@@ -0,0 +1,426 @@
+package com.futo.platformplayer.states
+
+import android.os.Build
+import android.util.Log
+import com.futo.platformplayer.LittleEndianDataInputStream
+import com.futo.platformplayer.LittleEndianDataOutputStream
+import com.futo.platformplayer.Settings
+import com.futo.platformplayer.UIDialogs
+import com.futo.platformplayer.activities.SyncShowPairingCodeActivity
+import com.futo.platformplayer.constructs.Event1
+import com.futo.platformplayer.constructs.Event2
+import com.futo.platformplayer.encryption.GEncryptionProvider
+import com.futo.platformplayer.getConnectedSocket
+import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.mdns.DnsService
+import com.futo.platformplayer.mdns.ServiceDiscoverer
+import com.futo.platformplayer.noise.protocol.DHState
+import com.futo.platformplayer.noise.protocol.Noise
+import com.futo.platformplayer.stores.FragmentedStorage
+import com.futo.platformplayer.stores.StringStringMapStorage
+import com.futo.platformplayer.stores.StringArrayStorage
+import com.futo.platformplayer.stores.StringStorage
+import com.futo.platformplayer.sync.SyncDeviceInfo
+import com.futo.platformplayer.sync.SyncKeyPair
+import com.futo.platformplayer.sync.SyncSession
+import com.futo.platformplayer.sync.SyncSocketSession
+import com.futo.polycentric.core.base64ToByteArray
+import com.futo.polycentric.core.toBase64
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.ServerSocket
+import java.net.Socket
+import java.util.Base64
+import java.util.Locale
+
+class StateSync {
+ private val _authorizedDevices = FragmentedStorage.get("authorized_devices")
+ private val _syncKeyPair = FragmentedStorage.get("sync_key_pair")
+ private val _lastAddressStorage = FragmentedStorage.get("sync_last_address_storage")
+
+ private var _serverSocket: ServerSocket? = null
+ private var _thread: Thread? = null
+ private var _connectThread: Thread? = null
+ private var _started = false
+ private val _sessions: MutableMap = mutableMapOf()
+ private val _lastConnectTimes: MutableMap = mutableMapOf()
+ //TODO: Should sync mdns and casting mdns be merged?
+ //TODO: Decrease interval that devices are updated
+ //TODO: Send less data
+ val _serviceDiscoverer = ServiceDiscoverer(arrayOf("_gsync._tcp.local")) { handleServiceUpdated(it) }
+
+ var keyPair: DHState? = null
+ var publicKey: String? = null
+ val deviceRemoved: Event1 = Event1()
+ val deviceUpdatedOrAdded: Event2 = Event2()
+
+ fun start() {
+ _started = true
+
+ if (Settings.instance.synchronization.broadcast || Settings.instance.synchronization.connectDiscovered) {
+ _serviceDiscoverer.start()
+ }
+
+ try {
+ val syncKeyPair = Json.decodeFromString(GEncryptionProvider.instance.decrypt(_syncKeyPair.value))
+ val p = Noise.createDH(dh)
+ p.setPublicKey(syncKeyPair.publicKey.base64ToByteArray(), 0)
+ p.setPrivateKey(syncKeyPair.privateKey.base64ToByteArray(), 0)
+ keyPair = p
+ } catch (e: Throwable) {
+ //Sync key pair non-existing, invalid or lost
+ val p = Noise.createDH(dh)
+ p.generateKeyPair()
+
+ val publicKey = ByteArray(p.publicKeyLength)
+ p.getPublicKey(publicKey, 0)
+ val privateKey = ByteArray(p.privateKeyLength)
+ p.getPrivateKey(privateKey, 0)
+
+ val syncKeyPair = SyncKeyPair(1, publicKey.toBase64(), privateKey.toBase64())
+ _syncKeyPair.setAndSave(GEncryptionProvider.instance.encrypt(Json.encodeToString(syncKeyPair)))
+
+ Logger.e(TAG, "Failed to load existing key pair", e)
+ keyPair = p
+ }
+
+ publicKey = keyPair?.let {
+ val pkey = ByteArray(it.publicKeyLength)
+ it.getPublicKey(pkey, 0)
+ return@let pkey.toBase64()
+ }
+
+ if (Settings.instance.synchronization.broadcast) {
+ publicKey?.let { _serviceDiscoverer.broadcastService(getDeviceName(), "_gsync._tcp.local", PORT.toUShort(), texts = arrayListOf("pk=${it.replace('+', '-').replace('/', '_').replace("=", "")}")) }
+ }
+
+ Logger.i(TAG, "Sync key pair initialized (public key = ${publicKey})")
+
+ _thread = Thread {
+ val serverSocket = ServerSocket(PORT)
+ _serverSocket = serverSocket
+
+ Log.i(TAG, "Running on port ${PORT} (TCP)")
+
+ while (_started) {
+ val socket = serverSocket.accept()
+ val session = createSocketSession(socket, true) { session, socketSession ->
+
+ }
+
+ session.startAsResponder()
+ }
+ }.apply { start() }
+
+ if (Settings.instance.synchronization.connectLast) {
+ _connectThread = Thread {
+ Log.i(TAG, "Running auto reconnector")
+
+ while (_started) {
+ val authorizedDevices = synchronized(_authorizedDevices) {
+ return@synchronized _authorizedDevices.values
+ }
+
+ val lastKnownMap = synchronized(_lastAddressStorage) {
+ return@synchronized _lastAddressStorage.map.toMap()
+ }
+
+ val addressesToConnect = authorizedDevices.mapNotNull {
+ val connected = isConnected(it)
+ if (connected) {
+ return@mapNotNull null
+ }
+
+ val lastKnownAddress = lastKnownMap[it] ?: return@mapNotNull null
+ return@mapNotNull Pair(it, lastKnownAddress)
+ }
+
+ for (connectPair in addressesToConnect) {
+ try {
+ val syncDeviceInfo = SyncDeviceInfo(connectPair.first, arrayOf(connectPair.second), PORT)
+ connect(syncDeviceInfo)
+ } catch (e: Throwable) {
+ Logger.i(TAG, "Failed to connect to " + connectPair.first, e)
+ }
+ }
+ Thread.sleep(5000)
+ }
+ }.apply { start() }
+ }
+ }
+
+ private fun getDeviceName(): String {
+ val manufacturer = Build.MANUFACTURER.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
+ val model = Build.MODEL
+
+ return if (model.startsWith(manufacturer, ignoreCase = true)) {
+ model.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
+ } else {
+ "$manufacturer $model".replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
+ }
+ }
+
+ fun isConnected(publicKey: String): Boolean {
+ return synchronized(_sessions) {
+ _sessions[publicKey]?.connected ?: false
+ }
+ }
+
+ fun isAuthorized(publicKey: String): Boolean {
+ return synchronized(_authorizedDevices) {
+ _authorizedDevices.values.contains(publicKey)
+ }
+ }
+
+ fun getSession(publicKey: String): SyncSession? {
+ return synchronized(_sessions) {
+ _sessions[publicKey]
+ }
+ }
+
+ private fun handleServiceUpdated(services: List) {
+ if (!Settings.instance.synchronization.connectDiscovered) {
+ return
+ }
+
+ for (s in services) {
+ //TODO: Addresses IPv4 only?
+ val addresses = s.addresses.mapNotNull { it.hostAddress }.toTypedArray()
+ val port = s.port.toInt()
+ if (s.name.endsWith("._gsync._tcp.local")) {
+ val name = s.name.substring(0, s.name.length - "._gsync._tcp.local".length)
+ val urlSafePkey = s.texts.firstOrNull { it.startsWith("pk=") }?.substring("pk=".length) ?: continue
+ val pkey = Base64.getEncoder().encodeToString(Base64.getDecoder().decode(urlSafePkey.replace('-', '+').replace('_', '/')))
+
+ val syncDeviceInfo = SyncDeviceInfo(pkey, addresses, port)
+ val authorized = isAuthorized(pkey)
+
+ if (authorized && !isConnected(pkey)) {
+ val now = System.currentTimeMillis()
+ val lastConnectTime = synchronized(_lastConnectTimes) {
+ _lastConnectTimes[pkey] ?: 0
+ }
+
+ //Connect once every 30 seconds, max
+ if (now - lastConnectTime > 30000) {
+ synchronized(_lastConnectTimes) {
+ _lastConnectTimes[pkey] = now
+ }
+
+ Logger.i(TAG, "Found device authorized device '${name}' with pkey=$pkey, attempting to connect")
+
+ try {
+ connect(syncDeviceInfo)
+ } catch (e: Throwable) {
+ Logger.e(TAG, "Failed to connect to $pkey", e)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun unauthorize(remotePublicKey: String) {
+ Logger.i(TAG, "${remotePublicKey} unauthorized received")
+ _authorizedDevices.remove(remotePublicKey)
+ _authorizedDevices.save()
+ deviceRemoved.emit(remotePublicKey)
+ }
+
+ private fun createSocketSession(socket: Socket, isResponder: Boolean, onAuthorized: (session: SyncSession, socketSession: SyncSocketSession) -> Unit): SyncSocketSession {
+ var session: SyncSession? = null
+ return SyncSocketSession((socket.remoteSocketAddress as InetSocketAddress).address.hostAddress!!, keyPair!!, LittleEndianDataInputStream(socket.getInputStream()), LittleEndianDataOutputStream(socket.getOutputStream()),
+ onClose = { s ->
+ session?.removeSocketSession(s)
+ },
+ onHandshakeComplete = { s ->
+ val remotePublicKey = s.remotePublicKey
+ if (remotePublicKey == null) {
+ s.stop()
+ return@SyncSocketSession
+ }
+
+ Logger.i(TAG, "Handshake complete with ${s.remotePublicKey}")
+
+ synchronized(_sessions) {
+ session = _sessions[s.remotePublicKey]
+ if (session == null) {
+ session = SyncSession(remotePublicKey, onAuthorized = {
+ Logger.i(TAG, "${s.remotePublicKey} authorized")
+ synchronized(_lastAddressStorage) {
+ _lastAddressStorage.setAndSave(remotePublicKey, s.remoteAddress)
+ }
+
+ onAuthorized(it, s)
+ _authorizedDevices.addDistinct(remotePublicKey)
+ _authorizedDevices.save()
+ deviceUpdatedOrAdded.emit(it.remotePublicKey, session!!)
+ }, onUnauthorized = {
+ unauthorize(remotePublicKey)
+
+ synchronized(_sessions) {
+ session?.close()
+ _sessions.remove(remotePublicKey)
+ }
+ }, onConnectedChanged = { it, connected ->
+ Logger.i(TAG, "${s.remotePublicKey} connected: " + connected)
+ deviceUpdatedOrAdded.emit(it.remotePublicKey, session!!)
+ }, onClose = {
+ Logger.i(TAG, "${s.remotePublicKey} closed")
+
+ synchronized(_sessions)
+ {
+ _sessions.remove(it.remotePublicKey)
+ }
+
+ deviceRemoved.emit(it.remotePublicKey)
+
+ })
+ _sessions[remotePublicKey] = session!!
+ }
+ session!!.addSocketSession(s)
+ }
+
+ if (isResponder) {
+ val isAuthorized = synchronized(_authorizedDevices) {
+ _authorizedDevices.values.contains(remotePublicKey)
+ }
+
+ if (!isAuthorized) {
+ val scope = StateApp.instance.scopeOrNull
+ val activity = SyncShowPairingCodeActivity.activity
+
+ if (scope != null && activity != null) {
+ scope.launch(Dispatchers.Main) {
+ UIDialogs.showConfirmationDialog(activity, "Allow connection from ${remotePublicKey}?", action = {
+ scope.launch(Dispatchers.IO) {
+ session!!.authorize(s)
+ Logger.i(TAG, "Connection authorized for ${remotePublicKey} by confirmation")
+ }
+ }, cancelAction = {
+ scope.launch(Dispatchers.IO) {
+ try {
+ unauthorize(remotePublicKey)
+ } catch (e: Throwable) {
+ Logger.w(TAG, "Failed to send unauthorize", e)
+ }
+
+ synchronized(_sessions) {
+ session?.close()
+ _sessions.remove(remotePublicKey)
+ }
+ }
+ })
+ }
+ }
+ } else {
+ //Responder does not need to check because already approved
+ session!!.authorize(s)
+ Logger.i(TAG, "Connection authorized for ${remotePublicKey} because already authorized")
+ }
+ } else {
+ //Initiator does not need to check because the manual action of scanning the QR counts as approval
+ session!!.authorize(s)
+ Logger.i(TAG, "Connection authorized for ${remotePublicKey} because initiator")
+ }
+ },
+ onData = { s, opcode, data ->
+ session?.handlePacket(s, opcode, data)
+ })
+ }
+
+ fun stop() {
+ _started = false
+ _serviceDiscoverer.stop()
+
+ _serverSocket?.close()
+ _serverSocket = null
+
+ //_thread?.join()
+ _thread = null
+ _connectThread = null
+ }
+
+ fun connect(deviceInfo: SyncDeviceInfo, onStatusUpdate: ((session: SyncSocketSession?, complete: Boolean, message: String) -> Unit)? = null): SyncSocketSession {
+ onStatusUpdate?.invoke(null, false, "Connecting...")
+ val socket = getConnectedSocket(deviceInfo.addresses.map { InetAddress.getByName(it) }, deviceInfo.port) ?: throw Exception("Failed to connect")
+ onStatusUpdate?.invoke(null, false, "Handshaking...")
+
+ val session = createSocketSession(socket, false) { _, ss ->
+ onStatusUpdate?.invoke(ss, true, "Handshake complete")
+ }
+
+ session.startAsInitiator(deviceInfo.publicKey)
+ return session
+ }
+
+ fun hasAtLeastOneDevice(): Boolean {
+ synchronized(_authorizedDevices) {
+ return _authorizedDevices.values.isNotEmpty()
+ }
+ }
+
+ fun getAll(): List {
+ synchronized(_authorizedDevices) {
+ return _authorizedDevices.values.toList()
+ }
+ }
+
+ suspend fun delete(publicKey: String) {
+ withContext(Dispatchers.IO) {
+ try {
+ val session = getSession(publicKey)
+ session?.let {
+ try {
+ session.unauthorize()
+ } catch (ex: Throwable) {
+ Logger.w(TAG, "Failed to send unauthorize (delete)", ex)
+ }
+
+ session.close()
+ }
+
+ synchronized(_sessions) {
+ _sessions.remove(publicKey)
+ }
+
+ synchronized(_authorizedDevices) {
+ _authorizedDevices.remove(publicKey)
+ }
+ _authorizedDevices.save()
+
+ withContext(Dispatchers.Main) {
+ deviceRemoved.emit(publicKey)
+ }
+ } catch (e: Throwable) {
+ Logger.w(TAG, "Failed to delete", e)
+ }
+ }
+
+ }
+
+ companion object {
+ val dh = "25519"
+ val pattern = "IK"
+ val cipher = "ChaChaPoly"
+ val hash = "BLAKE2b"
+ var protocolName = "Noise_${pattern}_${dh}_${cipher}_${hash}"
+ val version = 1
+
+ private const val TAG = "StateSync"
+ const val PORT = 12315
+
+ private var _instance: StateSync? = null
+ val instance: StateSync
+ get() {
+ if(_instance == null)
+ _instance = StateSync()
+ return _instance!!
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/stores/MapStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/CachedPolycentricProfileStorage.kt
similarity index 100%
rename from app/src/main/java/com/futo/platformplayer/stores/MapStorage.kt
rename to app/src/main/java/com/futo/platformplayer/stores/CachedPolycentricProfileStorage.kt
diff --git a/app/src/main/java/com/futo/platformplayer/stores/StringStringMapStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/StringStringMapStorage.kt
new file mode 100644
index 00000000..659ec16b
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/stores/StringStringMapStorage.kt
@@ -0,0 +1,29 @@
+package com.futo.platformplayer.stores
+
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+@kotlinx.serialization.Serializable
+class StringStringMapStorage : FragmentedStorageFileJson() {
+ var map: HashMap = hashMapOf()
+
+ override fun encode(): String {
+ return Json.encodeToString(this)
+ }
+
+ fun get(key: String): String? {
+ return map[key]
+ }
+
+ fun setAndSave(key: String, value: String): String {
+ map[key] = value
+ save()
+ return value
+ }
+
+ fun setAndSaveBlocking(key: String, value: String): String {
+ map[key] = value
+ saveBlocking()
+ return value
+ }
+}
diff --git a/app/src/main/java/com/futo/platformplayer/sync/LinkType.java b/app/src/main/java/com/futo/platformplayer/sync/LinkType.java
new file mode 100644
index 00000000..a0ae426e
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/sync/LinkType.java
@@ -0,0 +1,7 @@
+package com.futo.platformplayer.sync;
+
+public enum LinkType {
+ None,
+ Local,
+ Proxied
+}
diff --git a/app/src/main/java/com/futo/platformplayer/sync/SyncDeviceInfo.kt b/app/src/main/java/com/futo/platformplayer/sync/SyncDeviceInfo.kt
new file mode 100644
index 00000000..8164cefb
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/sync/SyncDeviceInfo.kt
@@ -0,0 +1,14 @@
+package com.futo.platformplayer.sync
+
+@kotlinx.serialization.Serializable
+class SyncDeviceInfo {
+ var publicKey: String
+ var addresses: Array
+ var port: Int
+
+ constructor(publicKey: String, addresses: Array, port: Int) {
+ this.publicKey = publicKey
+ this.addresses = addresses
+ this.port = port
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/sync/SyncKeyPair.kt b/app/src/main/java/com/futo/platformplayer/sync/SyncKeyPair.kt
new file mode 100644
index 00000000..c4126683
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/sync/SyncKeyPair.kt
@@ -0,0 +1,14 @@
+package com.futo.platformplayer.sync
+
+@kotlinx.serialization.Serializable
+class SyncKeyPair {
+ var publicKey: String
+ var privateKey: String
+ var version: Int
+
+ constructor(version: Int, publicKey: String, privateKey: String) {
+ this.publicKey = publicKey
+ this.privateKey = privateKey
+ this.version = version
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/sync/SyncSession.kt b/app/src/main/java/com/futo/platformplayer/sync/SyncSession.kt
new file mode 100644
index 00000000..95c96b9a
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/sync/SyncSession.kt
@@ -0,0 +1,118 @@
+package com.futo.platformplayer.sync
+
+import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.sync.SyncSocketSession.Opcode
+import java.nio.ByteBuffer
+
+interface IAuthorizable {
+ val isAuthorized: Boolean
+}
+
+class SyncSession : IAuthorizable {
+ private val _socketSessions: MutableList = mutableListOf()
+ private var _authorized: Boolean = false
+ private var _remoteAuthorized: Boolean = false
+ private val _onAuthorized: (session: SyncSession) -> Unit
+ private val _onUnauthorized: (session: SyncSession) -> Unit
+ private val _onClose: (session: SyncSession) -> Unit
+ private val _onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit
+ val remotePublicKey: String
+ override val isAuthorized get() = _authorized && _remoteAuthorized
+
+ var connected: Boolean = false
+ private set(v) {
+ if (field != v) {
+ field = v
+ this._onConnectedChanged(this, v)
+ }
+ }
+
+ constructor(remotePublicKey: String, onAuthorized: (session: SyncSession) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit) {
+ this.remotePublicKey = remotePublicKey
+ _onAuthorized = onAuthorized
+ _onUnauthorized = onUnauthorized
+ _onConnectedChanged = onConnectedChanged
+ _onClose = onClose
+ }
+
+ fun addSocketSession(socketSession: SyncSocketSession) {
+ if (socketSession.remotePublicKey != remotePublicKey) {
+ throw Exception("Public key of session must match public key of socket session")
+ }
+
+ synchronized(_socketSessions) {
+ _socketSessions.add(socketSession)
+ connected = _socketSessions.isNotEmpty()
+ }
+
+ socketSession.authorizable = this
+ }
+
+ fun authorize(socketSession: SyncSocketSession) {
+ socketSession.send(Opcode.NOTIFY_AUTHORIZED.value)
+ _authorized = true
+ checkAuthorized()
+ }
+
+ fun unauthorize(socketSession: SyncSocketSession? = null) {
+ if (socketSession != null)
+ socketSession.send(Opcode.NOTIFY_UNAUTHORIZED.value)
+ else {
+ val ss = synchronized(_socketSessions) {
+ _socketSessions.first()
+ }
+
+ ss.send(Opcode.NOTIFY_UNAUTHORIZED.value)
+ }
+ }
+
+ private fun checkAuthorized() {
+ if (isAuthorized)
+ _onAuthorized.invoke(this)
+ }
+
+ fun removeSocketSession(socketSession: SyncSocketSession) {
+ synchronized(_socketSessions) {
+ _socketSessions.remove(socketSession)
+ connected = _socketSessions.isNotEmpty()
+ }
+ }
+
+ fun close() {
+ synchronized(_socketSessions) {
+ for (socketSession in _socketSessions) {
+ socketSession.stop()
+ }
+
+ _socketSessions.clear()
+ }
+
+ _onClose.invoke(this)
+ }
+
+ fun handlePacket(socketSession: SyncSocketSession, opcode: UByte, data: ByteBuffer) {
+ Logger.i(TAG, "Handle packet (opcode: ${opcode}, data.length: ${data.remaining()})")
+
+ when (opcode) {
+ Opcode.NOTIFY_AUTHORIZED.value -> {
+ _remoteAuthorized = true
+ checkAuthorized()
+ }
+ Opcode.NOTIFY_UNAUTHORIZED.value -> {
+ _remoteAuthorized = false
+ _onUnauthorized(this)
+ }
+ //TODO: Handle any kind of packet (that is not necessarily authorized)
+ }
+
+ if (!isAuthorized) {
+ return
+ }
+
+ Logger.i(TAG, "Received ${opcode} (${data.remaining()} bytes)")
+ }
+
+ private companion object {
+ const val TAG = "SyncSession"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/sync/SyncSocketSession.kt b/app/src/main/java/com/futo/platformplayer/sync/SyncSocketSession.kt
new file mode 100644
index 00000000..f6b0507b
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/sync/SyncSocketSession.kt
@@ -0,0 +1,379 @@
+package com.futo.platformplayer.sync
+
+import com.futo.platformplayer.LittleEndianDataInputStream
+import com.futo.platformplayer.LittleEndianDataOutputStream
+import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.noise.protocol.CipherStatePair
+import com.futo.platformplayer.noise.protocol.DHState
+import com.futo.platformplayer.noise.protocol.HandshakeState
+import com.futo.platformplayer.states.StateSync
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+class SyncSocketSession {
+ enum class Opcode(val value: UByte) {
+ PING(0u),
+ PONG(1u),
+ NOTIFY_AUTHORIZED(2u),
+ NOTIFY_UNAUTHORIZED(3u),
+ STREAM_START(4u),
+ STREAM_DATA(5u),
+ STREAM_END(6u)
+ }
+
+ private val _inputStream: LittleEndianDataInputStream
+ private val _outputStream: LittleEndianDataOutputStream
+ private val _sendLockObject = Object()
+ private val _buffer = ByteArray(MAXIMUM_PACKET_SIZE_ENCRYPTED)
+ private val _bufferDecrypted = ByteArray(MAXIMUM_PACKET_SIZE)
+ private val _sendBuffer = ByteArray(MAXIMUM_PACKET_SIZE)
+ private val _sendBufferEncrypted = ByteArray(MAXIMUM_PACKET_SIZE_ENCRYPTED)
+ private val _syncStreams = hashMapOf()
+ private val _streamIdGenerator = 0
+ private val _streamIdGeneratorLock = Object()
+ private val _onClose: (session: SyncSocketSession) -> Unit
+ private val _onHandshakeComplete: (session: SyncSocketSession) -> Unit
+ private var _thread: Thread? = null
+ private var _cipherStatePair: CipherStatePair? = null
+ private var _remotePublicKey: String? = null
+ val remotePublicKey: String? get() = _remotePublicKey
+ private var _started: Boolean = false
+ private val _localKeyPair: DHState
+ private var _localPublicKey: String
+ val localPublicKey: String get() = _localPublicKey
+ private val _onData: (session: SyncSocketSession, opcode: UByte, data: ByteBuffer) -> Unit
+ var authorizable: IAuthorizable? = null
+
+ val remoteAddress: String
+
+ constructor(remoteAddress: String, localKeyPair: DHState, inputStream: LittleEndianDataInputStream, outputStream: LittleEndianDataOutputStream, onClose: (session: SyncSocketSession) -> Unit, onHandshakeComplete: (session: SyncSocketSession) -> Unit, onData: (session: SyncSocketSession, opcode: UByte, data: ByteBuffer) -> Unit) {
+ _inputStream = inputStream
+ _outputStream = outputStream
+ _onClose = onClose
+ _onHandshakeComplete = onHandshakeComplete
+ _localKeyPair = localKeyPair
+ _onData = onData
+ this.remoteAddress = remoteAddress
+
+ val localPublicKey = ByteArray(localKeyPair.publicKeyLength)
+ localKeyPair.getPublicKey(localPublicKey, 0)
+ _localPublicKey = java.util.Base64.getEncoder().encodeToString(localPublicKey)
+ }
+
+ fun startAsInitiator(remotePublicKey: String) {
+ _started = true
+ _thread = Thread {
+ try {
+ handshakeAsInitiator(remotePublicKey)
+ _onHandshakeComplete.invoke(this)
+ receiveLoop()
+ } catch (e: Throwable) {
+ Logger.e(TAG, "Failed to run as initiator", e)
+ } finally {
+ stop()
+ }
+ }.apply { start() }
+ }
+
+ fun startAsResponder() {
+ _started = true
+ _thread = Thread {
+ try {
+ handshakeAsResponder()
+ _onHandshakeComplete.invoke(this)
+ receiveLoop()
+ } catch(e: Throwable) {
+ Logger.e(TAG, "Failed to run as responder", e)
+ } finally {
+ stop()
+ }
+ }.apply { start() }
+ }
+
+ private fun receiveLoop() {
+ while (_started) {
+ try {
+ val messageSize = _inputStream.readInt()
+ if (messageSize > MAXIMUM_PACKET_SIZE_ENCRYPTED) {
+ throw Exception("Message size (${messageSize}) cannot exceed MAXIMUM_PACKET_SIZE (${MAXIMUM_PACKET_SIZE_ENCRYPTED})")
+ }
+
+ //Logger.i(TAG, "Receiving message (size = ${messageSize})")
+
+ var bytesRead = 0
+ while (bytesRead < messageSize) {
+ val read = _inputStream.read(_buffer, bytesRead, messageSize - bytesRead)
+ if (read == -1)
+ throw Exception("Stream closed")
+ bytesRead += read
+ }
+
+ val plen: Int = _cipherStatePair!!.receiver.decryptWithAd(null, _buffer, 0, _bufferDecrypted, 0, messageSize)
+ //Logger.i(TAG, "Decrypted message (size = ${plen})")
+
+ handleData(_bufferDecrypted, plen)
+ } catch (e: Throwable) {
+ Logger.e(TAG, "Exception while receiving data", e)
+ break
+ }
+ }
+ }
+
+ fun stop() {
+ _started = false
+ _onClose(this)
+ _inputStream.close()
+ _outputStream.close()
+ _thread = null
+ Logger.i(TAG, "Session closed")
+ }
+
+ private fun handshakeAsInitiator(remotePublicKey: String) {
+ performVersionCheck()
+
+ val initiator = HandshakeState(StateSync.protocolName, HandshakeState.INITIATOR)
+ initiator.localKeyPair.copyFrom(_localKeyPair)
+
+ initiator.remotePublicKey.setPublicKey(java.util.Base64.getDecoder().decode(remotePublicKey), 0)
+ _cipherStatePair = handshake(initiator)
+
+ _remotePublicKey = initiator.remotePublicKey.let {
+ val pkey = ByteArray(it.publicKeyLength)
+ it.getPublicKey(pkey, 0)
+ return@let java.util.Base64.getEncoder().encodeToString(pkey)
+ }
+ }
+
+ private fun handshakeAsResponder() {
+ performVersionCheck()
+
+ val responder = HandshakeState(StateSync.protocolName, HandshakeState.RESPONDER)
+ responder.localKeyPair.copyFrom(_localKeyPair)
+ _cipherStatePair = handshake(responder)
+
+ _remotePublicKey = responder.remotePublicKey.let {
+ val pkey = ByteArray(it.publicKeyLength)
+ it.getPublicKey(pkey, 0)
+ return@let java.util.Base64.getEncoder().encodeToString(pkey)
+ }
+ }
+
+ private fun performVersionCheck() {
+ _outputStream.writeInt(1)
+ val version = _inputStream.readInt()
+ Logger.i(TAG, "performVersionCheck (version = $version)")
+ if (version != 1)
+ throw Exception("Invalid version")
+ }
+
+ private fun handshake(handshakeState: HandshakeState): CipherStatePair {
+ handshakeState.start()
+
+ val message = ByteArray(8192)
+ val plaintext = ByteArray(8192)
+
+ while (_started) {
+ when (handshakeState.action) {
+ HandshakeState.READ_MESSAGE -> {
+ val messageSize = _inputStream.readInt()
+ Logger.i(TAG, "Handshake read message (size = ${messageSize})")
+
+ var bytesRead = 0
+ while (bytesRead < messageSize) {
+ val read = _inputStream.read(message, bytesRead, messageSize - bytesRead)
+ if (read == -1)
+ throw Exception("Stream closed")
+ bytesRead += read
+ }
+
+ handshakeState.readMessage(message, 0, messageSize, plaintext, 0)
+ }
+ HandshakeState.WRITE_MESSAGE -> {
+ val messageSize = handshakeState.writeMessage(message, 0, null, 0, 0)
+ Logger.i(TAG, "Handshake wrote message (size = ${messageSize})")
+ _outputStream.writeInt(messageSize)
+ _outputStream.write(message, 0, messageSize)
+ }
+ HandshakeState.SPLIT -> {
+ //Logger.i(TAG, "Handshake split")
+ return handshakeState.split()
+ }
+ else -> throw Exception("Unexpected state (handshakeState.action = ${handshakeState.action})")
+ }
+ }
+
+ throw Exception("Handshake finished without completing")
+ }
+
+
+ fun send(opcode: UByte, data: ByteBuffer) {
+ if (data.remaining() + HEADER_SIZE > MAXIMUM_PACKET_SIZE) {
+ val segmentSize = MAXIMUM_PACKET_SIZE - HEADER_SIZE
+ val segmentData = ByteArray(segmentSize)
+ var sendOffset = 0
+ val id = synchronized(_streamIdGeneratorLock) {
+ _streamIdGenerator + 1
+ }
+
+ while (sendOffset < data.remaining()) {
+ val bytesRemaining = data.remaining() - sendOffset
+ var bytesToSend: Int
+ var segmentPacketSize: Int
+ val segmentOpcode: UByte
+
+ if (sendOffset == 0) {
+ segmentOpcode = Opcode.STREAM_START.value
+ bytesToSend = segmentSize - 4 - 4 - 1
+ segmentPacketSize = bytesToSend + 4 + 4 + 1
+ } else {
+ bytesToSend = minOf(segmentSize - 4 - 4, bytesRemaining)
+ segmentOpcode = if (bytesToSend >= bytesRemaining) Opcode.STREAM_END.value else Opcode.STREAM_DATA.value
+ segmentPacketSize = bytesToSend + 4 + 4
+ }
+
+ ByteBuffer.wrap(segmentData).order(ByteOrder.LITTLE_ENDIAN).apply {
+ putInt(id)
+ putInt(if (segmentOpcode == Opcode.STREAM_START.value) data.remaining() else sendOffset)
+ if (segmentOpcode == Opcode.STREAM_START.value) {
+ put(opcode.toByte())
+ }
+ put(data.array(), data.position() + sendOffset, bytesToSend)
+ }
+
+ send(segmentOpcode, ByteBuffer.wrap(segmentData, 0, segmentPacketSize))
+ sendOffset += bytesToSend
+ }
+ } else {
+ synchronized(_sendLockObject) {
+ ByteBuffer.wrap(_sendBuffer).order(ByteOrder.LITTLE_ENDIAN).apply {
+ putInt(data.remaining() + 1)
+ put(opcode.toByte())
+ put(data.array(), data.position(), data.remaining())
+ }
+
+ //Logger.i(TAG, "Encrypting message (size = ${data.size + HEADER_SIZE})")
+ val len = _cipherStatePair!!.sender.encryptWithAd(null, _sendBuffer, 0, _sendBufferEncrypted, 0, data.remaining() + HEADER_SIZE)
+ //Logger.i(TAG, "Sending encrypted message (size = ${len})")
+ _outputStream.writeInt(len)
+ _outputStream.write(_sendBufferEncrypted, 0, len)
+ }
+ }
+ }
+
+ fun send(opcode: UByte) {
+ synchronized(_sendLockObject) {
+ ByteBuffer.wrap(_sendBuffer, 0, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(1)
+ _sendBuffer.asUByteArray()[4] = opcode
+
+ //Logger.i(TAG, "Encrypting message (size = ${HEADER_SIZE})")
+
+ val len = _cipherStatePair!!.sender.encryptWithAd(null, _sendBuffer, 0, _sendBufferEncrypted, 0, HEADER_SIZE)
+ //Logger.i(TAG, "Sending encrypted message (size = ${len})")
+
+ _outputStream.writeInt(len)
+ _outputStream.write(_sendBufferEncrypted, 0, len)
+ }
+ }
+
+ private fun handleData(data: ByteArray, length: Int) {
+ if (length < HEADER_SIZE)
+ throw Exception("Packet must be at least 5 bytes (header size)")
+
+ val size = ByteBuffer.wrap(data, 0, 4).order(ByteOrder.LITTLE_ENDIAN).int
+ if (size != length - 4)
+ throw Exception("Incomplete packet received")
+
+ val opcode = data.asUByteArray()[4]
+ val packetData = ByteBuffer.wrap(data, HEADER_SIZE, size - 1)
+
+ handlePacket(opcode, packetData.order(ByteOrder.LITTLE_ENDIAN))
+ }
+
+ private fun handlePacket(opcode: UByte, data: ByteBuffer) {
+ when (opcode) {
+ Opcode.PING.value -> {
+ send(Opcode.PONG.value)
+ //Logger.i(TAG, "Received ping, sent pong")
+ return
+ }
+ Opcode.PONG.value -> {
+ //Logger.i(TAG, "Received pong")
+ return
+ }
+ Opcode.NOTIFY_AUTHORIZED.value,
+ Opcode.NOTIFY_UNAUTHORIZED.value -> {
+ _onData.invoke(this, opcode, data)
+ return
+ }
+ }
+
+ if (authorizable?.isAuthorized != true) {
+ return
+ }
+
+ when (opcode) {
+ Opcode.STREAM_START.value -> {
+ val id = data.int
+ val expectedSize = data.int
+ val op = data.get().toUByte()
+
+ val syncStream = SyncStream(expectedSize, op)
+ if (data.remaining() > 0) {
+ syncStream.add(data.array(), data.position(), data.remaining())
+ }
+
+ synchronized(_syncStreams) {
+ _syncStreams[id] = syncStream
+ }
+ }
+ Opcode.STREAM_DATA.value -> {
+ val id = data.int
+ val expectedOffset = data.int
+
+ val syncStream = synchronized(_syncStreams) {
+ _syncStreams[id] ?: throw Exception("Received data for sync stream that does not exist")
+ }
+
+ if (expectedOffset != syncStream.bytesReceived) {
+ throw Exception("Expected offset does not match the amount of received bytes")
+ }
+
+ if (data.remaining() > 0) {
+ syncStream.add(data.array(), data.position(), data.remaining())
+ }
+ }
+ Opcode.STREAM_END.value -> {
+ val id = data.int
+ val expectedOffset = data.int
+
+ val syncStream = synchronized(_syncStreams) {
+ _syncStreams.remove(id) ?: throw Exception("Received data for sync stream that does not exist")
+ }
+
+ if (expectedOffset != syncStream.bytesReceived) {
+ throw Exception("Expected offset does not match the amount of received bytes")
+ }
+
+ if (data.remaining() > 0) {
+ syncStream.add(data.array(), data.position(), data.remaining())
+ }
+
+ if (!syncStream.isComplete) {
+ throw Exception("After sync stream end, the stream must be complete")
+ }
+
+ handlePacket(syncStream.opcode, syncStream.getBytes().let { ByteBuffer.wrap(it).order(ByteOrder.LITTLE_ENDIAN) })
+ }
+ else -> {
+ _onData.invoke(this, opcode, data)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "SyncSocketSession"
+ const val MAXIMUM_PACKET_SIZE = 65535 - 16
+ const val MAXIMUM_PACKET_SIZE_ENCRYPTED = MAXIMUM_PACKET_SIZE + 16
+ const val HEADER_SIZE = 5
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/sync/SyncStream.kt b/app/src/main/java/com/futo/platformplayer/sync/SyncStream.kt
new file mode 100644
index 00000000..339616e8
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/sync/SyncStream.kt
@@ -0,0 +1,41 @@
+package com.futo.platformplayer.sync
+
+class SyncStream(expectedSize: Int, val opcode: UByte) {
+ companion object {
+ const val MAXIMUM_SIZE = 10_000_000
+ }
+
+ private val _buffer: ByteArray = ByteArray(expectedSize)
+ private val _expectedSize: Int = expectedSize
+ var bytesReceived: Int = 0
+ private set
+ var isComplete: Boolean = false
+ private set
+
+ init {
+ if (expectedSize > MAXIMUM_SIZE) {
+ throw Exception("$expectedSize exceeded maximum size $MAXIMUM_SIZE")
+ }
+ }
+
+ fun add(data: ByteArray, offset: Int = 0, length: Int = data.size - offset) {
+ require(offset >= 0 && length >= 0 && offset + length <= data.size) { "Invalid offset or length" }
+
+ val remainingBytes = _expectedSize - bytesReceived
+ if (length > remainingBytes) {
+ throw Exception("More bytes received $length than expected remaining $remainingBytes")
+ }
+ data.copyInto(
+ destination = _buffer,
+ destinationOffset = bytesReceived,
+ startIndex = offset,
+ endIndex = offset + length
+ )
+ bytesReceived += length
+ isComplete = bytesReceived == _expectedSize
+ }
+
+ fun getBytes(): ByteArray {
+ return _buffer
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/views/sync/SyncDeviceView.kt b/app/src/main/java/com/futo/platformplayer/views/sync/SyncDeviceView.kt
new file mode 100644
index 00000000..e0133c1b
--- /dev/null
+++ b/app/src/main/java/com/futo/platformplayer/views/sync/SyncDeviceView.kt
@@ -0,0 +1,68 @@
+package com.futo.platformplayer.views.sync
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.futo.platformplayer.R
+import com.futo.platformplayer.constructs.Event0
+import com.futo.platformplayer.sync.LinkType
+
+class SyncDeviceView : ConstraintLayout {
+ val _imageLinkType: ImageView
+ val _textLinkType: TextView
+ val _imageClear: ImageView
+ val _textName: TextView
+ val _textStatus: TextView
+ val _layoutLinkType: LinearLayout
+ val onRemove: Event0 = Event0()
+
+ constructor(context: Context, attributeSet: AttributeSet? = null) : super(context) {
+ inflate(context, R.layout.view_sync, this)
+
+ _imageLinkType = findViewById(R.id.image_link_type)
+ _textLinkType = findViewById(R.id.text_link_type)
+ _imageClear = findViewById(R.id.image_clear)
+ _textName = findViewById(R.id.text_name)
+ _textStatus = findViewById(R.id.text_status)
+ _layoutLinkType = findViewById(R.id.layout_link_type)
+
+ _imageClear.setOnClickListener {
+ onRemove.emit()
+ }
+ }
+
+ fun setLinkType(linkType: LinkType): SyncDeviceView {
+ if (linkType == LinkType.None) {
+ _layoutLinkType.visibility = View.GONE
+ return this
+ }
+
+ _layoutLinkType.visibility = View.VISIBLE
+ _imageLinkType.setImageResource(when (linkType) {
+ LinkType.Proxied -> R.drawable.ic_internet
+ LinkType.Local -> R.drawable.ic_lan
+ else -> 0
+ })
+ _textLinkType.text = when(linkType) {
+ LinkType.Proxied -> "Proxied"
+ LinkType.Local -> "Local"
+ else -> null
+ }
+
+ return this
+ }
+
+ fun setName(name: String): SyncDeviceView {
+ _textName.text = name
+ return this
+ }
+
+ fun setStatus(status: String): SyncDeviceView {
+ _textStatus.text = status
+ return this
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_019be7_round_6dp.xml b/app/src/main/res/drawable/background_019be7_round_6dp.xml
new file mode 100644
index 00000000..034a0715
--- /dev/null
+++ b/app/src/main/res/drawable/background_019be7_round_6dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_1b_round_6dp.xml b/app/src/main/res/drawable/background_1b_round_6dp.xml
new file mode 100644
index 00000000..02056b71
--- /dev/null
+++ b/app/src/main/res/drawable/background_1b_round_6dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_2e_round.xml b/app/src/main/res/drawable/background_2e_round.xml
new file mode 100644
index 00000000..02674a04
--- /dev/null
+++ b/app/src/main/res/drawable/background_2e_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_border_2e_round_6dp.xml b/app/src/main/res/drawable/background_border_2e_round_6dp.xml
new file mode 100644
index 00000000..d28730f3
--- /dev/null
+++ b/app/src/main/res/drawable/background_border_2e_round_6dp.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_solid_border_2e_round_6dp.xml b/app/src/main/res/drawable/background_solid_border_2e_round_6dp.xml
new file mode 100644
index 00000000..544c218f
--- /dev/null
+++ b/app/src/main/res/drawable/background_solid_border_2e_round_6dp.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/device_sync.xml b/app/src/main/res/drawable/device_sync.xml
new file mode 100644
index 00000000..e5e33a4d
--- /dev/null
+++ b/app/src/main/res/drawable/device_sync.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_device.xml b/app/src/main/res/drawable/ic_device.xml
new file mode 100644
index 00000000..1667a699
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_internet.xml b/app/src/main/res/drawable/ic_internet.xml
new file mode 100644
index 00000000..f1f084b9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_internet.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_lan.xml b/app/src/main/res/drawable/ic_lan.xml
new file mode 100644
index 00000000..a0e30c58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_lan.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_online.xml b/app/src/main/res/drawable/ic_online.xml
new file mode 100644
index 00000000..58edc453
--- /dev/null
+++ b/app/src/main/res/drawable/ic_online.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_pair_fail.xml b/app/src/main/res/drawable/ic_pair_fail.xml
new file mode 100644
index 00000000..416e2840
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pair_fail.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_pair_success.xml b/app/src/main/res/drawable/ic_pair_success.xml
new file mode 100644
index 00000000..9a282b21
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pair_success.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_sync_home.xml b/app/src/main/res/layout/activity_sync_home.xml
new file mode 100644
index 00000000..6cb4872c
--- /dev/null
+++ b/app/src/main/res/layout/activity_sync_home.xml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sync_pair.xml b/app/src/main/res/layout/activity_sync_pair.xml
new file mode 100644
index 00000000..e95f324b
--- /dev/null
+++ b/app/src/main/res/layout/activity_sync_pair.xml
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sync_show_pairing_code.xml b/app/src/main/res/layout/activity_sync_show_pairing_code.xml
new file mode 100644
index 00000000..a7d5631a
--- /dev/null
+++ b/app/src/main/res/layout/activity_sync_show_pairing_code.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_sync.xml b/app/src/main/res/layout/view_sync.xml
new file mode 100644
index 00000000..461b8189
--- /dev/null
+++ b/app/src/main/res/layout/view_sync.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5097d091..dbc73061 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -354,6 +354,14 @@
Give feedback on the application
Info
Networking
+ Synchronization
+ Enable feature
+ Broadcast
+ Allow device to broadcast presence
+ Connect discovered
+ Allow device to search for and initiate connection with known paired devices
+ Try connect last
+ Allow device to automatically connect to last known
Gesture controls
Volume slider
Enable slide gesture to change volume
@@ -399,6 +407,8 @@
When the preview feedstyle is used, if items should auto-preview when scrolling over them
Log Level
Logging
+ Sync Grayjay
+ Sync your settings across multiple devices
Manage Polycentric identity
Manage your Polycentric identity
Manual check
diff --git a/app/src/test/java/com/futo/platformplayer/NoiseProtocolTests.kt b/app/src/test/java/com/futo/platformplayer/NoiseProtocolTests.kt
new file mode 100644
index 00000000..cf6e9290
--- /dev/null
+++ b/app/src/test/java/com/futo/platformplayer/NoiseProtocolTests.kt
@@ -0,0 +1,628 @@
+import com.futo.platformplayer.LittleEndianDataInputStream
+import com.futo.platformplayer.LittleEndianDataOutputStream
+import com.futo.platformplayer.logging.ILogConsumer
+import com.futo.platformplayer.logging.LogLevel
+import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.noise.protocol.CipherState
+import com.futo.platformplayer.noise.protocol.CipherStatePair
+import com.futo.platformplayer.noise.protocol.HandshakeState
+import com.futo.platformplayer.noise.protocol.Noise
+import com.futo.platformplayer.states.StateSync
+import com.futo.platformplayer.sync.IAuthorizable
+import com.futo.platformplayer.sync.SyncSocketSession
+import com.futo.platformplayer.sync.SyncStream
+import junit.framework.TestCase.assertEquals
+import org.junit.Assert.assertArrayEquals
+import org.junit.Test
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.PipedInputStream
+import java.io.PipedOutputStream
+import java.lang.Thread.sleep
+import java.nio.ByteBuffer
+import java.util.Base64
+import java.util.Random
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.math.exp
+
+
+class NoiseProtocolTest {
+ constructor() {
+ Logger.setLogConsumers(listOf(
+ object : ILogConsumer {
+ override fun willConsume(level: LogLevel, tag: String): Boolean {
+ return true
+ }
+
+ override fun consume(level: LogLevel, tag: String, text: String?, e: Throwable?) {
+ when (level) {
+ LogLevel.VERBOSE -> println("${level};INTERNAL;$tag: ${text}, ${e}")
+ LogLevel.INFORMATION -> println("${level};INTERNAL;$tag: ${text}, ${e}")
+ LogLevel.WARNING -> println("${level};INTERNAL;$tag: ${text}, ${e}")
+ LogLevel.ERROR -> println("${level};INTERNAL;$tag: ${text}, ${e}")
+ else -> throw Exception("Unknown log level")
+ }
+ }
+
+ }
+ ))
+ }
+
+ class TestMessage {
+ val payload: ByteArray
+ val cipherText: ByteArray
+
+ @OptIn(ExperimentalStdlibApi::class)
+ constructor(p: String, c: String) {
+ payload = p.hexToByteArray()
+ cipherText = c.hexToByteArray()
+ }
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testNoiseIK25519HandshakeAndMessage() {
+ val dh = "25519"
+ val pattern = "IK"
+ val cipher = "ChaChaPoly"
+ val hash = "BLAKE2b"
+ var protocolName = "Noise"
+ protocolName += "_${pattern}_${dh}_${cipher}_${hash}"
+
+ val messages = arrayListOf(
+ TestMessage("4c756477696720766f6e204d69736573", "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c7944ba83a447b38c83e327ad936929812f624884847b7831e95e197b2f797088efdd232fe541af156ec6d0657602902a8c3ee64e470f4b6fcd9298ce0b56fe20f86e60d9d933ec6e103ffb09e6001d6abb64"),
+ TestMessage("4d757272617920526f746862617264", "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f1448088439f069b267a06b3de3ecb1043bcb09807c6cd101f3826192a65f11ef3fe4317"),
+ TestMessage("462e20412e20486179656b", "cd54383060e7a28434cca27fb1cc524cfbabeb18181589df219d07"),
+ TestMessage("4361726c204d656e676572", "a856d3bf0246bfc476c655009cd1ed677b8dcc5b349ae8ef2a05f2"),
+ TestMessage("4a65616e2d426170746973746520536179", "49063084b2c51f098337cb8a13739ac848f907e67cfb2cc8a8b60586467aa02fc7"),
+ TestMessage("457567656e2042f6686d20766f6e2042617765726b", "8b9709d23b47e4639df7678d7a21741eba4ef1e9c60383001c7435549c20f9d56f30e935d3")
+ )
+
+ val initiator = HandshakeState(protocolName, HandshakeState.INITIATOR)
+ val responder = HandshakeState(protocolName, HandshakeState.RESPONDER)
+ assertEquals(HandshakeState.INITIATOR, initiator.role)
+ assertEquals(HandshakeState.RESPONDER, responder.role)
+ assertEquals(protocolName, initiator.protocolName)
+ assertEquals(protocolName, responder.protocolName)
+
+ // Set all keys and special values that we need.
+ val init_prologue = "50726f6c6f677565313233".hexToByteArray()
+ initiator.setPrologue(init_prologue, 0, init_prologue.size)
+ initiator.localKeyPair.setPrivateKey("e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1".hexToByteArray(), 0)
+ initiator.remotePublicKey.setPublicKey("31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62".hexToByteArray(), 0)
+ initiator.getFixedEphemeralKey().setPrivateKey("893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a".hexToByteArray(), 0)
+
+ val prologue = "50726f6c6f677565313233".hexToByteArray()
+ responder.setPrologue(prologue, 0, prologue.size)
+
+ responder.localKeyPair.setPrivateKey("4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893".hexToByteArray(), 0)
+ responder.getFixedEphemeralKey().setPrivateKey("bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b".hexToByteArray(), 0)
+
+ // Start both sides of the handshake.
+ assertEquals(HandshakeState.NO_ACTION, initiator.action)
+ assertEquals(HandshakeState.NO_ACTION, responder.action)
+ initiator.start()
+ responder.start()
+ assertEquals(HandshakeState.WRITE_MESSAGE, initiator.action)
+ assertEquals(HandshakeState.READ_MESSAGE, responder.action)
+
+ // Work through the messages one by one until both sides "split".
+ var role: Int = HandshakeState.INITIATOR
+ var index = 0
+ var send: HandshakeState?
+ var recv: HandshakeState?
+ val isOneWay = false
+ val message = ByteArray(8192)
+ val plaintext = ByteArray(8192)
+ while (index < messages.size) {
+ if (initiator.action == HandshakeState.SPLIT && responder.action == 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.action)
+ assertEquals(HandshakeState.READ_MESSAGE, recv.action)
+ val msg: TestMessage = messages[index]
+ val len: Int = send.writeMessage(message, 0, msg.payload, 0, msg.payload.size)
+ assertEquals(msg.cipherText.size, len)
+ assertSubArrayEquals("$index: ciphertext", msg.cipherText, message)
+ val plen: Int = recv.readMessage(message, 0, len, plaintext, 0)
+ assertEquals(msg.payload.size, plen)
+ assertSubArrayEquals("$index: payload", msg.payload, plaintext)
+ ++index
+ }
+
+ assertEquals(HandshakeState.INITIATOR, initiator.role)
+ assertEquals(HandshakeState.RESPONDER, responder.role)
+
+ // Handshake finished. Check the handshake hash values.
+ val handshakeHash = "00e51d2aac81a9b8ebe441d6af3e1c8efc0f030cc608332edcb42588ff6a0ce26415ddc106e95277a5e6d54132f1e5245976b89caf96d262f1fe5a7f0c55c078".hexToByteArray()
+ assertArrayEquals(handshakeHash, initiator.getHandshakeHash())
+ assertArrayEquals(handshakeHash, responder.getHandshakeHash())
+ assertEquals(HandshakeState.SPLIT, initiator.action)
+ assertEquals(HandshakeState.SPLIT, responder.action)
+
+
+ // Split the two sides to get the transport ciphers.
+ val initPair: CipherStatePair = initiator.split()
+ val respPair: CipherStatePair = responder.split()
+ assertEquals(HandshakeState.COMPLETE, initiator.action)
+ assertEquals(HandshakeState.COMPLETE, responder.action)
+
+ // Now handle the data transport.
+ var csend: CipherState?
+ var crecv: CipherState?
+ while (index < messages.size) {
+ val msg: TestMessage = messages[index]
+ if (role == HandshakeState.INITIATOR) {
+ // Send on the initiator, receive on the responder.
+ csend = initPair.sender
+ crecv = respPair.receiver
+ if (!isOneWay)role = HandshakeState.RESPONDER
+ } else {
+ // Send on the responder, receive on the initiator.
+ csend = respPair.sender
+ crecv = initPair.receiver
+ role = HandshakeState.INITIATOR
+ }
+ val len: Int = csend.encryptWithAd(null, msg.payload, 0, message, 0, msg.payload.size)
+ assertEquals(msg.cipherText.size, len)
+ assertSubArrayEquals("$index: ciphertext", msg.cipherText, message)
+ val plen: Int = crecv.decryptWithAd(null, message, 0, plaintext, 0, len)
+ assertEquals(msg.payload.size, plen)
+ assertSubArrayEquals("$index: payload", msg.payload, plaintext)
+ ++index
+ }
+
+ // Clean up.
+ initiator.destroy()
+ responder.destroy()
+ initPair.destroy()
+ respPair.destroy()
+ }
+
+ @Test
+ fun testNoiseIK25519HandshakeAndMessageRandom() {
+ val dh = "25519"
+ val pattern = "IK"
+ val cipher = "ChaChaPoly"
+ val hash = "BLAKE2b"
+ var protocolName = "Noise"
+ protocolName += "_${pattern}_${dh}_${cipher}_${hash}"
+
+ val initiator = HandshakeState(protocolName, HandshakeState.INITIATOR)
+ val responder = HandshakeState(protocolName, HandshakeState.RESPONDER)
+ assertEquals(HandshakeState.INITIATOR, initiator.role)
+ assertEquals(HandshakeState.RESPONDER, responder.role)
+ assertEquals(protocolName, initiator.protocolName)
+ assertEquals(protocolName, responder.protocolName)
+
+ // Set all keys and special values that we need.
+ responder.localKeyPair.generateKeyPair()
+ val responderPublicKey = ByteArray(responder.localKeyPair.publicKeyLength)
+ responder.localKeyPair.getPublicKey(responderPublicKey, 0)
+
+ initiator.localKeyPair.generateKeyPair()
+ initiator.remotePublicKey.setPublicKey(responderPublicKey, 0)
+
+ // Start both sides of the handshake.
+ assertEquals(HandshakeState.NO_ACTION, initiator.action)
+ assertEquals(HandshakeState.NO_ACTION, responder.action)
+ initiator.start()
+ responder.start()
+ assertEquals(HandshakeState.WRITE_MESSAGE, initiator.action)
+ assertEquals(HandshakeState.READ_MESSAGE, responder.action)
+
+ // Work through the messages one by one until both sides "split".
+ var role: Int = HandshakeState.INITIATOR
+ var send: HandshakeState?
+ var recv: HandshakeState?
+ val message = ByteArray(8192)
+ val plaintext = ByteArray(8192)
+ for (i in 0..10) {
+ if (i == 9)
+ throw Exception("Handshake not finished in time")
+
+ if (initiator.action == HandshakeState.SPLIT && responder.action == HandshakeState.SPLIT) {
+ break
+ }
+
+ if (role == HandshakeState.INITIATOR) {
+ // Send on the initiator, receive on the responder.
+ send = initiator
+ recv = responder
+ role = HandshakeState.RESPONDER
+ } else {
+ // Send on the responder, receive on the initiator.
+ send = responder
+ recv = initiator
+ role = HandshakeState.INITIATOR
+ }
+ assertEquals(HandshakeState.WRITE_MESSAGE, send.action)
+ assertEquals(HandshakeState.READ_MESSAGE, recv.action)
+ val len: Int = send.writeMessage(message, 0, null, 0, 0)
+ recv.readMessage(message, 0, len, plaintext, 0)
+ }
+
+ assertEquals(HandshakeState.INITIATOR, initiator.role)
+ assertEquals(HandshakeState.RESPONDER, responder.role)
+
+ // Handshake finished. Check the handshake hash values.
+ assertEquals(HandshakeState.SPLIT, initiator.action)
+ assertEquals(HandshakeState.SPLIT, responder.action)
+
+ // Split the two sides to get the transport ciphers.
+ val initPair: CipherStatePair = initiator.split()
+ val respPair: CipherStatePair = responder.split()
+ assertEquals(HandshakeState.COMPLETE, initiator.action)
+ assertEquals(HandshakeState.COMPLETE, responder.action)
+
+ // Now handle the data transport.
+ var csend: CipherState?
+ var crecv: CipherState?
+ for (i in 0..1) {
+ if (role == HandshakeState.INITIATOR) {
+ // Send on the initiator, receive on the responder.
+ csend = initPair.sender
+ crecv = respPair.receiver
+ role = HandshakeState.RESPONDER
+ } else {
+ // Send on the responder, receive on the initiator.
+ csend = respPair.sender
+ crecv = initPair.receiver
+ role = HandshakeState.INITIATOR
+ }
+ val expected = "Message counter $i"
+ val payload = expected.toByteArray()
+ val len: Int = csend.encryptWithAd(null, payload, 0, message, 0, payload.size)
+ val plen: Int = crecv.decryptWithAd(null, message, 0, plaintext, 0, len)
+ assertEquals(expected, plaintext.slice(IntRange(0, plen - 1)).toByteArray().decodeToString())
+ }
+
+ // Clean up.
+ initiator.destroy()
+ responder.destroy()
+ initPair.destroy()
+ respPair.destroy()
+ }
+
+ @Test
+ fun testNoiseIK25519HandshakeAndMessageRandomSenderReceiver() {
+ val dh = "25519"
+ val pattern = "IK"
+ val cipher = "ChaChaPoly"
+ val hash = "BLAKE2b"
+ var protocolName = "Noise"
+ protocolName += "_${pattern}_${dh}_${cipher}_${hash}"
+
+ val initiator = HandshakeState(protocolName, HandshakeState.INITIATOR)
+ assertEquals(HandshakeState.INITIATOR, initiator.role)
+ assertEquals(protocolName, initiator.protocolName)
+
+ val responder = HandshakeState(protocolName, HandshakeState.RESPONDER)
+ assertEquals(HandshakeState.RESPONDER, responder.role)
+ assertEquals(protocolName, responder.protocolName)
+
+ responder.localKeyPair.generateKeyPair()
+ val responderPublicKey = ByteArray(responder.localKeyPair.publicKeyLength)
+ responder.localKeyPair.getPublicKey(responderPublicKey, 0)
+
+ initiator.localKeyPair.generateKeyPair()
+ initiator.remotePublicKey.setPublicKey(responderPublicKey, 0)
+
+ val initiatorToResponderOut = PipedOutputStream()
+ val responderToInitiatorOut = PipedOutputStream()
+
+ val initiatorToResponderIn = PipedInputStream(initiatorToResponderOut)
+ val responderToInitiatorIn = PipedInputStream(responderToInitiatorOut)
+
+ // Start both sides of the handshake.
+ val responderThread = Thread {
+ val writer = DataOutputStream(responderToInitiatorOut)
+ val reader = DataInputStream(initiatorToResponderIn)
+
+ writer.writeInt(23145)
+ val testValue = reader.readInt()
+ assertEquals(3141, testValue)
+
+ assertEquals(HandshakeState.NO_ACTION, responder.action)
+ responder.start()
+ assertEquals(HandshakeState.READ_MESSAGE, responder.action)
+
+ System.out.println("Responder start")
+
+ val message = ByteArray(8192)
+ val plaintext = ByteArray(8192)
+
+ var len: Int
+ while (true) {
+ if (responder.action == HandshakeState.SPLIT) {
+ break
+ }
+
+ val messageSize = reader.readInt()
+ reader.read(message, 0, messageSize)
+ System.out.println("Responder read (messageSize = ${messageSize}): ${Base64.getEncoder().encodeToString(message.slice(IntRange(0, messageSize - 1)).toByteArray())}")
+ responder.readMessage(message, 0, messageSize, plaintext, 0)
+
+ if (responder.action == HandshakeState.SPLIT) {
+ break
+ }
+
+ len = responder.writeMessage(message, 0, null, 0, 0)
+ writer.writeInt(len)
+ writer.write(message, 0, len)
+ System.out.println("Responder wrote (len = ${len}): ${Base64.getEncoder().encodeToString(message.slice(IntRange(0, len - 1)).toByteArray())}")
+ }
+
+ System.out.println("Responder handshake complete")
+
+ assertEquals(HandshakeState.RESPONDER, responder.role)
+ assertEquals(HandshakeState.SPLIT, responder.action)
+ val respPair: CipherStatePair = responder.split()
+ assertEquals(HandshakeState.COMPLETE, responder.action)
+
+ assertEquals(true, responder.hasRemotePublicKey())
+ val responderRemotePublicKey = ByteArray(responder.remotePublicKey.publicKeyLength)
+ responder.remotePublicKey.getPublicKey(responderRemotePublicKey, 0)
+ val initiatorLocalPublicKey = ByteArray(initiator.localKeyPair.publicKeyLength)
+ initiator.localKeyPair.getPublicKey(initiatorLocalPublicKey, 0)
+ assertArrayEquals(initiatorLocalPublicKey, responderRemotePublicKey)
+
+ System.out.println("Initiator local public key: ${Base64.getEncoder().encodeToString(initiatorLocalPublicKey)}")
+ System.out.println("Responder remote public key: ${Base64.getEncoder().encodeToString(responderRemotePublicKey)}")
+
+ //Handshake complete, now exchange a message
+ val expected = "Hello from responder"
+ val payload = expected.toByteArray()
+ len = respPair.sender.encryptWithAd(null, payload, 0, message, 0, payload.size)
+ writer.writeInt(len)
+ writer.write(message, 0, len)
+
+ val messageSize = reader.readInt()
+ reader.read(message, 0, messageSize)
+ val plen: Int = respPair.receiver.decryptWithAd(null, message, 0, plaintext, 0, messageSize)
+
+ val ptext = plaintext.slice(IntRange(0, plen - 1)).toByteArray().decodeToString()
+ System.out.println("Responder read: ${ptext}")
+ assertEquals("Hello from initiator", ptext)
+
+ respPair.destroy()
+ }.apply { start() }
+
+ val initiatorThread = Thread {
+ val writer = DataOutputStream(initiatorToResponderOut)
+ val reader = DataInputStream(responderToInitiatorIn)
+
+ writer.writeInt(3141)
+ val testValue = reader.readInt()
+ assertEquals(23145, testValue)
+
+ assertEquals(HandshakeState.NO_ACTION, initiator.action)
+ initiator.start()
+ assertEquals(HandshakeState.WRITE_MESSAGE, initiator.action)
+
+ val message = ByteArray(8192)
+ val plaintext = ByteArray(8192)
+
+ System.out.println("Initiator start")
+
+ var len: Int = initiator.writeMessage(message, 0, null, 0, 0)
+ writer.writeInt(len)
+ writer.write(message, 0, len)
+
+ System.out.println("Initiator wrote: ${Base64.getEncoder().encodeToString(message.slice(IntRange(0, len - 1)).toByteArray())}")
+
+ while (true) {
+ if (initiator.action == HandshakeState.SPLIT) {
+ break
+ }
+
+ val messageSize = reader.readInt()
+ reader.read(message, 0, messageSize)
+ System.out.println("Initiator read (messageSize = ${messageSize}): ${Base64.getEncoder().encodeToString(message.slice(IntRange(0, messageSize - 1)).toByteArray())}")
+ initiator.readMessage(message, 0, messageSize, plaintext, 0)
+
+ if (initiator.action == HandshakeState.SPLIT) {
+ break
+ }
+
+ len = initiator.writeMessage(message, 0, null, 0, 0)
+ writer.writeInt(len)
+ writer.write(message, 0, len)
+
+ System.out.println("Initiator wrote (len = ${len}): ${Base64.getEncoder().encodeToString(message.slice(IntRange(0, len - 1)).toByteArray())}")
+ }
+
+ System.out.println("Initiator handshake complete")
+
+ assertEquals(HandshakeState.INITIATOR, initiator.role)
+ assertEquals(HandshakeState.SPLIT, initiator.action)
+ val initPair: CipherStatePair = initiator.split()
+ assertEquals(HandshakeState.COMPLETE, initiator.action)
+
+ assertEquals(true, initiator.hasRemotePublicKey())
+ val initiatorRemotePublicKey = ByteArray(initiator.remotePublicKey.publicKeyLength)
+ initiator.remotePublicKey.getPublicKey(initiatorRemotePublicKey, 0)
+ val responderLocalPublicKey = ByteArray(responder.localKeyPair.publicKeyLength)
+ responder.localKeyPair.getPublicKey(responderLocalPublicKey, 0)
+ assertArrayEquals(responderLocalPublicKey, initiatorRemotePublicKey)
+
+ System.out.println("Responder local public key: ${Base64.getEncoder().encodeToString(responderLocalPublicKey)}")
+ System.out.println("Initiator remote public key: ${Base64.getEncoder().encodeToString(initiatorRemotePublicKey)}")
+
+ val expected = "Hello from initiator"
+ val payload = expected.toByteArray()
+ len = initPair.sender.encryptWithAd(null, payload, 0, message, 0, payload.size)
+ writer.writeInt(len)
+ writer.write(message, 0, len)
+
+ val messageSize = reader.readInt()
+ reader.read(message, 0, messageSize)
+ val plen: Int = initPair.receiver.decryptWithAd(null, message, 0, plaintext, 0, messageSize)
+
+ val ptext = plaintext.slice(IntRange(0, plen - 1)).toByteArray().decodeToString()
+ System.out.println("Initiator read: ${ptext}")
+ assertEquals("Hello from responder", ptext)
+
+ initPair.destroy()
+ }.apply { start() }
+
+ responderThread.join()
+ initiatorThread.join()
+
+ initiator.destroy()
+ responder.destroy()
+ }
+
+ private class Authorized : IAuthorizable {
+ override val isAuthorized: Boolean = true
+ }
+
+ @Test
+ fun testSyncSessionHandshakeAndCommunication() {
+ // Create piped streams to simulate a network connection
+ val initiatorToResponderOut = PipedOutputStream()
+ val responderToInitiatorOut = PipedOutputStream()
+ val initiatorToResponderIn = PipedInputStream(initiatorToResponderOut)
+ val responderToInitiatorIn = PipedInputStream(responderToInitiatorOut)
+
+ val initiatorInput = LittleEndianDataInputStream(responderToInitiatorIn)
+ val initiatorOutput = LittleEndianDataOutputStream(initiatorToResponderOut)
+ val responderInput = LittleEndianDataInputStream(initiatorToResponderIn)
+ val responderOutput = LittleEndianDataOutputStream(responderToInitiatorOut)
+
+ // Latches to track when handshake and communication are complete
+ val handshakeLatch = CountDownLatch(2)
+
+ val initiatorKeyPair = Noise.createDH(StateSync.dh)
+ initiatorKeyPair.generateKeyPair()
+ val responderKeyPair = Noise.createDH(StateSync.dh)
+ responderKeyPair.generateKeyPair()
+
+ val randomBytesExactlyOnePacket = generateRandomByteArray(SyncSocketSession.MAXIMUM_PACKET_SIZE - SyncSocketSession.HEADER_SIZE)
+ val randomBytes = generateRandomByteArray(2 * (SyncSocketSession.MAXIMUM_PACKET_SIZE - SyncSocketSession.HEADER_SIZE))
+ val randomBytesBig = generateRandomByteArray(SyncStream.MAXIMUM_SIZE)
+
+ // Create and start the initiator session
+ val initiatorSession = SyncSocketSession("", initiatorKeyPair,
+ initiatorInput,
+ initiatorOutput,
+ onClose = { session ->
+ println("Initiator session closed")
+ },
+ onHandshakeComplete = { session ->
+ println("Initiator handshake complete")
+ handshakeLatch.countDown() // Handshake complete for initiator
+ },
+ onData = { session, opcode, data ->
+ println("Initiator received: Opcode $opcode, Data Length: ${data.remaining()}")
+
+ when (data.remaining()) {
+ randomBytesExactlyOnePacket.remaining() -> {
+ assertByteBufferEquals(randomBytesExactlyOnePacket, data)
+ println("randomBytesExactlyOnePacket valid")
+ }
+ randomBytes.remaining() -> {
+ assertByteBufferEquals(randomBytes, data)
+ println("randomBytes valid")
+ }
+ randomBytesBig.remaining() -> {
+ assertByteBufferEquals(randomBytesBig, data)
+ println("randomBytesBig valid")
+ }
+ else -> println("Unknown data size received")
+ }
+ }
+ )
+
+ // Create and start the responder session
+ val responderSession = SyncSocketSession("", responderKeyPair,
+ responderInput,
+ responderOutput,
+ onClose = { session ->
+ println("Responder session closed")
+ },
+ onHandshakeComplete = { session ->
+ println("Responder handshake complete")
+ handshakeLatch.countDown() // Handshake complete for responder
+ },
+ onData = { session, opcode, data ->
+ println("Responder received: Opcode $opcode, Data Length: ${data.remaining()}")
+
+ when (data.remaining()) {
+ randomBytesExactlyOnePacket.remaining() -> {
+ assertByteBufferEquals(randomBytesExactlyOnePacket, data)
+ println("randomBytesExactlyOnePacket valid")
+ }
+ randomBytes.remaining() -> {
+ assertByteBufferEquals(randomBytes, data)
+ println("randomBytes valid")
+ }
+ randomBytesBig.remaining() -> {
+ assertByteBufferEquals(randomBytesBig, data)
+ println("randomBytesBig valid")
+ }
+ else -> println("Unknown data size received")
+ }
+ }
+ )
+
+ initiatorSession.startAsInitiator(responderSession.localPublicKey)
+ responderSession.startAsResponder()
+
+ initiatorSession.authorizable = Authorized()
+ responderSession.authorizable = Authorized()
+
+ handshakeLatch.await(10, TimeUnit.SECONDS)
+
+ // Simulate initiator sending a PING and responder replying with PONG
+ initiatorSession.send(SyncSocketSession.Opcode.PING.value)
+ responderSession.send(SyncSocketSession.Opcode.PONG.value)
+
+ // Test data transfer
+ responderSession.send(SyncSocketSession.Opcode.NOTIFY_AUTHORIZED.value, randomBytesExactlyOnePacket)
+ initiatorSession.send(SyncSocketSession.Opcode.NOTIFY_AUTHORIZED.value, randomBytes)
+
+ // Send large data to test stream handling
+ val start = System.currentTimeMillis()
+ responderSession.send(SyncSocketSession.Opcode.NOTIFY_AUTHORIZED.value, randomBytesBig)
+ println("Sent 10MB in ${System.currentTimeMillis() - start}ms")
+
+ // Wait for a brief period to simulate delay and allow communication
+ sleep(1000)
+
+ // Stop both sessions after the test
+ initiatorSession.stop()
+ responderSession.stop()
+ }
+
+ private fun generateRandomByteArray(size: Int): ByteBuffer {
+ val random = Random()
+ return ByteBuffer.wrap(ByteArray(size).apply { random.nextBytes(this) })
+ }
+
+ private fun assertSubArrayEquals(msg: String, expected: ByteArray, actual: ByteArray) {
+ for (index in expected.indices) assertEquals("$msg[$index]", expected[index], actual[index])
+ }
+
+ private fun assertByteBufferEquals(expected: ByteBuffer, actual: ByteBuffer) {
+ if (expected.remaining() != actual.remaining())
+ throw Exception("ByteBuffers have a different length")
+
+ for (i in 0 until expected.remaining()) {
+ if (expected.array()[expected.position() + i] != actual.array()[actual.position() + i])
+ throw Exception("Byte mismatch at index ${i}")
+ }
+ }
+}
\ No newline at end of file