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