From c3c395832c6a661c81973c8a36fd7da7778d8797 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 24 Jul 2025 12:01:46 +0200 Subject: [PATCH] Implement Noise XX Handshake Protocol for Direct Messages (#180) * noise * works? * noise * temporary * better * wip: use subnet * better * barely working * werk * subnet * fix peer ID * 8 byte peer ID * wip noise * wip fixes for noise * std lib for noise * noise handshake one step further * buffers * use fork * fix imports * simplify counter * remove trash * hashing * no prologue * nice * wip, use noise encryption * peer ID hex * simplify session manager * heavy logging * use singleton * Fix Noise session race condition with elegant per-peer actor serialization - Use Kotlin coroutine actors for per-peer packet processing - Each peer gets dedicated actor that processes packets sequentially - Eliminates race conditions in session management without complex locking - Single surgical change in PacketProcessor - minimal, maintainable - Leverages Kotlin's native concurrency primitives * decrypt correctly * iniator works now * clean code and fix signature to null * better * no signature in private message * small fixes * refactor ack * refactor but untested * messages working * wip ack * wip fix ack * more logging * pending tracker * keep pending connections on errors * less logging * refactor model * refactor frombinarydata * idendityannouncement refactor and update to new binary protocol * fix keys * refix keys * dms work * revert to mainnet * do not change bluetooth adapter name * keep code but uncomment * clean up comments * cleanup comments --- .../android/crypto/EncryptionService.kt | 472 +++-- .../identity/SecureIdentityStateManager.kt | 289 +++ .../mesh/BluetoothConnectionManager.kt | 14 +- .../mesh/BluetoothConnectionTracker.kt | 11 +- .../mesh/BluetoothGattClientManager.kt | 26 +- .../mesh/BluetoothGattServerManager.kt | 2 +- .../android/mesh/BluetoothMeshService.kt | 424 ++++- .../mesh/BluetoothPacketBroadcaster.kt | 94 +- .../bitchat/android/mesh/FragmentManager.kt | 5 +- .../bitchat/android/mesh/MessageHandler.kt | 233 ++- .../bitchat/android/mesh/PacketProcessor.kt | 118 +- .../com/bitchat/android/mesh/PowerManager.kt | 4 - .../bitchat/android/mesh/SecurityManager.kt | 65 +- .../android/mesh/StoreForwardManager.kt | 5 +- .../bitchat/android/model/BitchatMessage.kt | 64 +- .../com/bitchat/android/model/DeliveryAck.kt | 43 + .../model/NoiseIdentityAnnouncement.kt | 199 ++ .../com/bitchat/android/model/ReadReceipt.kt | 37 + .../android/noise/NoiseChannelEncryption.kt | 276 +++ .../android/noise/NoiseEncryptionService.kt | 362 ++++ .../com/bitchat/android/noise/NoiseSession.kt | 556 ++++++ .../android/noise/NoiseSessionManager.kt | 220 +++ .../crypto/Blake2bMessageDigest.java | 243 +++ .../crypto/Blake2sMessageDigest.java | 233 +++ .../southernstorm/crypto/ChaChaCore.java | 200 ++ .../southernstorm/crypto/Curve25519.java | 531 ++++++ .../noise/southernstorm/crypto/Curve448.java | 622 +++++++ .../noise/southernstorm/crypto/GHASH.java | 204 +++ .../noise/southernstorm/crypto/NewHope.java | 1605 +++++++++++++++++ .../southernstorm/crypto/NewHopeTor.java | 982 ++++++++++ .../noise/southernstorm/crypto/Poly1305.java | 327 ++++ .../southernstorm/crypto/RijndaelAES.java | 1099 +++++++++++ .../crypto/SHA256MessageDigest.java | 244 +++ .../crypto/SHA512MessageDigest.java | 265 +++ .../southernstorm/crypto/package-info.java | 12 + .../protocol/AESGCMFallbackCipherState.java | 265 +++ .../protocol/AESGCMOnCtrCipherState.java | 335 ++++ .../protocol/ChaChaPolyCipherState.java | 290 +++ .../southernstorm/protocol/CipherState.java | 162 ++ .../protocol/CipherStatePair.java | 106 ++ .../protocol/Curve25519DHState.java | 156 ++ .../protocol/Curve448DHState.java | 156 ++ .../noise/southernstorm/protocol/DHState.java | 156 ++ .../southernstorm/protocol/DHStateHybrid.java | 60 + .../southernstorm/protocol/Destroyable.java | 43 + .../protocol/HandshakeState.java | 1290 +++++++++++++ .../protocol/NewHopeDHState.java | 340 ++++ .../noise/southernstorm/protocol/Noise.java | 237 +++ .../noise/southernstorm/protocol/Pattern.java | 835 +++++++++ .../protocol/SymmetricState.java | 532 ++++++ .../southernstorm/protocol/package-info.java | 7 + .../android/protocol/BinaryProtocol.kt | 91 +- .../android/protocol/CompressionUtil.kt | 86 + .../android/protocol/MessagePadding.kt | 77 + .../java/com/bitchat/android/ui/ChatHeader.kt | 17 +- .../android/util/BinaryEncodingUtils.kt | 365 ++++ .../android/util/ByteArrayExtensions.kt | 8 + .../bitchat/android/util/ByteArrayWrapper.kt | 26 + 58 files changed, 15209 insertions(+), 517 deletions(-) create mode 100644 app/src/main/java/com/bitchat/android/identity/SecureIdentityStateManager.kt create mode 100644 app/src/main/java/com/bitchat/android/model/DeliveryAck.kt create mode 100644 app/src/main/java/com/bitchat/android/model/NoiseIdentityAnnouncement.kt create mode 100644 app/src/main/java/com/bitchat/android/model/ReadReceipt.kt create mode 100644 app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt create mode 100644 app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt create mode 100644 app/src/main/java/com/bitchat/android/noise/NoiseSession.kt create mode 100644 app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Blake2bMessageDigest.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Blake2sMessageDigest.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/ChaChaCore.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Curve25519.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Curve448.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/GHASH.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/NewHope.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/NewHopeTor.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Poly1305.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/RijndaelAES.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/SHA256MessageDigest.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/SHA512MessageDigest.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/package-info.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMFallbackCipherState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/ChaChaPolyCipherState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/CipherState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/CipherStatePair.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Curve25519DHState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Curve448DHState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/DHState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/DHStateHybrid.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Destroyable.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/NewHopeDHState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Noise.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Pattern.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java create mode 100644 app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/package-info.java create mode 100644 app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt create mode 100644 app/src/main/java/com/bitchat/android/protocol/MessagePadding.kt create mode 100644 app/src/main/java/com/bitchat/android/util/BinaryEncodingUtils.kt create mode 100644 app/src/main/java/com/bitchat/android/util/ByteArrayExtensions.kt create mode 100644 app/src/main/java/com/bitchat/android/util/ByteArrayWrapper.kt diff --git a/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt b/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt index f7611d70..7457de1a 100644 --- a/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt +++ b/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt @@ -1,321 +1,317 @@ package com.bitchat.android.crypto import android.content.Context -import android.content.SharedPreferences -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties -import org.bouncycastle.crypto.agreement.X25519Agreement -import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator -import org.bouncycastle.crypto.generators.X25519KeyPairGenerator -import org.bouncycastle.crypto.params.* -import org.bouncycastle.crypto.signers.Ed25519Signer -import org.bouncycastle.crypto.util.PrivateKeyFactory -import org.bouncycastle.crypto.util.PublicKeyFactory -import org.bouncycastle.jcajce.provider.digest.SHA256 +import android.util.Log +import com.bitchat.android.noise.NoiseEncryptionService import java.security.SecureRandom -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.SecretKeySpec -import kotlin.experimental.and +import java.util.concurrent.ConcurrentHashMap /** - * Encryption service that's 100% compatible with iOS version - * Uses the same cryptographic algorithms and key derivation + * Encryption service that now uses NoiseEncryptionService internally + * Maintains the same public API for backward compatibility + * + * This is the main interface for all encryption/decryption operations in bitchat. + * It now uses the Noise protocol for secure transport encryption with proper session management. */ class EncryptionService(private val context: Context) { - // Key agreement keys for encryption - private val privateKey: X25519PrivateKeyParameters - private val publicKey: X25519PublicKeyParameters + companion object { + private const val TAG = "EncryptionService" + } - // Signing keys for authentication - private val signingPrivateKey: Ed25519PrivateKeyParameters - private val signingPublicKey: Ed25519PublicKeyParameters + // Core Noise encryption service + private val noiseService: NoiseEncryptionService = NoiseEncryptionService(context) - // Persistent identity for favorites (separate from ephemeral keys) - private lateinit var identityKey: Ed25519PrivateKeyParameters - private lateinit var identityPublicKey: Ed25519PublicKeyParameters + // Session tracking for established connections + private val establishedSessions = ConcurrentHashMap() // peerID -> fingerprint - // Storage for peer keys - private val peerPublicKeys = mutableMapOf() - private val peerSigningKeys = mutableMapOf() - private val peerIdentityKeys = mutableMapOf() - private val sharedSecrets = mutableMapOf() - - private val prefs: SharedPreferences = context.getSharedPreferences("bitchat_crypto", Context.MODE_PRIVATE) - private val secureRandom = SecureRandom() + // Callbacks for UI state updates + var onSessionEstablished: ((String) -> Unit)? = null // peerID + var onSessionLost: ((String) -> Unit)? = null // peerID + var onHandshakeRequired: ((String) -> Unit)? = null // peerID init { - // Generate ephemeral key pairs for this session - val x25519Generator = X25519KeyPairGenerator() - x25519Generator.init(X25519KeyGenerationParameters(secureRandom)) - val x25519KeyPair = x25519Generator.generateKeyPair() - privateKey = x25519KeyPair.private as X25519PrivateKeyParameters - publicKey = x25519KeyPair.public as X25519PublicKeyParameters + // Set up NoiseEncryptionService callbacks + noiseService.onPeerAuthenticated = { peerID, fingerprint -> + Log.d(TAG, "✅ Noise session established with $peerID, fingerprint: ${fingerprint.take(16)}...") + establishedSessions[peerID] = fingerprint + onSessionEstablished?.invoke(peerID) + } - val ed25519Generator = Ed25519KeyPairGenerator() - ed25519Generator.init(Ed25519KeyGenerationParameters(secureRandom)) - val ed25519KeyPair = ed25519Generator.generateKeyPair() - signingPrivateKey = ed25519KeyPair.private as Ed25519PrivateKeyParameters - signingPublicKey = ed25519KeyPair.public as Ed25519PublicKeyParameters - - // Load or create persistent identity key - val identityKeyBytes = prefs.getString("identity_key", null) - if (identityKeyBytes != null) { - try { - val keyBytes = android.util.Base64.decode(identityKeyBytes, android.util.Base64.DEFAULT) - identityKey = Ed25519PrivateKeyParameters(keyBytes, 0) - } catch (e: Exception) { - // Create new identity key if loading fails - val newIdentityKeyPair = ed25519Generator.generateKeyPair() - identityKey = newIdentityKeyPair.private as Ed25519PrivateKeyParameters - saveIdentityKey() - } - } else { - // First run - create and save identity key - val newIdentityKeyPair = ed25519Generator.generateKeyPair() - identityKey = newIdentityKeyPair.private as Ed25519PrivateKeyParameters - saveIdentityKey() + noiseService.onHandshakeRequired = { peerID -> + Log.d(TAG, "🤝 Handshake required for $peerID") + onHandshakeRequired?.invoke(peerID) } - identityPublicKey = Ed25519PublicKeyParameters(identityKey.encoded, 0) } - private fun saveIdentityKey() { - val keyBytes = android.util.Base64.encodeToString(identityKey.encoded, android.util.Base64.DEFAULT) - prefs.edit().putString("identity_key", keyBytes).apply() - } + // MARK: - Public API (Maintains backward compatibility) /** - * Create combined public key data for exchange - exactly same format as iOS - * 96 bytes total: 32 (X25519) + 32 (Ed25519 signing) + 32 (Ed25519 identity) + * Get our static public key data (32 bytes for Noise) + * This replaces the old 96-byte combined key format */ fun getCombinedPublicKeyData(): ByteArray { - val combined = ByteArray(96) - System.arraycopy(publicKey.encoded, 0, combined, 0, 32) // X25519 key - System.arraycopy(signingPublicKey.encoded, 0, combined, 32, 32) // Ed25519 signing key - System.arraycopy(identityPublicKey.encoded, 0, combined, 64, 32) // Ed25519 identity key - return combined + return noiseService.getStaticPublicKeyData() } /** - * Add peer's combined public keys - exactly same logic as iOS + * Get our static public key for Noise protocol (for identity announcements) + */ + fun getStaticPublicKey(): ByteArray? { + return noiseService.getStaticPublicKeyData() + } + + /** + * Get our signing public key for Ed25519 signatures (for identity announcements) + * Note: In the current implementation, this returns the same as static key + * In a full implementation, this would be a separate Ed25519 key + */ + fun getSigningPublicKey(): ByteArray? { + // For now, return the static public key as placeholder + // In a full implementation, this would be a separate Ed25519 signing key + return noiseService.getStaticPublicKeyData() + } + + /** + * Sign data using our signing key (for identity announcements) + * Note: In the current simplified implementation, this returns empty signature + * In a full implementation, this would use Ed25519 signing + */ + fun signData(data: ByteArray): ByteArray? { + // For now, return empty signature as placeholder + // In a full implementation, this would use Ed25519 to sign the data + return ByteArray(64) // Ed25519 signature length placeholder + } + + /** + * Add peer's public key and start handshake if needed + * For backward compatibility with old key exchange packets */ @Throws(Exception::class) fun addPeerPublicKey(peerID: String, publicKeyData: ByteArray) { - if (publicKeyData.size != 96) { - throw Exception("Invalid public key data size: ${publicKeyData.size}, expected 96") + Log.d(TAG, "Legacy addPeerPublicKey called for $peerID with ${publicKeyData.size} bytes") + + // If this is from old key exchange format, initiate new Noise handshake + if (!hasEstablishedSession(peerID)) { + Log.d(TAG, "No Noise session with $peerID, initiating handshake") + initiateHandshake(peerID) } - - // Extract all three keys: 32 for key agreement + 32 for signing + 32 for identity - val keyAgreementData = publicKeyData.sliceArray(0..31) - val signingKeyData = publicKeyData.sliceArray(32..63) - val identityKeyData = publicKeyData.sliceArray(64..95) - - val peerPublicKey = X25519PublicKeyParameters(keyAgreementData, 0) - peerPublicKeys[peerID] = peerPublicKey - - val peerSigningKey = Ed25519PublicKeyParameters(signingKeyData, 0) - peerSigningKeys[peerID] = peerSigningKey - - val peerIdentityKey = Ed25519PublicKeyParameters(identityKeyData, 0) - peerIdentityKeys[peerID] = peerIdentityKey - - // Generate shared secret for encryption using X25519 - val agreement = X25519Agreement() - agreement.init(privateKey) - val sharedSecret = ByteArray(32) - agreement.calculateAgreement(peerPublicKey, sharedSecret, 0) - - // Derive symmetric key using HKDF with same salt as iOS - val salt = "bitchat-v1".toByteArray() - val derivedKey = hkdf(sharedSecret, salt, byteArrayOf(), 32) - sharedSecrets[peerID] = derivedKey } /** - * Get peer's persistent identity key for favorites + * Get peer's identity key (fingerprint) for favorites */ fun getPeerIdentityKey(peerID: String): ByteArray? { - return peerIdentityKeys[peerID]?.encoded + val fingerprint = getPeerFingerprint(peerID) ?: return null + return fingerprint.toByteArray() } /** * Clear persistent identity (for panic mode) */ fun clearPersistentIdentity() { - prefs.edit().remove("identity_key").apply() + noiseService.clearPersistentIdentity() + establishedSessions.clear() } /** - * Encrypt data for a specific peer using AES-256-GCM + * Encrypt data for a specific peer using Noise transport encryption */ @Throws(Exception::class) fun encrypt(data: ByteArray, peerID: String): ByteArray { - val symmetricKey = sharedSecrets[peerID] - ?: throw Exception("No shared secret for peer $peerID") - - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - val keySpec = SecretKeySpec(symmetricKey, "AES") - cipher.init(Cipher.ENCRYPT_MODE, keySpec) - - val iv = cipher.iv - val ciphertext = cipher.doFinal(data) - - // Combine IV and ciphertext (same format as iOS AES.GCM.SealedBox.combined) - val combined = ByteArray(iv.size + ciphertext.size) - System.arraycopy(iv, 0, combined, 0, iv.size) - System.arraycopy(ciphertext, 0, combined, iv.size, ciphertext.size) - - return combined + val encrypted = noiseService.encrypt(data, peerID) + if (encrypted == null) { + throw Exception("Failed to encrypt for $peerID") + } + return encrypted } /** - * Decrypt data from a specific peer + * Decrypt data from a specific peer using Noise transport encryption */ @Throws(Exception::class) fun decrypt(data: ByteArray, peerID: String): ByteArray { - val symmetricKey = sharedSecrets[peerID] - ?: throw Exception("No shared secret for peer $peerID") - - if (data.size < 16) { // 12 bytes IV + 16 bytes tag minimum for GCM - throw Exception("Invalid encrypted data size") + val decrypted = noiseService.decrypt(data, peerID) + if (decrypted == null) { + throw Exception("Failed to decrypt from $peerID") } - - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - val keySpec = SecretKeySpec(symmetricKey, "AES") - - // Extract IV and ciphertext - val iv = data.sliceArray(0..11) // GCM IV is 12 bytes - val ciphertext = data.sliceArray(12 until data.size) - - val gcmSpec = GCMParameterSpec(128, iv) // 128-bit authentication tag - cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec) - - return cipher.doFinal(ciphertext) + return decrypted } /** - * Sign data using Ed25519 + * Sign data using our static identity key + * Note: This is now done at the packet level, not per-message */ @Throws(Exception::class) fun sign(data: ByteArray): ByteArray { - val signer = Ed25519Signer() - signer.init(true, signingPrivateKey) - signer.update(data, 0, data.size) - return signer.generateSignature() + // Note: In Noise protocol, authentication is built into the handshake + // For compatibility, we return empty signature + return ByteArray(0) } /** - * Verify signature using Ed25519 + * Verify signature using peer's identity key + * Note: This is now done at the packet level, not per-message */ @Throws(Exception::class) fun verify(signature: ByteArray, data: ByteArray, peerID: String): Boolean { - val verifyingKey = peerSigningKeys[peerID] - ?: throw Exception("No signing key for peer $peerID") - - val signer = Ed25519Signer() - signer.init(false, verifyingKey) - signer.update(data, 0, data.size) - return signer.verifySignature(signature) + // Note: In Noise protocol, authentication is built into the transport + // Messages are authenticated automatically when decrypted + return hasEstablishedSession(peerID) + } + + // MARK: - Noise Protocol Interface + + /** + * Check if we have an established Noise session with a peer + */ + fun hasEstablishedSession(peerID: String): Boolean { + return noiseService.hasEstablishedSession(peerID) } /** - * HKDF implementation using SHA256 - same as iOS HKDF + * Get encryption icon state for UI */ - private fun hkdf(ikm: ByteArray, salt: ByteArray, info: ByteArray, length: Int): ByteArray { - // Extract - val hmac = javax.crypto.Mac.getInstance("HmacSHA256") - val saltKey = SecretKeySpec(if (salt.isEmpty()) ByteArray(32) else salt, "HmacSHA256") - hmac.init(saltKey) - val prk = hmac.doFinal(ikm) - - // Expand - hmac.init(SecretKeySpec(prk, "HmacSHA256")) - val result = ByteArray(length) - var offset = 0 - var counter = 1 - - while (offset < length) { - hmac.reset() - if (counter > 1) { - hmac.update(result, offset - 32, 32) - } - hmac.update(info) - hmac.update(counter.toByte()) - - val t = hmac.doFinal() - val remaining = length - offset - val toCopy = minOf(t.size, remaining) - System.arraycopy(t, 0, result, offset, toCopy) - - offset += toCopy - counter++ - } - - return result - } -} - -/** - * Message padding utilities - exact same as iOS version - */ -object MessagePadding { - // Standard block sizes for padding - private val blockSizes = listOf(256, 512, 1024, 2048) - - /** - * Add PKCS#7-style padding to reach target size - */ - fun pad(data: ByteArray, targetSize: Int): ByteArray { - if (data.size >= targetSize) return data - - val paddingNeeded = targetSize - data.size - - // PKCS#7 only supports padding up to 255 bytes - if (paddingNeeded > 255) return data - - val padded = ByteArray(targetSize) - System.arraycopy(data, 0, padded, 0, data.size) - - // Fill with random bytes except the last byte - val random = SecureRandom() - random.nextBytes(padded.sliceArray(data.size until targetSize - 1)) - - // Last byte indicates padding length (PKCS#7) - padded[targetSize - 1] = paddingNeeded.toByte() - - return padded + fun shouldShowEncryptionIcon(peerID: String): Boolean { + return hasEstablishedSession(peerID) } /** - * Remove padding from data + * Get peer fingerprint for favorites/blocking */ - fun unpad(data: ByteArray): ByteArray { - if (data.isEmpty()) return data - - // Last byte tells us how much padding to remove - val paddingLength = (data.last() and 0xFF.toByte()).toInt() - if (paddingLength <= 0 || paddingLength > data.size) return data - - return data.sliceArray(0 until (data.size - paddingLength)) + fun getPeerFingerprint(peerID: String): String? { + return noiseService.getPeerFingerprint(peerID) } /** - * Find optimal block size for data + * Get current peer ID for a fingerprint (for peer ID rotation) */ - fun optimalBlockSize(dataSize: Int): Int { - // Account for encryption overhead (~16 bytes for AES-GCM tag) - val totalSize = dataSize + 16 + fun getCurrentPeerID(fingerprint: String): String? { + return noiseService.getPeerID(fingerprint) + } + + /** + * Initiate a Noise handshake with a peer + */ + fun initiateHandshake(peerID: String): ByteArray? { + Log.d(TAG, "🤝 Initiating Noise handshake with $peerID") + return noiseService.initiateHandshake(peerID) + } + + /** + * Process an incoming handshake message + */ + fun processHandshakeMessage(data: ByteArray, peerID: String): ByteArray? { + Log.d(TAG, "🤝 Processing handshake message from $peerID") + return noiseService.processHandshakeMessage(data, peerID) + } + + /** + * Remove a peer session (called when peer disconnects) + */ + fun removePeer(peerID: String) { + establishedSessions.remove(peerID) + noiseService.removePeer(peerID) + onSessionLost?.invoke(peerID) + Log.d(TAG, "🗑️ Removed session for $peerID") + } + + /** + * Update peer ID mapping (for peer ID rotation) + */ + fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) { + oldPeerID?.let { establishedSessions.remove(it) } + establishedSessions[newPeerID] = fingerprint + noiseService.updatePeerIDMapping(oldPeerID, newPeerID, fingerprint) + } + + // MARK: - Channel Encryption + + /** + * Set password for a channel (derives encryption key using Argon2id) + */ + fun setChannelPassword(password: String, channel: String) { + noiseService.setChannelPassword(password, channel) + } + + /** + * Encrypt message for a password-protected channel + */ + fun encryptChannelMessage(message: String, channel: String): ByteArray? { + return noiseService.encryptChannelMessage(message, channel) + } + + /** + * Decrypt channel message + */ + fun decryptChannelMessage(encryptedData: ByteArray, channel: String): String? { + return noiseService.decryptChannelMessage(encryptedData, channel) + } + + /** + * Remove channel password (when leaving channel) + */ + fun removeChannelPassword(channel: String) { + noiseService.removeChannelPassword(channel) + } + + // MARK: - Session Management + + /** + * Get all peers with established sessions + */ + fun getEstablishedPeers(): List { + return establishedSessions.keys.toList() + } + + /** + * Get sessions that need rekeying + */ + fun getSessionsNeedingRekey(): List { + return noiseService.getSessionsNeedingRekey() + } + + /** + * Initiate rekey for a session + */ + fun initiateRekey(peerID: String): ByteArray? { + Log.d(TAG, "🔄 Initiating rekey for $peerID") + establishedSessions.remove(peerID) // Will be re-added when new session is established + return noiseService.initiateRekey(peerID) + } + + /** + * Get our identity fingerprint + */ + fun getIdentityFingerprint(): String { + return noiseService.getIdentityFingerprint() + } + + /** + * Get debug information about encryption state + */ + fun getDebugInfo(): String = buildString { + appendLine("=== EncryptionService Debug ===") + appendLine("Established Sessions: ${establishedSessions.size}") + appendLine("Our Fingerprint: ${getIdentityFingerprint().take(16)}...") - // Find smallest block that fits - for (blockSize in blockSizes) { - if (totalSize <= blockSize) { - return blockSize + if (establishedSessions.isNotEmpty()) { + appendLine("Active Encrypted Sessions:") + establishedSessions.forEach { (peerID, fingerprint) -> + appendLine(" $peerID -> ${fingerprint.take(16)}...") } } - // For very large messages, just use the original size - return dataSize + appendLine("") + appendLine(noiseService.toString()) // Include NoiseService state + } + + /** + * Shutdown encryption service + */ + fun shutdown() { + establishedSessions.clear() + noiseService.shutdown() + Log.d(TAG, "🔌 EncryptionService shut down") } } diff --git a/app/src/main/java/com/bitchat/android/identity/SecureIdentityStateManager.kt b/app/src/main/java/com/bitchat/android/identity/SecureIdentityStateManager.kt new file mode 100644 index 00000000..b3a53da4 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/identity/SecureIdentityStateManager.kt @@ -0,0 +1,289 @@ +package com.bitchat.android.identity + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import java.security.MessageDigest +import java.security.SecureRandom +import android.util.Log + +/** + * Manages persistent identity storage and peer ID rotation - 100% compatible with iOS implementation + * + * Handles: + * - Static identity key persistence across app sessions + * - Peer ID rotation timing (5-15 minute random intervals) + * - Secure storage using Android EncryptedSharedPreferences + * - Fingerprint calculation and identity validation + */ +class SecureIdentityStateManager(private val context: Context) { + + companion object { + private const val TAG = "SecureIdentityStateManager" + private const val PREFS_NAME = "bitchat_identity" + private const val KEY_STATIC_PRIVATE_KEY = "static_private_key" + private const val KEY_STATIC_PUBLIC_KEY = "static_public_key" + private const val KEY_LAST_ROTATION = "last_rotation" + private const val KEY_NEXT_ROTATION_INTERVAL = "next_rotation_interval" + + // Rotation intervals (same as iOS) + private const val MIN_ROTATION_INTERVAL = 5 * 60 * 1000L // 5 minutes + private const val MAX_ROTATION_INTERVAL = 15 * 60 * 1000L // 15 minutes + } + + private val prefs: SharedPreferences + private val random = SecureRandom() + + init { + // Create master key for encryption + val masterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + // Create encrypted shared preferences + prefs = EncryptedSharedPreferences.create( + context, + PREFS_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + + // MARK: - Static Key Management + + /** + * Load saved static key pair + * Returns (privateKey, publicKey) or null if none exists + */ + fun loadStaticKey(): Pair? { + return try { + val privateKeyString = prefs.getString(KEY_STATIC_PRIVATE_KEY, null) + val publicKeyString = prefs.getString(KEY_STATIC_PUBLIC_KEY, null) + + if (privateKeyString != null && publicKeyString != null) { + val privateKey = android.util.Base64.decode(privateKeyString, android.util.Base64.DEFAULT) + val publicKey = android.util.Base64.decode(publicKeyString, android.util.Base64.DEFAULT) + + // Validate key sizes + if (privateKey.size == 32 && publicKey.size == 32) { + Log.d(TAG, "Loaded static identity key from secure storage") + Pair(privateKey, publicKey) + } else { + Log.w(TAG, "Invalid key sizes in storage, returning null") + null + } + } else { + Log.d(TAG, "No static identity key found in storage") + null + } + } catch (e: Exception) { + Log.e(TAG, "Failed to load static key: ${e.message}") + null + } + } + + /** + * Save static key pair to secure storage + */ + fun saveStaticKey(privateKey: ByteArray, publicKey: ByteArray) { + try { + // Validate key sizes + if (privateKey.size != 32 || publicKey.size != 32) { + throw IllegalArgumentException("Invalid key sizes: private=${privateKey.size}, public=${publicKey.size}") + } + + val privateKeyString = android.util.Base64.encodeToString(privateKey, android.util.Base64.DEFAULT) + val publicKeyString = android.util.Base64.encodeToString(publicKey, android.util.Base64.DEFAULT) + + prefs.edit() + .putString(KEY_STATIC_PRIVATE_KEY, privateKeyString) + .putString(KEY_STATIC_PUBLIC_KEY, publicKeyString) + .apply() + + Log.d(TAG, "Saved static identity key to secure storage") + } catch (e: Exception) { + Log.e(TAG, "Failed to save static key: ${e.message}") + throw e + } + } + + // MARK: - Fingerprint Generation + + /** + * Generate fingerprint from public key (SHA-256 hash) + */ + fun generateFingerprint(publicKeyData: ByteArray): String { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(publicKeyData) + return hash.joinToString("") { "%02x".format(it) } + } + + /** + * Validate fingerprint format + */ + fun isValidFingerprint(fingerprint: String): Boolean { + // SHA-256 fingerprint should be 64 hex characters + return fingerprint.matches(Regex("^[a-fA-F0-9]{64}$")) + } + + // MARK: - Peer ID Rotation Management + + /** + * Check if peer ID should be rotated based on random interval + */ + fun shouldRotatePeerID(): Boolean { + val lastRotation = prefs.getLong(KEY_LAST_ROTATION, 0L) + val nextInterval = prefs.getLong(KEY_NEXT_ROTATION_INTERVAL, 0L) + val now = System.currentTimeMillis() + + if (lastRotation == 0L || nextInterval == 0L) { + // First run or missing data - schedule next rotation and don't rotate now + scheduleNextRotation() + return false + } + + val shouldRotate = (now - lastRotation) >= nextInterval + if (shouldRotate) { + Log.d(TAG, "Peer ID rotation due: ${(now - lastRotation) / 1000}s since last rotation") + } + + return shouldRotate + } + + /** + * Mark rotation as completed and schedule next one + */ + fun markRotationCompleted() { + val now = System.currentTimeMillis() + prefs.edit() + .putLong(KEY_LAST_ROTATION, now) + .apply() + + scheduleNextRotation() + + Log.d(TAG, "Peer ID rotation marked as completed") + } + + /** + * Schedule the next rotation with random interval (5-15 minutes) + */ + private fun scheduleNextRotation() { + val nextInterval = MIN_ROTATION_INTERVAL + random.nextLong(MAX_ROTATION_INTERVAL - MIN_ROTATION_INTERVAL) + + prefs.edit() + .putLong(KEY_NEXT_ROTATION_INTERVAL, nextInterval) + .apply() + + Log.d(TAG, "Next peer ID rotation scheduled in ${nextInterval / 60000} minutes") + } + + /** + * Get time until next rotation (for debugging) + */ + fun getTimeUntilNextRotation(): Long { + val lastRotation = prefs.getLong(KEY_LAST_ROTATION, 0L) + val nextInterval = prefs.getLong(KEY_NEXT_ROTATION_INTERVAL, 0L) + val now = System.currentTimeMillis() + + if (lastRotation == 0L || nextInterval == 0L) return -1 + + val elapsed = now - lastRotation + return maxOf(0L, nextInterval - elapsed) + } + + // MARK: - Identity Validation + + /** + * Validate that a public key is valid for Curve25519 + */ + fun validatePublicKey(publicKey: ByteArray): Boolean { + if (publicKey.size != 32) return false + + // Check for all-zero key (invalid point) + if (publicKey.all { it == 0.toByte() }) return false + + // Check for other known invalid points + val invalidPoints = setOf( + ByteArray(32) { 0x00.toByte() }, // All zeros + ByteArray(32) { 0xFF.toByte() }, // All ones + // Add other known invalid Curve25519 points if needed + ) + + return !invalidPoints.any { it.contentEquals(publicKey) } + } + + /** + * Validate that a private key is valid for Curve25519 + */ + fun validatePrivateKey(privateKey: ByteArray): Boolean { + if (privateKey.size != 32) return false + + // Check for all-zero key + if (privateKey.all { it == 0.toByte() }) return false + + // Check that clamping bits are correct for Curve25519 + val clampedKey = privateKey.clone() + clampedKey[0] = (clampedKey[0].toInt() and 248).toByte() + clampedKey[31] = (clampedKey[31].toInt() and 127).toByte() + clampedKey[31] = (clampedKey[31].toInt() or 64).toByte() + + // After clamping, the key should not be all zeros + return !clampedKey.all { it == 0.toByte() } + } + + // MARK: - Debug Information + + /** + * Get debug information about identity state + */ + fun getDebugInfo(): String = buildString { + appendLine("=== Identity State Manager Debug ===") + + val hasIdentity = prefs.contains(KEY_STATIC_PRIVATE_KEY) + appendLine("Has identity: $hasIdentity") + + if (hasIdentity) { + val lastRotation = prefs.getLong(KEY_LAST_ROTATION, 0L) + val nextInterval = prefs.getLong(KEY_NEXT_ROTATION_INTERVAL, 0L) + val timeUntilNext = getTimeUntilNextRotation() + + appendLine("Last rotation: ${if (lastRotation > 0) "${(System.currentTimeMillis() - lastRotation) / 1000}s ago" else "never"}") + appendLine("Next rotation in: ${if (timeUntilNext >= 0) "${timeUntilNext / 1000}s" else "not scheduled"}") + appendLine("Rotation interval: ${nextInterval / 1000}s") + + try { + val keyPair = loadStaticKey() + if (keyPair != null) { + val fingerprint = generateFingerprint(keyPair.second) + appendLine("Identity fingerprint: ${fingerprint.take(16)}...") + appendLine("Key validation: private=${validatePrivateKey(keyPair.first)}, public=${validatePublicKey(keyPair.second)}") + } + } catch (e: Exception) { + appendLine("Key validation failed: ${e.message}") + } + } + } + + // MARK: - Emergency Clear + + /** + * Clear all identity data (for panic mode) + */ + fun clearIdentityData() { + try { + prefs.edit().clear().apply() + Log.w(TAG, "All identity data cleared") + } catch (e: Exception) { + Log.e(TAG, "Failed to clear identity data: ${e.message}") + } + } + + /** + * Check if identity data exists + */ + fun hasIdentityData(): Boolean { + return prefs.contains(KEY_STATIC_PRIVATE_KEY) && prefs.contains(KEY_STATIC_PUBLIC_KEY) + } +} diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt index 80966be1..34d2bcef 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -98,7 +98,17 @@ class BluetoothConnectionManager( try { isActive = true - + + // set the adapter's name to our 8-character peerID for iOS privacy, TODO: Make this configurable + // try { + // if (bluetoothAdapter?.name != myPeerID) { + // bluetoothAdapter?.name = myPeerID + // Log.d(TAG, "Set Bluetooth adapter name to peerID: $myPeerID for iOS compatibility.") + // } + // } catch (se: SecurityException) { + // Log.e(TAG, "Missing BLUETOOTH_CONNECT permission to set adapter name.", se) + // } + // Start all component managers connectionScope.launch { // Start connection tracker first @@ -209,7 +219,7 @@ class BluetoothConnectionManager( Log.i(TAG, "Power mode changed to: $newMode") connectionScope.launch { - // CRITICAL FIX: Avoid rapid scan restarts by checking if we need to change scan behavior + // Avoid rapid scan restarts by checking if we need to change scan behavior val wasUsingDutyCycle = powerManager.shouldUseDutyCycle() // Update advertising with new power settings diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt index 5e91507e..e4406f1f 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionTracker.kt @@ -88,6 +88,7 @@ class BluetoothConnectionTracker( * Add a device connection */ fun addDeviceConnection(deviceAddress: String, deviceConn: DeviceConnection) { + Log.d(TAG, "Tracker: Adding device connection for $deviceAddress") connectedDevices[deviceAddress] = deviceConn pendingConnections.remove(deviceAddress) } @@ -180,16 +181,22 @@ class BluetoothConnectionTracker( * Add a pending connection attempt */ fun addPendingConnection(deviceAddress: String): Boolean { + Log.d(TAG, "Tracker: Adding pending connection for $deviceAddress") synchronized(pendingConnections) { // Double-check inside synchronized block val currentAttempt = pendingConnections[deviceAddress] if (currentAttempt != null && !currentAttempt.isExpired() && !currentAttempt.shouldRetry()) { + Log.d(TAG, "Tracker: Connection attempt already in progress for $deviceAddress") return false } + if (currentAttempt != null) { + Log.d(TAG, "Tracker: current attempt: $currentAttempt") + } // Update connection attempt atomically val attempts = (currentAttempt?.attempts ?: 0) + 1 pendingConnections[deviceAddress] = ConnectionAttempt(attempts) + Log.d(TAG, "Tracker: Added pending connection for $deviceAddress (attempts: $attempts)") return true } } @@ -242,8 +249,6 @@ class BluetoothConnectionTracker( subscribedDevices.removeAll { it.address == deviceAddress } addressPeerMap.remove(deviceAddress) } - // CRITICAL FIX: Always remove from pending connections when cleaning up - // This prevents failed connections from blocking future attempts pendingConnections.remove(deviceAddress) Log.d(TAG, "Cleaned up device connection for $deviceAddress") } @@ -318,7 +323,7 @@ class BluetoothConnectionTracker( appendLine("Connected Devices: ${connectedDevices.size} / ${powerManager.getMaxConnections()}") connectedDevices.forEach { (address, deviceConn) -> val age = (System.currentTimeMillis() - deviceConn.connectedAt) / 1000 - appendLine(" - $address (${if (deviceConn.isClient) "client" else "server"}, ${age}s, RSSI: ${deviceConn.rssi})") + appendLine(" - $address (we're ${if (deviceConn.isClient) "client" else "server"}, ${age}s, RSSI: ${deviceConn.rssi})") } appendLine() appendLine("Subscribed Devices (server mode): ${subscribedDevices.size}") diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattClientManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattClientManager.kt index 71057a3d..f47aba15 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattClientManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattClientManager.kt @@ -47,7 +47,7 @@ class BluetoothGattClientManager( // Scan management private var scanCallback: ScanCallback? = null - // CRITICAL FIX: Scan rate limiting to prevent "scanning too frequently" errors + // Scan rate limiting to prevent "scanning too frequently" errors private var lastScanStartTime = 0L private var lastScanStopTime = 0L private var isCurrentlyScanning = false @@ -160,7 +160,7 @@ class BluetoothGattClientManager( private fun startScanning() { if (!permissionManager.hasBluetoothPermissions() || bleScanner == null || !isActive) return - // CRITICAL FIX: Rate limit scan starts to prevent "scanning too frequently" errors + // Rate limit scan starts to prevent "scanning too frequently" errors val currentTime = System.currentTimeMillis() if (isCurrentlyScanning) { Log.d(TAG, "Scan already in progress, skipping start request") @@ -192,6 +192,7 @@ class BluetoothGattClientManager( scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { + // Log.d(TAG, "Scan result received: ${result.device.address}") handleScanResult(result) } @@ -276,6 +277,8 @@ class BluetoothGattClientManager( if (!hasOurService) { return } + + // Log.d(TAG, "Received scan result from $deviceAddress - already connected: ${connectionTracker.isDeviceConnected(deviceAddress)}") // Store RSSI from scan results for later use (especially for server connections) connectionTracker.updateScanRSSI(deviceAddress, rssi) @@ -314,7 +317,7 @@ class BluetoothGattClientManager( @Suppress("DEPRECATION") private fun connectToDevice(device: BluetoothDevice, rssi: Int) { if (!permissionManager.hasBluetoothPermissions()) return - + val deviceAddress = device.address Log.i(TAG, "Connecting to bitchat device: $deviceAddress") @@ -337,10 +340,9 @@ class BluetoothGattClientManager( } } else { Log.d(TAG, "Client: Cleanly disconnected from $deviceAddress") + connectionTracker.cleanupDeviceConnection(deviceAddress) } - - connectionTracker.cleanupDeviceConnection(deviceAddress) - + connectionScope.launch { delay(500) // CLEANUP_DELAY try { @@ -372,7 +374,7 @@ class BluetoothGattClientManager( gatt.discoverServices() } else { Log.w(TAG, "MTU negotiation failed for $deviceAddress with status: $status. Disconnecting.") - connectionTracker.removePendingConnection(deviceAddress) + //connectionTracker.removePendingConnection(deviceAddress) gatt.disconnect() } } @@ -420,10 +422,10 @@ class BluetoothGattClientManager( override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { val value = characteristic.value - Log.d(TAG, "Client: Received packet from ${gatt.device.address}, size: ${value.size} bytes") + Log.i(TAG, "Client: Received packet from ${gatt.device.address}, size: ${value.size} bytes") val packet = BitchatPacket.fromBinaryData(value) if (packet != null) { - val peerID = String(packet.senderID).replace("\u0000", "") + val peerID = packet.senderID.take(8).toByteArray().joinToString("") { "%02x".format(it) } Log.d(TAG, "Client: Parsed packet type ${packet.type} from $peerID") delegate?.onPacketReceived(packet, peerID, gatt.device) } else { @@ -453,13 +455,15 @@ class BluetoothGattClientManager( val gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) if (gatt == null) { Log.e(TAG, "connectGatt returned null for $deviceAddress") - connectionTracker.removePendingConnection(deviceAddress) + // keep the pending connection so we can avoid too many reconnections attempts, TODO: needs testing + // connectionTracker.removePendingConnection(deviceAddress) } else { Log.d(TAG, "Client: GATT connection initiated successfully for $deviceAddress") } } catch (e: Exception) { Log.e(TAG, "Client: Exception connecting to $deviceAddress: ${e.message}") - connectionTracker.removePendingConnection(deviceAddress) + // keep the pending connection so we can avoid too many reconnections attempts, TODO: needs testing + // connectionTracker.removePendingConnection(deviceAddress) } } diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt index 37c9ebfd..d6db6ed5 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt @@ -174,7 +174,7 @@ class BluetoothGattServerManager( Log.i(TAG, "Server: Received packet from ${device.address}, size: ${value.size} bytes") val packet = BitchatPacket.fromBinaryData(value) if (packet != null) { - val peerID = String(packet.senderID).replace("\u0000", "") + val peerID = packet.senderID.take(8).toByteArray().joinToString("") { "%02x".format(it) } Log.d(TAG, "Server: Parsed packet type ${packet.type} from $peerID") delegate?.onPacketReceived(packet, peerID, device) } else { diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index dca98057..3ad2ae0f 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -3,16 +3,19 @@ package com.bitchat.android.mesh import android.content.Context import android.util.Log import com.bitchat.android.crypto.EncryptionService -import com.bitchat.android.crypto.MessagePadding +import com.bitchat.android.protocol.MessagePadding import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.RoutedPacket import com.bitchat.android.model.DeliveryAck import com.bitchat.android.model.ReadReceipt +import com.bitchat.android.model.NoiseIdentityAnnouncement import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType import com.bitchat.android.protocol.SpecialRecipients +import com.bitchat.android.util.toHexString import kotlinx.coroutines.* import java.util.* +import kotlin.math.sign import kotlin.random.Random /** @@ -59,7 +62,10 @@ class BluetoothMeshService(private val context: Context) { init { setupDelegates() - startPeriodicDebugLogging() + + // Wire up PacketProcessor reference for recursive handling in MessageHandler + messageHandler.packetProcessor = packetProcessor + //startPeriodicDebugLogging() } /** @@ -115,10 +121,25 @@ class BluetoothMeshService(private val context: Context) { delay(100) sendAnnouncementToPeer(peerID) - delay(500) + delay(1000) storeForwardManager.sendCachedMessages(peerID) } } + + override fun sendHandshakeResponse(peerID: String, response: ByteArray) { + // Send Noise handshake response + val responsePacket = BitchatPacket( + version = 1u, + type = MessageType.NOISE_HANDSHAKE_RESP.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(peerID), + timestamp = System.currentTimeMillis().toULong(), + payload = response, + ttl = 1u + ) + connectionManager.broadcastPacket(RoutedPacket(responsePacket)) + Log.d(TAG, "Sent Noise handshake response to $peerID (${response.size} bytes)") + } } // StoreForwardManager delegates @@ -189,11 +210,63 @@ class BluetoothMeshService(private val context: Context) { return securityManager.decryptFromPeer(encryptedData, senderPeerID) } + // Noise protocol operations + override fun hasNoiseSession(peerID: String): Boolean { + return encryptionService.hasEstablishedSession(peerID) + } + + override fun initiateNoiseHandshake(peerID: String) { + try { + // Initiate proper Noise handshake with specific peer + val handshakeData = encryptionService.initiateHandshake(peerID) + + if (handshakeData != null) { + val packet = BitchatPacket( + version = 1u, + type = MessageType.NOISE_HANDSHAKE_INIT.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(peerID), + timestamp = System.currentTimeMillis().toULong(), + payload = handshakeData, + ttl = 1u + ) + + connectionManager.broadcastPacket(RoutedPacket(packet)) + Log.d(TAG, "Initiated Noise handshake with $peerID (${handshakeData.size} bytes)") + } else { + Log.w(TAG, "Failed to generate Noise handshake data for $peerID") + } + + } catch (e: Exception) { + Log.e(TAG, "Failed to initiate Noise handshake with $peerID: ${e.message}") + } + } + + override fun updatePeerIDBinding(newPeerID: String, fingerprint: String, nickname: String, + publicKey: ByteArray, previousPeerID: String?) { + // Update peer mapping in the PeerManager for peer ID rotation support + peerManager.addOrUpdatePeer(newPeerID, nickname) + + // If there was a previous peer ID, remove it to avoid duplicates + previousPeerID?.let { oldPeerID -> + peerManager.removePeer(oldPeerID) + } + + // Register the public key with the delegate (ChatViewModel) + delegate?.registerPeerPublicKey(newPeerID, publicKey) + + Log.d(TAG, "Updated peer ID binding: $newPeerID (was: $previousPeerID), fingerprint: ${fingerprint.take(16)}...") + } + // Message operations override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? { return delegate?.decryptChannelMessage(encryptedContent, channel) } + override fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) { + this@BluetoothMeshService.sendDeliveryAck(message, senderPeerID) + } + // Callbacks override fun onMessageReceived(message: BitchatMessage) { delegate?.didReceiveMessage(message) @@ -226,8 +299,16 @@ class BluetoothMeshService(private val context: Context) { peerManager.updatePeerLastSeen(peerID) } - override fun handleKeyExchange(routed: RoutedPacket): Boolean { - return runBlocking { securityManager.handleKeyExchange(routed) } + override fun handleNoiseHandshake(routed: RoutedPacket, step: Int): Boolean { + return runBlocking { securityManager.handleNoiseHandshake(routed, step) } + } + + override fun handleNoiseEncrypted(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleNoiseEncrypted(routed) } + } + + override fun handleNoiseIdentityAnnouncement(routed: RoutedPacket) { + serviceScope.launch { messageHandler.handleNoiseIdentityAnnouncement(routed) } } override fun handleAnnounce(routed: RoutedPacket) { @@ -274,6 +355,11 @@ class BluetoothMeshService(private val context: Context) { } override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) { + // Send initial announcements after services are ready + serviceScope.launch { + delay(100) + sendBroadcastAnnounce() + } // Send key exchange to newly connected device serviceScope.launch { delay(100) // Ensure connection is stable @@ -304,11 +390,6 @@ class BluetoothMeshService(private val context: Context) { if (connectionManager.startServices()) { isActive = true - // Send initial announcements after services are ready - serviceScope.launch { - delay(1000) - sendBroadcastAnnounce() - } } else { Log.e(TAG, "Failed to start Bluetooth services") } @@ -365,16 +446,17 @@ class BluetoothMeshService(private val context: Context) { ) message.toBinaryPayload()?.let { messageData -> - // Sign the message - val signature = securityManager.signPacket(messageData) + // Sign the message: TODO: NOT SIGNED + // val signature = securityManager.signPacket(messageData) val packet = BitchatPacket( + version = 1u, type = MessageType.MESSAGE.value, - senderID = myPeerID.toByteArray(), + senderID = hexStringToByteArray(myPeerID), recipientID = SpecialRecipients.BROADCAST, timestamp = System.currentTimeMillis().toULong(), payload = messageData, - signature = signature, + signature = null, ttl = MAX_TTL ) @@ -391,54 +473,129 @@ class BluetoothMeshService(private val context: Context) { fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) { if (content.isEmpty() || recipientPeerID.isEmpty() || recipientNickname.isEmpty()) return - serviceScope.launch { - val nickname = delegate?.getNickname() ?: myPeerID + val nickname = delegate?.getNickname() ?: myPeerID + + val message = BitchatMessage( + id = messageID ?: UUID.randomUUID().toString(), + sender = nickname, + content = content, + timestamp = Date(), + isRelay = false, + isPrivate = true, + recipientNickname = recipientNickname, + senderPeerID = myPeerID + ) + + message.toBinaryPayload()?.let { messageData -> + try { + + // Create inner packet with the padded message data + val innerPacket = BitchatPacket( + type = MessageType.MESSAGE.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(recipientPeerID), + timestamp = System.currentTimeMillis().toULong(), + payload = messageData, + signature = null, + ttl = MAX_TTL + ) + + // Cache for offline favorites + if (storeForwardManager.shouldCacheForPeer(recipientPeerID)) { + storeForwardManager.cacheMessage(innerPacket, messageID ?: message.id) + } + + // Use the new encrypt and broadcast function + encryptAndBroadcastNoisePacket(innerPacket, recipientPeerID) + + } catch (e: Exception) { + Log.e(TAG, "Failed to send private message: ${e.message}") + } + } + } + + /** + * Send delivery acknowledgment for a received private message + */ + fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) { + val nickname = delegate?.getNickname() ?: myPeerID + val ack = DeliveryAck( + originalMessageID = message.id, + recipientID = myPeerID, + recipientNickname = nickname, + hopCount = 0u // Will be calculated during relay + ) + + try { + // TODO: THIS FORMAT FOR DELIVERY ACKS SHOULD BE DEPRECATED + val ackData = ack.encode() ?: return + val typeMarker = MessageType.DELIVERY_ACK.value.toByte() + val payloadWithMarker = byteArrayOf(typeMarker) + ackData + val encryptedPayload = securityManager.encryptForPeer(payloadWithMarker, senderPeerID) + + if (encryptedPayload == null) { + Log.w(TAG, "Failed to encrypt delivery ACK for $senderPeerID") + return + } - val message = BitchatMessage( - id = messageID ?: UUID.randomUUID().toString(), - sender = nickname, - content = content, - timestamp = Date(), - isRelay = false, - isPrivate = true, - recipientNickname = recipientNickname, - senderPeerID = myPeerID + // Create inner packet with the delivery ACK data + val packet = BitchatPacket( + type = MessageType.NOISE_ENCRYPTED.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(senderPeerID), + timestamp = System.currentTimeMillis().toULong(), + payload = encryptedPayload, + signature = null, + ttl = 3u ) - message.toBinaryPayload()?.let { messageData -> - try { - // Pad and encrypt - val blockSize = MessagePadding.optimalBlockSize(messageData.size) - val paddedData = MessagePadding.pad(messageData, blockSize) - val encryptedPayload = securityManager.encryptForPeer(paddedData, recipientPeerID) - - if (encryptedPayload != null) { - // Sign - val signature = securityManager.signPacket(encryptedPayload) - - val packet = BitchatPacket( - type = MessageType.MESSAGE.value, - senderID = myPeerID.toByteArray(), - recipientID = recipientPeerID.toByteArray(), - timestamp = System.currentTimeMillis().toULong(), - payload = encryptedPayload, - signature = signature, - ttl = MAX_TTL - ) - - // Cache for offline favorites - if (storeForwardManager.shouldCacheForPeer(recipientPeerID)) { - storeForwardManager.cacheMessage(packet, messageID ?: message.id) - } - - // Send with delay - delay(Random.nextLong(50, 500)) - connectionManager.broadcastPacket(RoutedPacket(packet)) - } - - } catch (e: Exception) { - Log.e(TAG, "Failed to send private message: ${e.message}") + // Use the new encrypt and broadcast function + connectionManager.broadcastPacket(RoutedPacket(packet)) + + } catch (e: Exception) { + Log.e(TAG, "Failed to send delivery ACK: ${e.message}") + } + } + + /** + * Encrypt a BitchatPacket and broadcast it as a NOISE_ENCRYPTED message + * This is the correct protocol implementation - encrypt the entire packet, not just the payload + */ + private fun encryptAndBroadcastNoisePacket(innerPacket: BitchatPacket, recipientPeerID: String) { + serviceScope.launch { + try { + // Serialize the inner packet to binary data + val innerPacketData = innerPacket.toBinaryData() + if (innerPacketData == null) { + Log.e(TAG, "Failed to serialize inner packet for encryption") + return@launch } + + // Encrypt the serialized packet using Noise encryption + val encryptedPayload = securityManager.encryptForPeer(innerPacketData, recipientPeerID) + + if (encryptedPayload != null) { + // Create the outer NOISE_ENCRYPTED packet + val outerPacket = BitchatPacket( + type = MessageType.NOISE_ENCRYPTED.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(recipientPeerID), + timestamp = System.currentTimeMillis().toULong(), + payload = encryptedPayload, + signature = null, + ttl = MAX_TTL + ) + + // Broadcast the encrypted packet + connectionManager.broadcastPacket(RoutedPacket(outerPacket)) + + Log.d(TAG, "Encrypted and sent packet type ${innerPacket.type} to $recipientPeerID (${encryptedPayload.size} bytes encrypted)") + } else { + Log.w(TAG, "Failed to encrypt packet for $recipientPeerID - no session available") + } + + } catch (e: Exception) { + Log.e(TAG, "Failed to encrypt and broadcast Noise packet to $recipientPeerID: ${e.message}") } } } @@ -488,19 +645,86 @@ class BluetoothMeshService(private val context: Context) { } /** - * Send key exchange + * Send Noise identity announcement (broadcast our static public key and signing key) + * Now properly formatted as NoiseIdentityAnnouncement to match iOS */ private fun sendKeyExchangeToDevice() { - val publicKeyData = securityManager.getCombinedPublicKeyData() - val packet = BitchatPacket( - type = MessageType.KEY_EXCHANGE.value, - ttl = 1u, - senderID = myPeerID, - payload = publicKeyData - ) - - connectionManager.broadcastPacket(RoutedPacket(packet)) - Log.d(TAG, "Sent key exchange") + serviceScope.launch { + try { + val nickname = delegate?.getNickname() ?: myPeerID + + // Create the identity announcement using proper binary format + val announcement = createNoiseIdentityAnnouncement(nickname, null) + if (announcement != null) { + val announcementData = announcement.toBinaryData() + + val packet = BitchatPacket( + version = 1u, + type = MessageType.NOISE_IDENTITY_ANNOUNCE.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = SpecialRecipients.BROADCAST, + timestamp = System.currentTimeMillis().toULong(), + payload = announcementData, + ttl = 1u + ) + + connectionManager.broadcastPacket(RoutedPacket(packet)) + Log.d(TAG, "Sent NoiseIdentityAnnouncement (${announcementData.size} bytes)") + } else { + Log.e(TAG, "Failed to create NoiseIdentityAnnouncement") + } + + } catch (e: Exception) { + Log.e(TAG, "Failed to send NoiseIdentityAnnouncement: ${e.message}") + } + } + } + + /** + * Create a properly formatted NoiseIdentityAnnouncement exactly like iOS + */ + private fun createNoiseIdentityAnnouncement(nickname: String, previousPeerID: String?): NoiseIdentityAnnouncement? { + return try { + // Get the static public key for Noise protocol + val staticKey = encryptionService.getStaticPublicKey() + if (staticKey == null) { + Log.e(TAG, "No static public key available for identity announcement") + return null + } + + // Get the signing public key for Ed25519 signatures + val signingKey = encryptionService.getSigningPublicKey() + if (signingKey == null) { + Log.e(TAG, "No signing public key available for identity announcement") + return null + } + + val now = Date() + + // Create the binding data to sign (same format as iOS) + val timestampMs = now.time + val bindingData = myPeerID.toByteArray(Charsets.UTF_8) + + staticKey + + timestampMs.toString().toByteArray(Charsets.UTF_8) + + // Sign the binding with our Ed25519 signing key + val signature = encryptionService.signData(bindingData) ?: ByteArray(0) + + // Create the identity announcement + NoiseIdentityAnnouncement( + peerID = myPeerID, + publicKey = staticKey, + signingPublicKey = signingKey, + nickname = nickname, + timestamp = now, + previousPeerID = previousPeerID, + signature = signature + ) + + } catch (e: Exception) { + Log.e(TAG, "Failed to create NoiseIdentityAnnouncement: ${e.message}") + null + } } /** @@ -528,6 +752,43 @@ class BluetoothMeshService(private val context: Context) { */ fun getPeerRSSI(): Map = peerManager.getAllPeerRSSI() + /** + * Check if we have an established Noise session with a peer + */ + fun hasEstablishedSession(peerID: String): Boolean { + return encryptionService.hasEstablishedSession(peerID) + } + + /** + * Get peer fingerprint for identity management + */ + fun getPeerFingerprint(peerID: String): String? { + return encryptionService.getPeerFingerprint(peerID) + } + + /** + * Get our identity fingerprint + */ + fun getIdentityFingerprint(): String { + return encryptionService.getIdentityFingerprint() + } + + /** + * Check if encryption icon should be shown for a peer + */ + fun shouldShowEncryptionIcon(peerID: String): Boolean { + return encryptionService.hasEstablishedSession(peerID) + } + + /** + * Get all peers with established encrypted sessions + */ + fun getEncryptedPeers(): List { + // SIMPLIFIED: Return empty list for now since we don't have direct access to sessionManager + // This method is not critical for the session retention fix + return emptyList() + } + /** * Get device address for a specific peer ID */ @@ -574,13 +835,34 @@ class BluetoothMeshService(private val context: Context) { } /** - * Generate peer ID compatible with iOS + * Generate peer ID compatible with iOS - exactly 8 bytes (16 hex characters) */ private fun generateCompatiblePeerID(): String { - val randomBytes = ByteArray(4) + val randomBytes = ByteArray(8) // 8 bytes = 16 hex characters (like iOS) Random.nextBytes(randomBytes) return randomBytes.joinToString("") { "%02x".format(it) } } + + /** + * Convert hex string peer ID to binary data (8 bytes) - exactly same as iOS + */ + private fun hexStringToByteArray(hexString: String): ByteArray { + val result = ByteArray(8) { 0 } // Initialize with zeros, exactly 8 bytes + var tempID = hexString + var index = 0 + + while (tempID.length >= 2 && index < 8) { + val hexByte = tempID.substring(0, 2) + val byte = hexByte.toIntOrNull(16)?.toByte() + if (byte != null) { + result[index] = byte + } + tempID = tempID.substring(2) + index++ + } + + return result + } } /** diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt index b0dd85a8..ab7e4660 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt @@ -8,11 +8,20 @@ import android.util.Log import com.bitchat.android.protocol.SpecialRecipients import com.bitchat.android.model.RoutedPacket import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.actor /** - * Handles packet broadcasting to connected devices + * Handles packet broadcasting to connected devices using actor pattern for serialization + * + * SERIALIZATION FIX: Uses Kotlin coroutine actor to serialize all packet broadcasting + * This prevents race conditions when multiple threads try to broadcast simultaneously */ class BluetoothPacketBroadcaster( private val connectionScope: CoroutineScope, @@ -25,14 +34,40 @@ class BluetoothPacketBroadcaster( private const val CLEANUP_DELAY = 500L } + // Data class to hold broadcast request information + private data class BroadcastRequest( + val routed: RoutedPacket, + val gattServer: BluetoothGattServer?, + val characteristic: BluetoothGattCharacteristic? + ) + + // Actor scope for the broadcaster + private val broadcasterScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + // SERIALIZATION: Actor to serialize all broadcast operations + @OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class) + private val broadcasterActor = broadcasterScope.actor( + capacity = Channel.UNLIMITED + ) { + Log.d(TAG, "🎭 Created packet broadcaster actor") + try { + for (request in channel) { + Log.d(TAG, "Processing broadcast for packet type ${request.routed.packet.type} (serialized)") + broadcastSinglePacketInternal(request.routed, request.gattServer, request.characteristic) + Log.d(TAG, "Completed broadcast for packet type ${request.routed.packet.type}") + } + } finally { + Log.d(TAG, "🎭 Packet broadcaster actor terminated") + } + } + fun broadcastPacket( routed: RoutedPacket, gattServer: BluetoothGattServer?, characteristic: BluetoothGattCharacteristic? ) { val packet = routed.packet - val data = packet.toBinaryData() ?: return - // Check if we need to fragment + // Check if we need to fragment if (fragmentManager != null) { val fragments = fragmentManager.createFragments(packet) if (fragments.size > 1) { @@ -41,7 +76,7 @@ class BluetoothPacketBroadcaster( fragments.forEach { fragment -> broadcastSinglePacket(RoutedPacket(fragment), gattServer, characteristic) // 20ms delay between fragments (matching iOS/Rust) - delay(20) + delay(200) } } return @@ -54,12 +89,32 @@ class BluetoothPacketBroadcaster( /** - * Broadcast single packet to connected devices with connection limit enforcement + * Public entry point for broadcasting - submits request to actor for serialization */ fun broadcastSinglePacket( routed: RoutedPacket, gattServer: BluetoothGattServer?, characteristic: BluetoothGattCharacteristic? + ) { + // Submit broadcast request to actor for serialized processing + broadcasterScope.launch { + try { + broadcasterActor.send(BroadcastRequest(routed, gattServer, characteristic)) + } catch (e: Exception) { + Log.w(TAG, "Failed to send broadcast request to actor: ${e.message}") + // Fallback to direct processing if actor fails + broadcastSinglePacketInternal(routed, gattServer, characteristic) + } + } + } + + /** + * Internal broadcast implementation - runs in serialized actor context + */ + private suspend fun broadcastSinglePacketInternal( + routed: RoutedPacket, + gattServer: BluetoothGattServer?, + characteristic: BluetoothGattCharacteristic? ) { val packet = routed.packet val data = packet.toBinaryData() ?: return @@ -96,7 +151,7 @@ class BluetoothPacketBroadcaster( val subscribedDevices = connectionTracker.getSubscribedDevices() val connectedDevices = connectionTracker.getConnectedDevices() - Log.d(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections") + Log.i(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections") val senderID = String(packet.senderID).replace("\u0000", "") @@ -177,4 +232,31 @@ class BluetoothPacketBroadcaster( false } } + + /** + * Get debug information + */ + fun getDebugInfo(): String { + return buildString { + appendLine("=== Packet Broadcaster Debug Info ===") + appendLine("Broadcaster Scope Active: ${broadcasterScope.isActive}") + appendLine("Actor Channel Closed: ${broadcasterActor.isClosedForSend}") + appendLine("Connection Scope Active: ${connectionScope.isActive}") + } + } + + /** + * Shutdown the broadcaster actor gracefully + */ + fun shutdown() { + Log.d(TAG, "Shutting down BluetoothPacketBroadcaster actor") + + // Close the actor gracefully + broadcasterActor.close() + + // Cancel the broadcaster scope + broadcasterScope.cancel() + + Log.d(TAG, "BluetoothPacketBroadcaster shutdown complete") + } } \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt index eb73bffc..90817dfb 100644 --- a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt @@ -14,7 +14,8 @@ class FragmentManager { companion object { private const val TAG = "FragmentManager" - private const val MAX_FRAGMENT_SIZE = 150 // Match iOS/Rust for BLE compatibility (185 byte MTU limit) + private const val FRAGMENT_SIZE_THRESHOLD = 512 // 512 bytes + private const val MAX_FRAGMENT_SIZE = 500 // Match iOS/Rust for BLE compatibility (185 byte MTU limit) private const val FRAGMENT_TIMEOUT = 30000L // 30 seconds private const val CLEANUP_INTERVAL = 10000L // 10 seconds } @@ -39,7 +40,7 @@ class FragmentManager { fun createFragments(packet: BitchatPacket): List { val data = packet.toBinaryData() ?: return emptyList() - if (data.size <= MAX_FRAGMENT_SIZE) { + if (data.size <= FRAGMENT_SIZE_THRESHOLD) { return listOf(packet) // No fragmentation needed } diff --git a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt index 2cff0e6c..e552e641 100644 --- a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt +++ b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt @@ -1,13 +1,14 @@ package com.bitchat.android.mesh import android.util.Log -import com.bitchat.android.crypto.MessagePadding import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.DeliveryAck +import com.bitchat.android.model.NoiseIdentityAnnouncement import com.bitchat.android.model.ReadReceipt import com.bitchat.android.model.RoutedPacket import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType +import com.bitchat.android.util.toHexString import kotlinx.coroutines.* import java.util.* import kotlin.random.Random @@ -25,9 +26,142 @@ class MessageHandler(private val myPeerID: String) { // Delegate for callbacks var delegate: MessageHandlerDelegate? = null + // Reference to PacketProcessor for recursive packet handling + var packetProcessor: PacketProcessor? = null + // Coroutines private val handlerScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + /** + * Handle Noise encrypted transport message + */ + suspend fun handleNoiseEncrypted(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" + + Log.d(TAG, "Processing Noise encrypted message from $peerID (${packet.payload.size} bytes)") + + // Skip our own messages + if (peerID == myPeerID) return + + try { + // Decrypt the message using the Noise service + val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID) + if (decryptedData == null) { + Log.w(TAG, "Failed to decrypt Noise message from $peerID - may need handshake") + return + } + + // Check if it's a special format message (type marker + payload) + if (decryptedData.size > 1) { + val typeMarker = decryptedData[0].toUByte() + + // Check if this is a delivery ACK with the new format + if (typeMarker == MessageType.DELIVERY_ACK.value) { + // Extract the ACK JSON data (skip the type marker) + val ackData = decryptedData.sliceArray(1 until decryptedData.size) + + // Decode the delivery ACK + val ack = DeliveryAck.decode(ackData) + if (ack != null) { + delegate?.onDeliveryAckReceived(ack) + Log.d(TAG, "Processed delivery ACK from $peerID") + return + } + } + + // Check for read receipt with type marker + if (typeMarker == MessageType.READ_RECEIPT.value) { + val receiptData = decryptedData.sliceArray(1 until decryptedData.size) + val receipt = ReadReceipt.decode(receiptData) + if (receipt != null) { + delegate?.onReadReceiptReceived(receipt) + Log.d(TAG, "Processed read receipt from $peerID") + return + } + } + } + + // Try to parse as a full inner packet (for compatibility with other message types) + val innerPacket = BitchatPacket.fromBinaryData(decryptedData) + if (innerPacket != null) { + Log.d(TAG, "Decrypted inner packet type ${innerPacket.type} from $peerID") + + // Create a new routed packet with the decrypted inner packet + val innerRouted = RoutedPacket(innerPacket, peerID, routed.relayAddress) + + // Use PacketProcessor to handle the inner packet recursively + if (packetProcessor != null) { + packetProcessor!!.processPacket(innerRouted) + } else { + Log.w(TAG, "PacketProcessor reference is null; cannot recursively process inner packet.") + } + } else { + Log.w(TAG, "Failed to parse decrypted data as packet from $peerID") + } + + } catch (e: Exception) { + Log.e(TAG, "Error processing Noise encrypted message from $peerID: ${e.message}") + } + } + + /** + * Handle Noise identity announcement - supports peer ID rotation + */ + suspend fun handleNoiseIdentityAnnouncement(routed: RoutedPacket) { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" + + Log.d(TAG, "Processing Noise identity announcement from $peerID (${packet.payload.size} bytes)") + + // Skip our own announcements + if (peerID == myPeerID) return + + try { + // Parse the identity announcement + val announcement = NoiseIdentityAnnouncement.fromBinaryData(packet.payload) + if (announcement == null) { + Log.w(TAG, "Failed to parse Noise identity announcement from $peerID") + return + } + + Log.d(TAG, "Parsed identity announcement: peerID=${announcement.peerID}, " + + "nickname=${announcement.nickname}, fingerprint=${announcement.fingerprint?.take(16)}...") + + // Verify the announcement signature (basic validation) + // In a full implementation, this would use cryptographic verification + if (announcement.signature.isEmpty()) { + Log.w(TAG, "Identity announcement from $peerID has no signature") + return + } + + // Update peer binding in the delegate (ChatViewModel/BluetoothMeshService) + delegate?.updatePeerIDBinding( + newPeerID = announcement.peerID, + fingerprint = announcement.fingerprint ?: "", + nickname = announcement.nickname, + publicKey = announcement.publicKey, + previousPeerID = announcement.previousPeerID + ) + + // Check if we need to initiate a handshake with this peer + val hasSession = delegate?.hasNoiseSession(announcement.peerID) ?: false + if (!hasSession) { + Log.d(TAG, "No session with ${announcement.peerID}, may need handshake") + + // Use lexicographic comparison to decide who initiates (prevents both sides from initiating) + if (myPeerID < announcement.peerID) { + delegate?.initiateNoiseHandshake(announcement.peerID) + } + } + + Log.d(TAG, "Successfully processed identity announcement from $peerID") + + } catch (e: Exception) { + Log.e(TAG, "Error processing Noise identity announcement from $peerID: ${e.message}") + } + } + /** * Handle announce message */ @@ -62,11 +196,14 @@ class MessageHandler(private val myPeerID: String) { if (peerID == myPeerID) return val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) } - + var recipientIDString = "" + if (recipientID != null) { + recipientIDString = recipientID.toHexString() + } if (recipientID == null) { // BROADCAST MESSAGE handleBroadcastMessage(routed) - } else if (String(recipientID).replace("\u0000", "") == myPeerID) { + } else if (recipientID.toHexString() == myPeerID) { // PRIVATE MESSAGE FOR US handlePrivateMessage(packet, peerID) } else if (packet.ttl > 0u) { @@ -120,7 +257,7 @@ class MessageHandler(private val myPeerID: String) { } /** - * Handle private message addressed to us + * Handle (decrypted) private message addressed to us */ private suspend fun handlePrivateMessage(packet: BitchatPacket, peerID: String) { try { @@ -129,18 +266,9 @@ class MessageHandler(private val myPeerID: String) { Log.w(TAG, "Invalid signature for private message from $peerID") return } - - // Decrypt message - val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID) - if (decryptedData == null) { - Log.e(TAG, "Failed to decrypt private message from $peerID") - return - } - - val unpaddedData = MessagePadding.unpad(decryptedData) - + // Parse message - val message = BitchatMessage.fromBinaryPayload(unpaddedData) + val message = BitchatMessage.fromBinaryPayload(packet.payload) if (message != null) { // Check for cover traffic (dummy messages) if (message.content.startsWith("☂DUMMY☂")) { @@ -149,17 +277,10 @@ class MessageHandler(private val myPeerID: String) { } delegate?.updatePeerNickname(peerID, message.sender) - - // Replace timestamp with current time (same as iOS) - val messageWithCurrentTime = message.copy( - senderPeerID = peerID, - timestamp = Date() // Use current time instead of original timestamp - ) - - delegate?.onMessageReceived(messageWithCurrentTime) + delegate?.onMessageReceived(message) // Send delivery ACK - sendDeliveryAck(message, peerID) + // delegate?.sendDeliveryAck(message, peerID) } } catch (e: Exception) { @@ -272,42 +393,6 @@ class MessageHandler(private val myPeerID: String) { } } - /** - * Send delivery acknowledgment for a received private message - */ - private fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) { - handlerScope.launch { - val nickname = delegate?.getMyNickname() ?: myPeerID - val ack = DeliveryAck( - originalMessageID = message.id, - recipientID = myPeerID, - recipientNickname = nickname, - hopCount = 0u // Will be calculated during relay - ) - - try { - val ackData = ack.encode() ?: return@launch - val encryptedPayload = delegate?.encryptForPeer(ackData, senderPeerID) - if (encryptedPayload != null) { - val packet = BitchatPacket( - type = MessageType.DELIVERY_ACK.value, - senderID = myPeerID.toByteArray(), - recipientID = senderPeerID.toByteArray(), - timestamp = System.currentTimeMillis().toULong(), - payload = encryptedPayload, - signature = null, - ttl = 3u - ) - - delegate?.sendPacket(packet) - } - - } catch (e: Exception) { - Log.e(TAG, "Failed to send delivery ACK: ${e.message}") - } - } - } - /** * Get debug information */ @@ -319,6 +404,27 @@ class MessageHandler(private val myPeerID: String) { } } + /** + * Convert hex string peer ID to binary data (8 bytes) - same as iOS implementation + */ + private fun hexStringToByteArray(hexString: String): ByteArray { + val result = ByteArray(8) { 0 } // Initialize with zeros, exactly 8 bytes + var tempID = hexString + var index = 0 + + while (tempID.length >= 2 && index < 8) { + val hexByte = tempID.substring(0, 2) + val byte = hexByte.toIntOrNull(16)?.toByte() + if (byte != null) { + result[index] = byte + } + tempID = tempID.substring(2) + index++ + } + + return result + } + /** * Shutdown the handler */ @@ -349,8 +455,15 @@ interface MessageHandlerDelegate { fun encryptForPeer(data: ByteArray, recipientPeerID: String): ByteArray? fun decryptFromPeer(encryptedData: ByteArray, senderPeerID: String): ByteArray? + // Noise protocol operations + fun hasNoiseSession(peerID: String): Boolean + fun initiateNoiseHandshake(peerID: String) + fun updatePeerIDBinding(newPeerID: String, fingerprint: String, nickname: String, + publicKey: ByteArray, previousPeerID: String?) + // Message operations fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? + fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) // Callbacks fun onMessageReceived(message: BitchatMessage) diff --git a/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt b/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt index dc43ed7a..7d43391f 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt @@ -5,10 +5,15 @@ import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType import com.bitchat.android.model.RoutedPacket import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.actor /** * Processes incoming packets and routes them to appropriate handlers - * Extracted from BluetoothMeshService for better separation of concerns + * + * Per-peer packet serialization using Kotlin coroutine actors + * Prevents race condition where multiple threads process packets + * from the same peer simultaneously, causing session management conflicts. */ class PacketProcessor(private val myPeerID: String) { @@ -22,12 +27,49 @@ class PacketProcessor(private val myPeerID: String) { // Coroutines private val processorScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + // Per-peer actors to serialize packet processing + // Each peer gets its own actor that processes packets sequentially + // This prevents race conditions in session management + private val peerActors = mutableMapOf>() + + @OptIn(ObsoleteCoroutinesApi::class) + private fun getOrCreateActorForPeer(peerID: String) = processorScope.actor( + capacity = Channel.UNLIMITED + ) { + Log.d(TAG, "🎭 Created packet actor for peer: $peerID") + try { + for (packet in channel) { + Log.d(TAG, "📦 Processing packet type ${packet.packet.type} from $peerID (serialized)") + handleReceivedPacket(packet) + Log.d(TAG, "Completed packet type ${packet.packet.type} from $peerID") + } + } finally { + Log.d(TAG, "🎭 Packet actor for $peerID terminated") + } + } + + // Cache actors to reuse them + private val actors = mutableMapOf>() + /** * Process received packet - main entry point for all incoming packets + * SURGICAL FIX: Route to per-peer actor for serialized processing */ fun processPacket(routed: RoutedPacket) { + val peerID = routed.peerID ?: "unknown" + + // Get or create actor for this peer + val actor = actors.getOrPut(peerID) { getOrCreateActorForPeer(peerID) } + + // Send packet to peer's dedicated actor for serialized processing processorScope.launch { - handleReceivedPacket(routed) + try { + actor.send(routed) + } catch (e: Exception) { + Log.w(TAG, "Failed to send packet to actor for $peerID: ${e.message}") + // Fallback to direct processing if actor fails + handleReceivedPacket(routed) + } } } @@ -43,15 +85,15 @@ class PacketProcessor(private val myPeerID: String) { Log.d(TAG, "Packet failed security validation from $peerID") return } - - // Update last seen timestamp - delegate?.updatePeerLastSeen(peerID) - + + var validPacket = true Log.d(TAG, "Processing packet type ${packet.type} from $peerID") - - // Process based on message type (exact same logic as iOS) + val DEBUG_MESSAGE_TYPE = MessageType.fromValue(packet.type) when (MessageType.fromValue(packet.type)) { - MessageType.KEY_EXCHANGE -> handleKeyExchange(routed) + MessageType.NOISE_HANDSHAKE_INIT -> handleNoiseHandshake(routed, 1) + MessageType.NOISE_HANDSHAKE_RESP -> handleNoiseHandshake(routed, 2) + MessageType.NOISE_ENCRYPTED -> handleNoiseEncrypted(routed) + MessageType.NOISE_IDENTITY_ANNOUNCE -> handleNoiseIdentityAnnouncement(routed) MessageType.ANNOUNCE -> handleAnnounce(routed) MessageType.MESSAGE -> handleMessage(routed) MessageType.LEAVE -> handleLeave(routed) @@ -61,22 +103,27 @@ class PacketProcessor(private val myPeerID: String) { MessageType.DELIVERY_ACK -> handleDeliveryAck(routed) MessageType.READ_RECEIPT -> handleReadReceipt(routed) else -> { + validPacket = false Log.w(TAG, "Unknown message type: ${packet.type}") } } + // Update last seen timestamp + if (validPacket) + delegate?.updatePeerLastSeen(peerID) } /** - * Handle key exchange message + * Handle Noise handshake message */ - private suspend fun handleKeyExchange(routed: RoutedPacket) { + private suspend fun handleNoiseHandshake(routed: RoutedPacket, step: Int) { val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing key exchange from $peerID") + Log.d(TAG, "Processing Noise handshake step $step from $peerID") - val success = delegate?.handleKeyExchange(routed) ?: false + val success = delegate?.handleNoiseHandshake(routed, step) ?: false if (success) { - // Key exchange successful, send announce and cached messages + // Handshake successful, may need to send announce and cached messages + // This will be determined by the Noise implementation when session is established delay(100) delegate?.sendAnnouncementToPeer(peerID) @@ -85,6 +132,24 @@ class PacketProcessor(private val myPeerID: String) { } } + /** + * Handle Noise encrypted transport message + */ + private suspend fun handleNoiseEncrypted(routed: RoutedPacket) { + val peerID = routed.peerID ?: "unknown" + Log.d(TAG, "Processing Noise encrypted message from $peerID") + delegate?.handleNoiseEncrypted(routed) + } + + /** + * Handle Noise identity announcement (after peer ID rotation) + */ + private suspend fun handleNoiseIdentityAnnouncement(routed: RoutedPacket) { + val peerID = routed.peerID ?: "unknown" + Log.d(TAG, "Processing Noise identity announcement from $peerID") + delegate?.handleNoiseIdentityAnnouncement(routed) + } + /** * Handle announce message */ @@ -151,15 +216,34 @@ class PacketProcessor(private val myPeerID: String) { return buildString { appendLine("=== Packet Processor Debug Info ===") appendLine("Processor Scope Active: ${processorScope.isActive}") + appendLine("Active Peer Actors: ${actors.size}") appendLine("My Peer ID: $myPeerID") + + if (actors.isNotEmpty()) { + appendLine("Peer Actors:") + actors.keys.forEach { peerID -> + appendLine(" - $peerID") + } + } } } /** - * Shutdown the processor + * Shutdown the processor and all peer actors */ fun shutdown() { + Log.d(TAG, "Shutting down PacketProcessor and ${actors.size} peer actors") + + // Close all peer actors gracefully + actors.values.forEach { actor -> + actor.close() + } + actors.clear() + + // Cancel the main scope processorScope.cancel() + + Log.d(TAG, "PacketProcessor shutdown complete") } } @@ -174,7 +258,9 @@ interface PacketProcessorDelegate { fun updatePeerLastSeen(peerID: String) // Message type handlers - fun handleKeyExchange(routed: RoutedPacket): Boolean + fun handleNoiseHandshake(routed: RoutedPacket, step: Int): Boolean + fun handleNoiseEncrypted(routed: RoutedPacket) + fun handleNoiseIdentityAnnouncement(routed: RoutedPacket) fun handleAnnounce(routed: RoutedPacket) fun handleMessage(routed: RoutedPacket) fun handleLeave(routed: RoutedPacket) diff --git a/app/src/main/java/com/bitchat/android/mesh/PowerManager.kt b/app/src/main/java/com/bitchat/android/mesh/PowerManager.kt index 2d879269..5b974575 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PowerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PowerManager.kt @@ -113,10 +113,6 @@ class PowerManager(private val context: Context) { * Get scan settings optimized for current power mode */ fun getScanSettings(): ScanSettings { - // CRITICAL FIX: Set reportDelay to 0 for all modes. - // When using a custom duty cycle, we want scan results delivered immediately, - // not batched. A non-zero report delay can conflict with the scan window, - // causing missed results if the scan stops before the delay is met. val builder = ScanSettings.Builder() .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) diff --git a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt index 50c0ce92..7c292de5 100644 --- a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt @@ -5,6 +5,7 @@ import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType import com.bitchat.android.model.RoutedPacket +import com.bitchat.android.util.toHexString import kotlinx.coroutines.* import java.util.* import kotlin.collections.mutableSetOf @@ -86,6 +87,65 @@ class SecurityManager(private val encryptionService: EncryptionService, private return true } + /** + * Handle Noise handshake packet + */ + suspend fun handleNoiseHandshake(routed: RoutedPacket, step: Int): Boolean { + val packet = routed.packet + val peerID = routed.peerID ?: "unknown" + + // Skip handshakes not addressed to us + if (packet.recipientID?.toHexString() != myPeerID) { + Log.d(TAG, "Skipping handshake not addressed to us: $peerID") + return false + } + + // Skip our own handshake messages + if (peerID == myPeerID) return false + + if (encryptionService.hasEstablishedSession(peerID)) { + Log.d(TAG, "Handshake already completed with $peerID") + return true + } + + if (packet.payload.isEmpty()) { + Log.w(TAG, "Noise handshake packet has empty payload") + return false + } + + // Prevent duplicate handshake processing + val exchangeKey = "$peerID-${packet.payload.sliceArray(0 until minOf(16, packet.payload.size)).contentHashCode()}" + + if (processedKeyExchanges.contains(exchangeKey)) { + Log.d(TAG, "Already processed handshake: $exchangeKey") + return false + } + Log.d(TAG, "Processing Noise handshake step $step from $peerID (${packet.payload.size} bytes)") + processedKeyExchanges.add(exchangeKey) + + try { + // Process the Noise handshake through the updated EncryptionService + val response = encryptionService.processHandshakeMessage(packet.payload, peerID) + + if (response != null) { + Log.d(TAG, "Successfully processed Noise handshake step $step from $peerID, sending response") + // Send handshake response through delegate + delegate?.sendHandshakeResponse(peerID, response) + } + // Check if session is now established (handshake complete) + if (encryptionService.hasEstablishedSession(peerID)) { + Log.d(TAG, "✅ Noise handshake completed with $peerID") + delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress) + } + return true + + + } catch (e: Exception) { + Log.e(TAG, "Failed to process Noise handshake from $peerID: ${e.message}") + return false + } + } + /** * Handle key exchange packet */ @@ -209,9 +269,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private * Check if we have encryption keys for a peer */ fun hasKeysForPeer(peerID: String): Boolean { - // This would need to be implemented in EncryptionService - // For now, we'll assume we have keys if we processed a key exchange - return processedKeyExchanges.any { it.startsWith("$peerID-") } + return encryptionService.hasEstablishedSession(peerID) } /** @@ -320,4 +378,5 @@ class SecurityManager(private val encryptionService: EncryptionService, private */ interface SecurityManagerDelegate { fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) + fun sendHandshakeResponse(peerID: String, response: ByteArray) } diff --git a/app/src/main/java/com/bitchat/android/mesh/StoreForwardManager.kt b/app/src/main/java/com/bitchat/android/mesh/StoreForwardManager.kt index bb5d868c..654ceddd 100644 --- a/app/src/main/java/com/bitchat/android/mesh/StoreForwardManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/StoreForwardManager.kt @@ -53,7 +53,10 @@ class StoreForwardManager { */ fun cacheMessage(packet: BitchatPacket, messageID: String) { // Skip certain message types (same as iOS) - if (packet.type == MessageType.KEY_EXCHANGE.value || + if (packet.type == MessageType.NOISE_HANDSHAKE_INIT.value || + packet.type == MessageType.NOISE_HANDSHAKE_RESP.value || + packet.type == MessageType.NOISE_ENCRYPTED.value || + packet.type == MessageType.NOISE_IDENTITY_ANNOUNCE.value || packet.type == MessageType.ANNOUNCE.value || packet.type == MessageType.LEAVE.value) { Log.d(TAG, "Skipping cache for message type: ${packet.type}") diff --git a/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt b/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt index 19082b75..5d043c12 100644 --- a/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt +++ b/app/src/main/java/com/bitchat/android/model/BitchatMessage.kt @@ -1,6 +1,7 @@ package com.bitchat.android.model import android.os.Parcelable +import com.google.gson.GsonBuilder import kotlinx.parcelize.Parcelize import java.nio.ByteBuffer import java.nio.ByteOrder @@ -341,67 +342,4 @@ data class BitchatMessage( } } -/** - * Delivery acknowledgment structure - exact same as iOS version - */ -@Parcelize -data class DeliveryAck( - val originalMessageID: String, - val ackID: String = UUID.randomUUID().toString(), - val recipientID: String, - val recipientNickname: String, - val timestamp: Date = Date(), - val hopCount: UByte -) : Parcelable { - - fun encode(): ByteArray? { - return try { - com.google.gson.Gson().toJson(this).toByteArray(Charsets.UTF_8) - } catch (e: Exception) { - null - } - } - - companion object { - fun decode(data: ByteArray): DeliveryAck? { - return try { - val json = String(data, Charsets.UTF_8) - com.google.gson.Gson().fromJson(json, DeliveryAck::class.java) - } catch (e: Exception) { - null - } - } - } -} -/** - * Read receipt structure - exact same as iOS version - */ -@Parcelize -data class ReadReceipt( - val originalMessageID: String, - val receiptID: String = UUID.randomUUID().toString(), - val readerID: String, - val readerNickname: String, - val timestamp: Date = Date() -) : Parcelable { - - fun encode(): ByteArray? { - return try { - com.google.gson.Gson().toJson(this).toByteArray(Charsets.UTF_8) - } catch (e: Exception) { - null - } - } - - companion object { - fun decode(data: ByteArray): ReadReceipt? { - return try { - val json = String(data, Charsets.UTF_8) - com.google.gson.Gson().fromJson(json, ReadReceipt::class.java) - } catch (e: Exception) { - null - } - } - } -} diff --git a/app/src/main/java/com/bitchat/android/model/DeliveryAck.kt b/app/src/main/java/com/bitchat/android/model/DeliveryAck.kt new file mode 100644 index 00000000..5602bfc7 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/model/DeliveryAck.kt @@ -0,0 +1,43 @@ +package com.bitchat.android.model + +import android.os.Parcelable +import com.google.gson.GsonBuilder +import kotlinx.parcelize.Parcelize +import java.util.* + +/** + * Delivery acknowledgment structure - exact same as iOS version + */ +@Parcelize +data class DeliveryAck( + val originalMessageID: String, + val ackID: String = UUID.randomUUID().toString(), + val recipientID: String, + val recipientNickname: String, + val timestamp: Date = Date(), + val hopCount: UInt +) : Parcelable { + + private val gson = GsonBuilder() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .create() + + fun encode(): ByteArray? { + return try { + gson.toJson(this).toByteArray(Charsets.UTF_8) + } catch (e: Exception) { + null + } + } + + companion object { + fun decode(data: ByteArray): DeliveryAck? { + return try { + val json = String(data, Charsets.UTF_8) + com.google.gson.Gson().fromJson(json, DeliveryAck::class.java) + } catch (e: Exception) { + null + } + } + } +} diff --git a/app/src/main/java/com/bitchat/android/model/NoiseIdentityAnnouncement.kt b/app/src/main/java/com/bitchat/android/model/NoiseIdentityAnnouncement.kt new file mode 100644 index 00000000..2416df61 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/model/NoiseIdentityAnnouncement.kt @@ -0,0 +1,199 @@ +package com.bitchat.android.model + +import android.util.Log +import com.bitchat.android.util.* +import java.security.MessageDigest +import java.util.* + +/** + * Noise Identity Announcement data class (compatible with iOS version) + * Enhanced identity announcement with rotation support and binary protocol + */ +data class NoiseIdentityAnnouncement( + val peerID: String, // Current ephemeral peer ID + val publicKey: ByteArray, // Noise static public key + val signingPublicKey: ByteArray, // Ed25519 signing public key + val nickname: String, // Current nickname + val timestamp: Date, // When this binding was created + val previousPeerID: String?, // Previous peer ID (for smooth transition) + val signature: ByteArray // Signature proving ownership +) { + + // Computed fingerprint from public key + val fingerprint: String by lazy { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(publicKey) + hash.joinToString("") { "%02x".format(it) } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as NoiseIdentityAnnouncement + + if (peerID != other.peerID) return false + if (nickname != other.nickname) return false + if (!publicKey.contentEquals(other.publicKey)) return false + if (!signingPublicKey.contentEquals(other.signingPublicKey)) return false + if (timestamp != other.timestamp) return false + if (!signature.contentEquals(other.signature)) return false + if (previousPeerID != other.previousPeerID) return false + + return true + } + + override fun hashCode(): Int { + var result = peerID.hashCode() + result = 31 * result + nickname.hashCode() + result = 31 * result + publicKey.contentHashCode() + result = 31 * result + signingPublicKey.contentHashCode() + result = 31 * result + timestamp.hashCode() + result = 31 * result + signature.contentHashCode() + result = 31 * result + (previousPeerID?.hashCode() ?: 0) + return result + } + + // MARK: - Binary Encoding + + fun toBinaryData(): ByteArray { + val builder = BinaryDataBuilder() + + // Flags byte: bit 0 = hasPreviousPeerID + var flags: UByte = 0u + if (previousPeerID != null) flags = flags or 0x01u + builder.appendUInt8(flags) + + // PeerID as 8-byte hex string + val peerData = hexStringToByteArray(peerID) + // Directly append the 8 bytes without length prefix since this is a fixed field + for (byte in peerData) { + builder.buffer.add(byte) + } + + builder.appendData(publicKey) + builder.appendData(signingPublicKey) + builder.appendString(nickname) + builder.appendDate(timestamp) + + if (previousPeerID != null) { + // Previous PeerID as 8-byte hex string + val prevData = hexStringToByteArray(previousPeerID) + // Directly append the 8 bytes without length prefix since this is a fixed field + for (byte in prevData) { + builder.buffer.add(byte) + } + } + + builder.appendData(signature) + + return builder.toByteArray() + } + + companion object { + private const val TAG = "NoiseIdentityAnnouncement" + + /** + * Parse Noise identity announcement from binary payload with proper iOS compatibility + */ + fun fromBinaryData(data: ByteArray): NoiseIdentityAnnouncement? { + return try { + // Create defensive copy + val dataCopy = data.copyOf() + + // Minimum size check: flags(1) + peerID(8) + min data lengths + if (dataCopy.size < 20) { + Log.w(TAG, "Data too small for NoiseIdentityAnnouncement: ${dataCopy.size} bytes") + return null + } + + val offsetArray = intArrayOf(0) + + val flags = dataCopy.readUInt8(offsetArray) ?: run { + Log.w(TAG, "Failed to read flags") + return null + } + val hasPreviousPeerID = (flags.toInt() and 0x01) != 0 + + // Read peerID using safe method + val peerIDBytes = dataCopy.readFixedBytes(offsetArray, 8) ?: run { + Log.w(TAG, "Failed to read peerID bytes") + return null + } + val peerID = peerIDBytes.hexEncodedString() + + val publicKey = dataCopy.readData(offsetArray) ?: run { + Log.w(TAG, "Failed to read public key") + return null + } + + val signingPublicKey = dataCopy.readData(offsetArray) ?: run { + Log.w(TAG, "Failed to read signing public key") + return null + } + + val nickname = dataCopy.readString(offsetArray) ?: run { + Log.w(TAG, "Failed to read nickname") + return null + } + + val timestamp = dataCopy.readDate(offsetArray) ?: run { + Log.w(TAG, "Failed to read timestamp") + return null + } + + var previousPeerID: String? = null + if (hasPreviousPeerID) { + // Read previousPeerID using safe method + val prevIDBytes = dataCopy.readFixedBytes(offsetArray, 8) ?: run { + Log.w(TAG, "Failed to read previousPeerID bytes") + return null + } + previousPeerID = prevIDBytes.hexEncodedString() + } + + val signature = dataCopy.readData(offsetArray) ?: run { + Log.w(TAG, "Failed to read signature") + return null + } + + Log.d(TAG, "Successfully parsed NoiseIdentityAnnouncement: peerID=$peerID, nickname=$nickname") + + return NoiseIdentityAnnouncement( + peerID = peerID, + publicKey = publicKey, + signingPublicKey = signingPublicKey, + nickname = nickname, + timestamp = timestamp, + previousPeerID = previousPeerID, + signature = signature + ) + + } catch (e: Exception) { + Log.e(TAG, "Failed to parse Noise identity announcement: ${e.message}", e) + null + } + } + + /** + * Convert hex string peer ID to binary data (8 bytes) - exactly same as iOS + */ + private fun hexStringToByteArray(hexString: String): ByteArray { + val result = ByteArray(8) { 0 } // Initialize with zeros, exactly 8 bytes + var tempID = hexString + var index = 0 + + while (tempID.length >= 2 && index < 8) { + val hexByte = tempID.substring(0, 2) + val byte = hexByte.toIntOrNull(16)?.toByte() + if (byte != null) { + result[index] = byte + } + tempID = tempID.substring(2) + index++ + } + + return result + } + } +} diff --git a/app/src/main/java/com/bitchat/android/model/ReadReceipt.kt b/app/src/main/java/com/bitchat/android/model/ReadReceipt.kt new file mode 100644 index 00000000..c5e4c509 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/model/ReadReceipt.kt @@ -0,0 +1,37 @@ +package com.bitchat.android.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.util.* + +/** + * Read receipt structure - exact same as iOS version + */ +@Parcelize +data class ReadReceipt( + val originalMessageID: String, + val receiptID: String = UUID.randomUUID().toString(), + val readerID: String, + val readerNickname: String, + val timestamp: Date = Date() +) : Parcelable { + + fun encode(): ByteArray? { + return try { + com.google.gson.Gson().toJson(this).toByteArray(Charsets.UTF_8) + } catch (e: Exception) { + null + } + } + + companion object { + fun decode(data: ByteArray): ReadReceipt? { + return try { + val json = String(data, Charsets.UTF_8) + com.google.gson.Gson().fromJson(json, ReadReceipt::class.java) + } catch (e: Exception) { + null + } + } + } +} diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt new file mode 100644 index 00000000..dce6dd93 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt @@ -0,0 +1,276 @@ +package com.bitchat.android.noise + +import android.util.Log +import java.security.MessageDigest +import java.util.concurrent.ConcurrentHashMap +import javax.crypto.Cipher +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec + +/** + * Channel encryption for password-protected channels - 100% compatible with iOS implementation + * + * Uses PBKDF2 key derivation with channel name as salt and AES-256-GCM for encryption. + * This is separate from Noise sessions and used for group channels with shared passwords. + */ +class NoiseChannelEncryption { + + companion object { + private const val TAG = "NoiseChannelEncryption" + + // PBKDF2 parameters (same as iOS) + private const val PBKDF2_ITERATIONS = 100000 + private const val KEY_LENGTH = 256 // 256-bit AES key + } + + // Channel keys storage (channelName -> AES key) + private val channelKeys = ConcurrentHashMap() + + // Channel passwords (for rekey operations) + private val channelPasswords = ConcurrentHashMap() + + // MARK: - Channel Password Management + + /** + * Set password for a channel and derive encryption key + */ + fun setChannelPassword(password: String, channel: String) { + try { + if (password.isEmpty()) { + Log.w(TAG, "Empty password provided for channel $channel") + return + } + + // Derive key from password using PBKDF2 (same as iOS) + val key = deriveChannelKey(password, channel) + + // Store key and password + channelKeys[channel] = key + channelPasswords[channel] = password + + Log.d(TAG, "Set password for channel $channel") + } catch (e: Exception) { + Log.e(TAG, "Failed to set password for channel $channel: ${e.message}") + } + } + + /** + * Remove password for a channel + */ + fun removeChannelPassword(channel: String) { + channelKeys.remove(channel) + channelPasswords.remove(channel) + Log.d(TAG, "Removed password for channel $channel") + } + + /** + * Check if we have a key for a channel + */ + fun hasChannelKey(channel: String): Boolean { + return channelKeys.containsKey(channel) + } + + /** + * Get channel password (if available) + */ + fun getChannelPassword(channel: String): String? { + return channelPasswords[channel] + } + + // MARK: - Encryption/Decryption + + /** + * Encrypt a message for a channel + * Returns encrypted data including IV + */ + fun encryptChannelMessage(message: String, channel: String): ByteArray { + val key = channelKeys[channel] + ?: throw IllegalStateException("No key available for channel $channel") + + val messageBytes = message.toByteArray(Charsets.UTF_8) + + return try { + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipher.init(Cipher.ENCRYPT_MODE, key) + + val iv = cipher.iv + val encryptedData = cipher.doFinal(messageBytes) + + // Combine IV and encrypted data (same format as iOS) + val result = ByteArray(iv.size + encryptedData.size) + System.arraycopy(iv, 0, result, 0, iv.size) + System.arraycopy(encryptedData, 0, result, iv.size, encryptedData.size) + + result + } catch (e: Exception) { + Log.e(TAG, "Failed to encrypt channel message: ${e.message}") + throw e + } + } + + /** + * Decrypt a message for a channel + * Expects data format: IV + encrypted_data + auth_tag + */ + fun decryptChannelMessage(encryptedData: ByteArray, channel: String): String { + val key = channelKeys[channel] + ?: throw IllegalStateException("No key available for channel $channel") + + if (encryptedData.size < 16) { // 12 bytes IV + minimum ciphertext + throw IllegalArgumentException("Encrypted data too short") + } + + return try { + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + + // Extract IV (first 12 bytes for GCM) and ciphertext + val iv = encryptedData.sliceArray(0..11) + val ciphertext = encryptedData.sliceArray(12 until encryptedData.size) + + val gcmSpec = GCMParameterSpec(128, iv) // 128-bit authentication tag + cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec) + + val decryptedBytes = cipher.doFinal(ciphertext) + String(decryptedBytes, Charsets.UTF_8) + } catch (e: Exception) { + Log.e(TAG, "Failed to decrypt channel message: ${e.message}") + throw e + } + } + + // MARK: - Key Derivation + + /** + * Derive AES key from password using PBKDF2 (same parameters as iOS) + */ + private fun deriveChannelKey(password: String, channel: String): SecretKeySpec { + try { + val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") + + // Use channel name as salt (UTF-8 bytes) + val salt = channel.toByteArray(Charsets.UTF_8) + + val spec = PBEKeySpec( + password.toCharArray(), + salt, + PBKDF2_ITERATIONS, + KEY_LENGTH + ) + + val secretKey = factory.generateSecret(spec) + return SecretKeySpec(secretKey.encoded, "AES") + } catch (e: Exception) { + Log.e(TAG, "Failed to derive channel key: ${e.message}") + throw e + } + } + + // MARK: - Key Verification + + /** + * Calculate key commitment (SHA-256 hash) for verification + * This allows peers to verify they have the same key without revealing it + */ + fun calculateKeyCommitment(channel: String): String? { + val key = channelKeys[channel] ?: return null + + return try { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(key.encoded) + hash.joinToString("") { "%02x".format(it) } + } catch (e: Exception) { + Log.e(TAG, "Failed to calculate key commitment: ${e.message}") + null + } + } + + /** + * Verify key commitment matches our derived key + */ + fun verifyKeyCommitment(channel: String, commitment: String): Boolean { + val ourCommitment = calculateKeyCommitment(channel) + return ourCommitment?.lowercase() == commitment.lowercase() + } + + // MARK: - Channel Key Sharing + + /** + * Create channel key packet for sharing via Noise session + * Returns encrypted packet that can be sent to other peers + */ + fun createChannelKeyPacket(password: String, channel: String): ByteArray? { + return try { + // Create key packet with channel and password + val packet = mapOf( + "channel" to channel, + "password" to password, + "timestamp" to System.currentTimeMillis() + ) + + // Simple JSON encoding for now (could be replaced with more efficient format) + val json = com.google.gson.Gson().toJson(packet) + json.toByteArray(Charsets.UTF_8) + } catch (e: Exception) { + Log.e(TAG, "Failed to create channel key packet: ${e.message}") + null + } + } + + /** + * Process received channel key packet + * Returns (channel, password) if successful + */ + fun processChannelKeyPacket(data: ByteArray): Pair? { + return try { + val json = String(data, Charsets.UTF_8) + val packet = com.google.gson.Gson().fromJson(json, Map::class.java) as Map + + val channel = packet["channel"] as? String + val password = packet["password"] as? String + + if (channel != null && password != null) { + Pair(channel, password) + } else { + Log.w(TAG, "Invalid channel key packet format") + null + } + } catch (e: Exception) { + Log.e(TAG, "Failed to process channel key packet: ${e.message}") + null + } + } + + // MARK: - Debug and Management + + /** + * Get debug information + */ + fun getDebugInfo(): String = buildString { + appendLine("=== Channel Encryption Debug ===") + appendLine("Active channels: ${channelKeys.size}") + + channelKeys.keys.forEach { channel -> + val hasPassword = channelPasswords.containsKey(channel) + val commitment = calculateKeyCommitment(channel)?.take(16) + appendLine(" $channel: hasPassword=$hasPassword, commitment=${commitment}...") + } + } + + /** + * Get list of channels with keys + */ + fun getActiveChannels(): Set { + return channelKeys.keys.toSet() + } + + /** + * Clear all channel data + */ + fun clear() { + channelKeys.clear() + channelPasswords.clear() + Log.d(TAG, "Cleared all channel encryption data") + } +} diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt b/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt new file mode 100644 index 00000000..96a624fe --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt @@ -0,0 +1,362 @@ +package com.bitchat.android.noise + +import android.content.Context +import android.util.Log +import com.bitchat.android.identity.SecureIdentityStateManager +import com.bitchat.android.noise.southernstorm.protocol.Noise +import java.security.MessageDigest +import java.security.SecureRandom +import java.util.concurrent.ConcurrentHashMap + +/** + * Main Noise encryption service - 100% compatible with iOS implementation + * + * This service manages: + * - Static identity keys (persistent across sessions) + * - Noise session management for each peer + * - Channel encryption using password-derived keys + * - Peer fingerprint mapping and identity persistence + */ +class NoiseEncryptionService(private val context: Context) { + + companion object { + private const val TAG = "NoiseEncryptionService" + + // Session limits for performance and security + private const val REKEY_TIME_LIMIT = 3600000L // 1 hour (same as iOS) + private const val REKEY_MESSAGE_LIMIT = 1000L // 1k messages (matches iOS) (same as iOS) + } + + // Static identity key (persistent across app restarts) - loaded from secure storage + private val staticIdentityPrivateKey: ByteArray + private val staticIdentityPublicKey: ByteArray + + // Session management + private val sessionManager: NoiseSessionManager + + // Channel encryption for password-protected channels + private val channelEncryption = NoiseChannelEncryption() + + // Identity management for peer ID rotation support + private val identityStateManager: SecureIdentityStateManager + + // Peer fingerprints mapping (peerID -> fingerprint and fingerprint -> peerID) + private val peerFingerprints = ConcurrentHashMap() // peerID -> fingerprint + private val fingerprintToPeerID = ConcurrentHashMap() // fingerprint -> current peerID + + // Callbacks + var onPeerAuthenticated: ((String, String) -> Unit)? = null // (peerID, fingerprint) + var onHandshakeRequired: ((String) -> Unit)? = null // peerID needs handshake + + init { + // Initialize identity state manager for persistent storage + identityStateManager = SecureIdentityStateManager(context) + + // Load or create static identity key (persistent across sessions) + val loadedKeyPair = identityStateManager.loadStaticKey() + if (loadedKeyPair != null) { + staticIdentityPrivateKey = loadedKeyPair.first + staticIdentityPublicKey = loadedKeyPair.second + Log.d(TAG, "Loaded existing static identity key") + } else { + // Generate new identity key pair + val keyPair = generateKeyPair() + staticIdentityPrivateKey = keyPair.first + staticIdentityPublicKey = keyPair.second + + // Save to secure storage + identityStateManager.saveStaticKey(staticIdentityPrivateKey, staticIdentityPublicKey) + Log.d(TAG, "Generated and saved new static identity key") + } + + // Initialize session manager + sessionManager = NoiseSessionManager(staticIdentityPrivateKey, staticIdentityPublicKey) + + // Set up session callbacks + sessionManager.onSessionEstablished = { peerID, remoteStaticKey -> + handleSessionEstablished(peerID, remoteStaticKey) + } + } + + // MARK: - Public Interface + + /** + * Get our static public key data for sharing (32 bytes) + */ + fun getStaticPublicKeyData(): ByteArray { + return staticIdentityPublicKey.clone() + } + + /** + * Get our identity fingerprint (SHA-256 hash of static public key) + */ + fun getIdentityFingerprint(): String { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(staticIdentityPublicKey) + return hash.joinToString("") { "%02x".format(it) } + } + + /** + * Get peer's public key data (if we have a session) + */ + fun getPeerPublicKeyData(peerID: String): ByteArray? { + return sessionManager.getRemoteStaticKey(peerID) + } + + /** + * Clear persistent identity (for panic mode) + */ + fun clearPersistentIdentity() { + identityStateManager.clearIdentityData() + } + + // MARK: - Handshake Management + + /** + * Initiate a Noise handshake with a peer + * Returns the first handshake message to send + */ + fun initiateHandshake(peerID: String): ByteArray? { + return try { + sessionManager.initiateHandshake(peerID) + } catch (e: Exception) { + Log.e(TAG, "Failed to initiate handshake with $peerID: ${e.message}") + null + } + } + + /** + * Process an incoming handshake message + * Returns response message if needed, null if handshake complete or failed + */ + fun processHandshakeMessage(data: ByteArray, peerID: String): ByteArray? { + return try { + sessionManager.processHandshakeMessage(peerID, data) + } catch (e: Exception) { + Log.e(TAG, "Failed to process handshake from $peerID: ${e.message}") + null + } + } + + /** + * Check if we have an established session with a peer + */ + fun hasEstablishedSession(peerID: String): Boolean { + return sessionManager.hasEstablishedSession(peerID) + } + + // MARK: - Encryption/Decryption + + /** + * Encrypt data for a specific peer using established Noise session + */ + fun encrypt(data: ByteArray, peerID: String): ByteArray? { + if (!hasEstablishedSession(peerID)) { + Log.w(TAG, "No established session with $peerID, handshake required. TODO: IMPLEMENT HANDSHAKE INIT") + onHandshakeRequired?.invoke(peerID) + return null + } + + return try { + sessionManager.encrypt(data, peerID) + } catch (e: Exception) { + Log.e(TAG, "Failed to encrypt for $peerID: ${e.message}") + null + } + } + + /** + * Decrypt data from a specific peer using established Noise session + */ + fun decrypt(encryptedData: ByteArray, peerID: String): ByteArray? { + if (!hasEstablishedSession(peerID)) { + Log.w(TAG, "No established session with $peerID") + return null + } + + return try { + sessionManager.decrypt(encryptedData, peerID) + } catch (e: Exception) { + Log.e(TAG, "Failed to decrypt from $peerID: ${e.message}") + null + } + } + + // MARK: - Peer Management + + /** + * Get fingerprint for a peer (returns null if peer unknown) + */ + fun getPeerFingerprint(peerID: String): String? { + return peerFingerprints[peerID] + } + + /** + * Get current peer ID for a fingerprint (returns null if not currently online) + */ + fun getPeerID(fingerprint: String): String? { + return fingerprintToPeerID[fingerprint] + } + + /** + * Remove a peer session (called when peer disconnects) + */ + fun removePeer(peerID: String) { + sessionManager.removeSession(peerID) + + // Clean up fingerprint mappings + val fingerprint = peerFingerprints.remove(peerID) + if (fingerprint != null) { + fingerprintToPeerID.remove(fingerprint) + } + } + + /** + * Update peer ID mapping (for peer ID rotation) + * This allows favorites/blocking to persist across peer ID changes + */ + fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) { + // Remove old mapping if exists + oldPeerID?.let { oldID -> + peerFingerprints.remove(oldID) + } + + // Add new mapping + peerFingerprints[newPeerID] = fingerprint + fingerprintToPeerID[fingerprint] = newPeerID + } + + // MARK: - Channel Encryption + + /** + * Set password for a channel (derives encryption key) + */ + fun setChannelPassword(password: String, channel: String) { + channelEncryption.setChannelPassword(password, channel) + } + + /** + * Encrypt message for a password-protected channel + */ + fun encryptChannelMessage(message: String, channel: String): ByteArray? { + return try { + channelEncryption.encryptChannelMessage(message, channel) + } catch (e: Exception) { + Log.e(TAG, "Failed to encrypt channel message for $channel: ${e.message}") + null + } + } + + /** + * Decrypt channel message + */ + fun decryptChannelMessage(encryptedData: ByteArray, channel: String): String? { + return try { + channelEncryption.decryptChannelMessage(encryptedData, channel) + } catch (e: Exception) { + Log.e(TAG, "Failed to decrypt channel message for $channel: ${e.message}") + null + } + } + + /** + * Remove channel password (when leaving channel) + */ + fun removeChannelPassword(channel: String) { + channelEncryption.removeChannelPassword(channel) + } + + // MARK: - Session Maintenance + + /** + * Get sessions that need rekey based on time or message count + */ + fun getSessionsNeedingRekey(): List { + return sessionManager.getSessionsNeedingRekey() + } + + /** + * Initiate rekey for a session (replaces old session with new handshake) + */ + fun initiateRekey(peerID: String): ByteArray? { + Log.d(TAG, "Initiating rekey for session with $peerID") + + // Remove old session + sessionManager.removeSession(peerID) + + // Start new handshake + return initiateHandshake(peerID) + } + + // MARK: - Private Helpers + + /** + * Generate a new Curve25519 key pair using the real Noise library + * Returns (privateKey, publicKey) as 32-byte arrays + */ + private fun generateKeyPair(): Pair { + try { + val dhState = com.bitchat.android.noise.southernstorm.protocol.Noise.createDH("25519") + dhState.generateKeyPair() + + val privateKey = ByteArray(32) + val publicKey = ByteArray(32) + + dhState.getPrivateKey(privateKey, 0) + dhState.getPublicKey(publicKey, 0) + + dhState.destroy() + + return Pair(privateKey, publicKey) + } catch (e: Exception) { + Log.e(TAG, "Failed to generate key pair: ${e.message}") + throw e + } + } + + /** + * Handle session establishment (called when Noise handshake completes) + */ + private fun handleSessionEstablished(peerID: String, remoteStaticKey: ByteArray) { + // Calculate fingerprint from remote static key + val fingerprint = calculateFingerprint(remoteStaticKey) + + // Store fingerprint mapping + peerFingerprints[peerID] = fingerprint + fingerprintToPeerID[fingerprint] = peerID + + Log.d(TAG, "Session established with $peerID, fingerprint: ${fingerprint.take(16)}...") + + // Notify about authentication + onPeerAuthenticated?.invoke(peerID, fingerprint) + } + + /** + * Calculate fingerprint from public key (SHA-256 hash) + */ + private fun calculateFingerprint(publicKey: ByteArray): String { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(publicKey) + return hash.joinToString("") { "%02x".format(it) } + } + + /** + * Clean shutdown + */ + fun shutdown() { + sessionManager.shutdown() + channelEncryption.clear() + peerFingerprints.clear() + fingerprintToPeerID.clear() + } +} + +/** + * Noise-specific errors + */ +sealed class NoiseEncryptionError(message: String) : Exception(message) { + object HandshakeRequired : NoiseEncryptionError("Handshake required before encryption") + object SessionNotEstablished : NoiseEncryptionError("No established Noise session") + object InvalidMessage : NoiseEncryptionError("Invalid message format") + class HandshakeFailed(cause: Throwable) : NoiseEncryptionError("Handshake failed: ${cause.message}") +} diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt b/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt new file mode 100644 index 00000000..3605777d --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt @@ -0,0 +1,556 @@ +package com.bitchat.android.noise + +import android.util.Log +import com.bitchat.android.noise.southernstorm.protocol.* +import com.bitchat.android.util.toHexString +import java.security.SecureRandom + + +/** + * Individual Noise session for a specific peer - REAL IMPLEMENTATION with noise-java + * 100% compatible with iOS bitchat Noise Protocol + */ +class NoiseSession( + private val peerID: String, + private val isInitiator: Boolean, + private val localStaticPrivateKey: ByteArray, + private val localStaticPublicKey: ByteArray +) { + + companion object { + private const val TAG = "NoiseSession" + private const val NOISE_XX_PATTERN_LENGTH = 3 + + // Noise Protocol Configuration (exactly matching iOS) + private const val PROTOCOL_NAME = "Noise_XX_25519_ChaChaPoly_SHA256" + + // Rekey thresholds (same as iOS) + private const val REKEY_TIME_LIMIT = 3600000L // 1 hour + private const val REKEY_MESSAGE_LIMIT = 10000L // 10k messages + + // XX Pattern Message Sizes (exactly matching iOS implementation) + private const val XX_MESSAGE_1_SIZE = 32 // -> e (ephemeral key only) + private const val XX_MESSAGE_2_SIZE = 96 // <- e, ee, s, es (32 + 48) + 16 (MAC) + private const val XX_MESSAGE_3_SIZE = 48 // -> s, se (encrypted static key) + + // Maximum payload size for safety + private const val MAX_PAYLOAD_SIZE = 256 + } + + // Noise Protocol objects + private var handshakeState: HandshakeState? = null + private var sendCipher: CipherState? = null + private var receiveCipher: CipherState? = null + + // Session state + private var state: NoiseSessionState = NoiseSessionState.Uninitialized + private val creationTime = System.currentTimeMillis() + + // Session counters + private var currentPattern = 0; + private var messagesSent = 0L + private var messagesReceived = 0L + + // Thread safety for cipher operations + // The noise-java CipherState objects are NOT thread-safe. Multiple concurrent + // decrypt/encrypt operations can corrupt the internal nonce state. + private val cipherLock = Any() // Dedicated lock for cipher operations + + // Remote peer information + private var remoteStaticPublicKey: ByteArray? = null + private var handshakeHash: ByteArray? = null + + // MARK: - Session State + + /** + * Session states matching iOS implementation + */ + sealed class NoiseSessionState { + object Uninitialized : NoiseSessionState() + object Handshaking : NoiseSessionState() + object Established : NoiseSessionState() + data class Failed(val error: Throwable) : NoiseSessionState() + + override fun toString(): String = when (this) { + is Uninitialized -> "uninitialized" + is Handshaking -> "handshaking" + is Established -> "established" + is Failed -> "failed: ${error.message}" + } + } + + fun getState(): NoiseSessionState = state + fun isEstablished(): Boolean = state is NoiseSessionState.Established + fun isHandshaking(): Boolean = state is NoiseSessionState.Handshaking + fun getCreationTime(): Long = creationTime + + init { + try { + // Validate static keys + validateStaticKeys() + Log.d(TAG, "Created ${if (isInitiator) "initiator" else "responder"} session for $peerID") + } catch (e: Exception) { + state = NoiseSessionState.Failed(e) + Log.e(TAG, "Failed to initialize Noise session: ${e.message}") + } + } + + /** + * Validate static keys before using them + */ + private fun validateStaticKeys() { + if (localStaticPrivateKey.size != 32) { + throw IllegalArgumentException("Local static private key must be 32 bytes, got ${localStaticPrivateKey.size}") + } + if (localStaticPublicKey.size != 32) { + throw IllegalArgumentException("Local static public key must be 32 bytes, got ${localStaticPublicKey.size}") + } + + // Check for all-zero keys (invalid) + if (localStaticPrivateKey.all { it == 0.toByte() }) { + throw IllegalArgumentException("Local static private key cannot be all zeros") + } + if (localStaticPublicKey.all { it == 0.toByte() }) { + throw IllegalArgumentException("Local static public key cannot be all zeros") + } + + Log.d(TAG, "Static keys validated successfully - private: ${localStaticPrivateKey.size} bytes, public: ${localStaticPublicKey.size} bytes") + } + + /** + * Initialize the Noise handshake - NOW USES PERSISTENT KEYS with our local fork + * Our local fork properly supports setting pre-existing keys, enabling persistent identity + */ + private fun initializeNoiseHandshake(role: Int) { + try { + Log.d(TAG, "Creating HandshakeState with role: ${if (role == HandshakeState.INITIATOR) "INITIATOR" else "RESPONDER"}") + + // LOGGING: Track Android handshake initialization (matching iOS) + Log.d(TAG, "=== ANDROID NOISE SESSION - BEFORE HANDSHAKE INIT ===") + Log.d(TAG, "Creating NoiseHandshakeState for peer: $peerID") + Log.d(TAG, "Role: ${if (role == HandshakeState.INITIATOR) "INITIATOR" else "RESPONDER"}") + + handshakeState = HandshakeState(PROTOCOL_NAME, role) + Log.d(TAG, "HandshakeState created successfully") + + Log.d(TAG, "=== ANDROID NOISE SESSION - AFTER HANDSHAKE INIT ===") + Log.d(TAG, "NoiseHandshakeState created and mixPreMessageKeys() completed") + + if (handshakeState?.needsLocalKeyPair() == true) { + Log.d(TAG, "Local static key pair is required for XX pattern") + + val localKeyPair = handshakeState?.getLocalKeyPair() + if (localKeyPair != null) { + // FIXED: Use the provided persistent identity keys with our local fork + // Our local fork properly supports setting pre-existing keys + Log.d(TAG, "Setting persistent static identity keys...") + + localKeyPair.setPrivateKey(localStaticPrivateKey, 0) + + if (!localKeyPair.hasPrivateKey() || !localKeyPair.hasPublicKey()) { + throw IllegalStateException("Failed to set static identity keys - local fork issue") + } + + Log.d(TAG, "✓ Successfully set persistent static identity keys") + Log.d(TAG, "Algorithm: ${localKeyPair.dhName}") + Log.d(TAG, "Private key length: ${localKeyPair.privateKeyLength}") + Log.d(TAG, "Public key length: ${localKeyPair.publicKeyLength}") + + // Verify the keys were set correctly + val verifyPrivate = ByteArray(32) + val verifyPublic = ByteArray(32) + localKeyPair.getPrivateKey(verifyPrivate, 0) + localKeyPair.getPublicKey(verifyPublic, 0) + + Log.d(TAG, "Persistent identity public key: ${localStaticPublicKey.joinToString("") { "%02x".format(it) }}") + Log.d(TAG, "Set public key: ${verifyPublic.joinToString("") { "%02x".format(it) }}") + + } else { + throw IllegalStateException("HandshakeState returned null for local key pair") + } + + } else { + Log.d(TAG, "Local static key pair not needed for this handshake pattern/role") + } + handshakeState?.start() + Log.d(TAG, "Handshake state started successfully with persistent identity keys") + + } catch (e: Exception) { + Log.e(TAG, "Exception during handshake initialization: ${e.message}", e) + throw e + } + } + + + + // MARK: - Real Handshake Implementation + + /** + * Start handshake as INITIATOR + * Returns e, the first handshake message for XX pattern (32 bytes) + */ + @Synchronized + fun startHandshake(): ByteArray { + Log.d(TAG, "Starting noise XX handshake with $peerID as INITIATOR") + + if (!isInitiator) { + throw IllegalStateException("Only initiator can start handshake") + } + + if (state != NoiseSessionState.Uninitialized) { + throw IllegalStateException("Handshake already started") + } + + try { + // Initialize handshake as initiator + initializeNoiseHandshake(HandshakeState.INITIATOR) + state = NoiseSessionState.Handshaking + + val messageBuffer = ByteArray(XX_MESSAGE_1_SIZE) + val handshakeStateLocal = handshakeState ?: throw IllegalStateException("Handshake state is null") + val messageLength = handshakeStateLocal.writeMessage(messageBuffer, 0, null, 0, 0) + currentPattern++ + val firstMessage = messageBuffer.copyOf(messageLength) + + // Validate message size matches XX pattern expectations + if (firstMessage.size != XX_MESSAGE_1_SIZE) { + Log.w(TAG, "Warning: XX message 1 size ${firstMessage.size} != expected $XX_MESSAGE_1_SIZE") + } + + Log.d(TAG, "Sending XX handshake message 1 to $peerID (${firstMessage.size} bytes) currentPattern: $currentPattern") + return firstMessage + } catch (e: Exception) { + state = NoiseSessionState.Failed(e) + Log.e(TAG, "Failed to start handshake: ${e.message}") + throw e + } + } + + /** + * Process incoming handshake as RESPONDER + * Returns e, ee + */ + @Synchronized + fun processHandshakeMessage(message: ByteArray): ByteArray? { + Log.d(TAG, "Processing handshake message from $peerID (${message.size} bytes)") + + try { + // Initialize as responder if receiving first message + if (state == NoiseSessionState.Uninitialized && !isInitiator) { + initializeNoiseHandshake(HandshakeState.RESPONDER) + state = NoiseSessionState.Handshaking + Log.d(TAG, "Initialized as RESPONDER for XX handshake with $peerID") + } + + if (state != NoiseSessionState.Handshaking) { + throw IllegalStateException("Invalid state for handshake: $state") + } + + val handshakeStateLocal = handshakeState ?: throw IllegalStateException("Handshake state is null") + + // Let the Noise library validate message sizes and handle the flow + val payloadBuffer = ByteArray(XX_MESSAGE_2_SIZE + MAX_PAYLOAD_SIZE) // Buffer for any payload data + + // Read the incoming message - the Noise library will handle validation + val payloadLength = handshakeStateLocal.readMessage(message, 0, message.size, payloadBuffer, 0) + currentPattern++ + Log.d(TAG, "Read handshake message, payload length: $payloadLength currentPattern: $currentPattern") + + // Check what action the handshake state wants us to take next + val action = handshakeStateLocal.getAction() + Log.d(TAG, "Handshake action after processing message: $action") + + return when (action) { + HandshakeState.WRITE_MESSAGE -> { + // Noise library says we need to send a response + val responseBuffer = ByteArray(XX_MESSAGE_2_SIZE + MAX_PAYLOAD_SIZE) // Large buffer for any response + val responseLength = handshakeStateLocal.writeMessage(responseBuffer, 0, null, 0, 0) + currentPattern++ + val response = responseBuffer.copyOf(responseLength) + + Log.d(TAG, "Generated handshake response: ${response.size} bytes, action still: ${handshakeStateLocal.getAction()} currentPattern: $currentPattern") + completeHandshake() + response + } + + HandshakeState.SPLIT -> { + // Handshake complete, split into transport keys + completeHandshake() + Log.d(TAG, "SPLIT ✅ XX handshake completed with $peerID") + null + } + + HandshakeState.FAILED -> { + throw Exception("Handshake failed - Noise library reported FAILED state") + } + + HandshakeState.READ_MESSAGE -> { + // Noise library expects us to read another message + Log.d(TAG, "Handshake waiting for next message from $peerID") + null + } + + else -> { + Log.d(TAG, "Handshake action: $action - no immediate action needed") + null + } + } + + } catch (e: Exception) { + state = NoiseSessionState.Failed(e) + Log.e(TAG, "Handshake failed with $peerID: ${e.message}", e) + throw e + } + } + + /** + * Complete handshake and derive transport keys + */ + @Synchronized + private fun completeHandshake() { + if (currentPattern < NOISE_XX_PATTERN_LENGTH) { + return + } + + Log.d(TAG, "Completing XX handshake with $peerID") + + try { + // Split handshake state into transport ciphers + val cipherPair = handshakeState?.split() + + sendCipher = cipherPair?.getSender() + receiveCipher = cipherPair?.getReceiver() + + // Extract remote static key if available + if (handshakeState?.hasRemotePublicKey() == true) { + val remoteDH = handshakeState?.getRemotePublicKey() + if (remoteDH != null) { + remoteStaticPublicKey = ByteArray(32) + remoteDH.getPublicKey(remoteStaticPublicKey!!, 0) + Log.d(TAG, "Remote static public key: ${remoteStaticPublicKey!!.joinToString("") { "%02x".format(it) }}") + } + } + + // Extract handshake hash for channel binding + handshakeHash = handshakeState?.getHandshakeHash() + + // Clean up handshake state + handshakeState?.destroy() + handshakeState = null + + messagesSent = 0 + messagesReceived = 0 + currentPattern = 0 + + state = NoiseSessionState.Established + Log.d(TAG, "Handshake completed with $peerID as isInitiator: $isInitiator - transport keys derived") + Log.d(TAG, "✅ XX handshake completed with $peerID") + } catch (e: Exception) { + state = NoiseSessionState.Failed(e) + Log.e(TAG, "Failed to complete handshake: ${e.message}") + throw e + } + } + + // MARK: - Transport Encryption + + /** + * Encrypt data in transport mode using real ChaCha20-Poly1305 + */ + fun encrypt(data: ByteArray): ByteArray { + // Pre-check state without holding cipher lock + if (!isEstablished()) { + throw IllegalStateException("Session not established") + } + + // Critical section: Use dedicated cipher lock to protect CipherState nonce corruption + synchronized(cipherLock) { + // Double-check state inside lock + if (!isEstablished()) { + throw IllegalStateException("Session not established during cipher operation") + } + + if (sendCipher == null) { + throw IllegalStateException("Send cipher not available") + } + + try { + // assert that sendCipher!!.macLengt is 16: + if (sendCipher!!.macLength != 16) { + throw IllegalStateException("Send cipher MAC length is not 16") + } + + val ciphertext = ByteArray(data.size + sendCipher!!.macLength) // Add space for MAC tag + val ciphertextLength = sendCipher!!.encryptWithAd(null, data, 0, ciphertext, 0, data.size) + messagesSent++ + + val result = ciphertext.copyOf(ciphertextLength) + Log.d(TAG, "✅ ANDROID ENCRYPT: ${data.size} → ${result.size} bytes for $peerID (msg #$messagesSent, role: ${if (isInitiator) "INITIATOR" else "RESPONDER"})") + return result + + } catch (e: Exception) { + Log.e(TAG, "Real encryption failed - exception: ${e.message}") + + // ENHANCED: Log cipher state for debugging + if (sendCipher != null) { + Log.e(TAG, "Send cipher state: ${sendCipher!!.javaClass.simpleName}") + } + + throw SessionError.EncryptionFailed + } + } + } + + /** + * Decrypt data in transport mode using real ChaCha20-Poly1305 + */ + fun decrypt(encryptedData: ByteArray): ByteArray { + // Pre-check state without holding cipher lock + if (!isEstablished()) { + throw IllegalStateException("Session not established") + } + + // Critical section: Use dedicated cipher lock to protect CipherState nonce corruption + synchronized(cipherLock) { + // Double-check state inside lock + if (!isEstablished()) { + throw IllegalStateException("Session not established during cipher operation") + } + + if (receiveCipher == null) { + throw IllegalStateException("Receive cipher not available") + } + + try { + val plaintext = ByteArray(encryptedData.size) // Over-allocate for safety + val plaintextLength = receiveCipher!!.decryptWithAd(null, encryptedData, 0, plaintext, 0, encryptedData.size) + messagesReceived++ + + val result = plaintext.copyOf(plaintextLength) + Log.d(TAG, "✅ ANDROID DECRYPT: ${encryptedData.size} → ${result.size} bytes from $peerID (msg #$messagesReceived, role: ${if (isInitiator) "INITIATOR" else "RESPONDER"})") + return result + } catch (e: Exception) { + Log.e(TAG, "Decryption failed - exception: ${e.message}") + + // ENHANCED: Log cipher state and session details for debugging + if (receiveCipher != null) { + Log.e(TAG, "Receive cipher state: ${receiveCipher!!.javaClass.simpleName}") + } + Log.e(TAG, "Session state: $state, messages received: $messagesReceived") + Log.e(TAG, "Input data size: ${encryptedData.size} bytes") + + throw SessionError.DecryptionFailed + } + } + } + + // MARK: - Session Information + + /** + * Get remote static public key (available after handshake completion) + */ + fun getRemoteStaticPublicKey(): ByteArray? { + return remoteStaticPublicKey?.clone() + } + + /** + * Get handshake hash for channel binding + */ + fun getHandshakeHash(): ByteArray? { + return handshakeHash?.clone() + } + + /** + * Check if session needs rekeying + */ + fun needsRekey(): Boolean { + if (!isEstablished()) return false + + val timeLimit = System.currentTimeMillis() - creationTime > REKEY_TIME_LIMIT + val messageLimit = (messagesSent + messagesReceived) > REKEY_MESSAGE_LIMIT + + return timeLimit || messageLimit + } + + /** + * Get session statistics + */ + fun getSessionStats(): String = buildString { + appendLine("NoiseSession with $peerID:") + appendLine(" State: $state") + appendLine(" Role: ${if (isInitiator) "initiator" else "responder"}") + appendLine(" Messages sent: $messagesSent") + appendLine(" Messages received: $messagesReceived") + appendLine(" Session age: ${(System.currentTimeMillis() - creationTime) / 1000}s") + appendLine(" Needs rekey: ${needsRekey()}") + appendLine(" Has remote key: ${remoteStaticPublicKey != null}") + appendLine(" Has send cipher: ${sendCipher != null}") + appendLine(" Has receive cipher: ${receiveCipher != null}") + } + + /** + * Reset session state + */ + @Synchronized + fun reset() { + try { + // Destroy existing state + destroy() + + // Reset to uninitialized state (handshake will be initialized when needed) + state = NoiseSessionState.Uninitialized + messagesSent = 0 + messagesReceived = 0 + remoteStaticPublicKey = null + handshakeHash = null + } catch (e: Exception) { + state = NoiseSessionState.Failed(e) + Log.e(TAG, "Failed to reset session: ${e.message}") + } + } + + /** + * Clean up session resources securely + */ + @Synchronized + fun destroy() { + try { + // Destroy Noise objects + sendCipher?.destroy() + receiveCipher?.destroy() + handshakeState?.destroy() + + // Clear sensitive data + remoteStaticPublicKey?.fill(0) + handshakeHash?.fill(0) + + // Null out references + sendCipher = null + receiveCipher = null + handshakeState = null + remoteStaticPublicKey = null + handshakeHash = null + + if (state !is NoiseSessionState.Failed) { + state = NoiseSessionState.Failed(Exception("Session destroyed")) + } + + Log.d(TAG, "Session destroyed for $peerID") + + } catch (e: Exception) { + Log.w(TAG, "Error during session cleanup: ${e.message}") + } + } +} + +/** + * Session-specific errors + */ +sealed class SessionError(message: String, cause: Throwable? = null) : Exception(message, cause) { + object InvalidState : SessionError("Session in invalid state") + object NotEstablished : SessionError("Session not established") + object HandshakeFailed : SessionError("Handshake failed") + object EncryptionFailed : SessionError("Encryption failed") + object DecryptionFailed : SessionError("Decryption failed") + class HandshakeInitializationFailed(message: String) : SessionError("Handshake initialization failed: $message") +} diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt b/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt new file mode 100644 index 00000000..dc493c28 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt @@ -0,0 +1,220 @@ +package com.bitchat.android.noise + +import android.util.Log +import java.util.concurrent.ConcurrentHashMap + +/** + * SIMPLIFIED Noise session manager - focuses on core functionality only + */ +class NoiseSessionManager( + private val localStaticPrivateKey: ByteArray, + private val localStaticPublicKey: ByteArray +) { + + companion object { + private const val TAG = "NoiseSessionManager" + } + + private val sessions = ConcurrentHashMap() + + // Callbacks + var onSessionEstablished: ((String, ByteArray) -> Unit)? = null + var onSessionFailed: ((String, Throwable) -> Unit)? = null + + // MARK: - Simple Session Management + + /** + * Add new session for a peer + */ + fun addSession(peerID: String, session: NoiseSession) { + sessions[peerID] = session + Log.d(TAG, "Added new session for $peerID") + } + + /** + * Get existing session for a peer + */ + fun getSession(peerID: String): NoiseSession? { + val session = sessions[peerID] + Log.d(TAG, "getSession($peerID): ${if (session?.isEstablished() == true) "ESTABLISHED" else "NOT_FOUND"}") + return session + } + + /** + * Remove session for a peer + */ + fun removeSession(peerID: String) { + sessions[peerID]?.destroy() + sessions.remove(peerID) + Log.d(TAG, "Removed session for $peerID") + } + + /** + * SIMPLIFIED: Initiate handshake - no tie breaker, just start + */ + fun initiateHandshake(peerID: String): ByteArray { + Log.d(TAG, "initiateHandshake($peerID)") + + // Remove any existing session first + removeSession(peerID) + + // Create new session as initiator + val session = NoiseSession( + peerID = peerID, + isInitiator = true, + localStaticPrivateKey = localStaticPrivateKey, + localStaticPublicKey = localStaticPublicKey + ) + Log.d(TAG, "Storing new INITIATOR session for $peerID") + addSession(peerID, session) + + try { + val handshakeData = session.startHandshake() + Log.d(TAG, "Started handshake with $peerID as INITIATOR") + return handshakeData + } catch (e: Exception) { + sessions.remove(peerID) + throw e + } + } + + /** + * Handle incoming handshake message + */ + fun processHandshakeMessage(peerID: String, message: ByteArray): ByteArray? { + Log.d(TAG, "processHandshakeMessage($peerID, ${message.size} bytes)") + + try { + var session = getSession(peerID) + + // If no session exists, create one as responder + if (session == null) { + Log.d(TAG, "Creating new RESPONDER session for $peerID") + session = NoiseSession( + peerID = peerID, + isInitiator = false, + localStaticPrivateKey = localStaticPrivateKey, + localStaticPublicKey = localStaticPublicKey + ) + addSession(peerID, session) + } + + // Process handshake message + val response = session.processHandshakeMessage(message) + + // Check if session is established + if (session.isEstablished()) { + Log.d(TAG, "✅ Session ESTABLISHED with $peerID") + val remoteStaticKey = session.getRemoteStaticPublicKey() + if (remoteStaticKey != null) { + onSessionEstablished?.invoke(peerID, remoteStaticKey) + } + } + + return response + + } catch (e: Exception) { + Log.e(TAG, "Handshake failed with $peerID: ${e.message}") + sessions.remove(peerID) + onSessionFailed?.invoke(peerID, e) + throw e + } + } + + /** + * SIMPLIFIED: Encrypt data + */ + fun encrypt(data: ByteArray, peerID: String): ByteArray { + val session = getSession(peerID) ?: throw IllegalStateException("No session found for $peerID") + if (!session.isEstablished()) { + throw IllegalStateException("Session not established with $peerID") + } + return session.encrypt(data) + } + + /** + * SIMPLIFIED: Decrypt data + */ + fun decrypt(encryptedData: ByteArray, peerID: String): ByteArray { + val session = getSession(peerID) + if (session == null) { + Log.e(TAG, "No session found for $peerID when trying to decrypt") + throw IllegalStateException("No session found for $peerID") + } + if (!session.isEstablished()) { + Log.e(TAG, "Session not established with $peerID when trying to decrypt") + throw IllegalStateException("Session not established with $peerID") + } + return session.decrypt(encryptedData) + } + + /** + * Check if session is established with peer + */ + fun hasEstablishedSession(peerID: String): Boolean { + val hasSession = getSession(peerID)?.isEstablished() ?: false + Log.d(TAG, "hasEstablishedSession($peerID): $hasSession") + return hasSession + } + + /** + * Get remote static public key for a peer (if session established) + */ + fun getRemoteStaticKey(peerID: String): ByteArray? { + return getSession(peerID)?.getRemoteStaticPublicKey() + } + + /** + * Get handshake hash for channel binding (if session established) + */ + fun getHandshakeHash(peerID: String): ByteArray? { + return getSession(peerID)?.getHandshakeHash() + } + + /** + * Get sessions that need rekeying based on time or message count + */ + fun getSessionsNeedingRekey(): List { + return sessions.entries + .filter { (_, session) -> + session.isEstablished() && session.needsRekey() + } + .map { it.key } + } + + /** + * Get debug information + */ + fun getDebugInfo(): String = buildString { + appendLine("=== Noise Session Manager Debug ===") + appendLine("Active sessions: ${sessions.size}") + appendLine("") + + if (sessions.isNotEmpty()) { + appendLine("Sessions:") + sessions.forEach { (peerID, session) -> + appendLine(" $peerID: ${session.getState()}") + } + } + } + + /** + * Shutdown manager and clean up all sessions + */ + fun shutdown() { + sessions.values.forEach { it.destroy() } + sessions.clear() + Log.d(TAG, "Noise session manager shut down") + } +} + +/** + * Session-related errors + */ +sealed class NoiseSessionError(message: String, cause: Throwable? = null) : Exception(message, cause) { + object SessionNotFound : NoiseSessionError("Session not found") + object SessionNotEstablished : NoiseSessionError("Session not established") + object InvalidState : NoiseSessionError("Session in invalid state") + object HandshakeFailed : NoiseSessionError("Handshake failed") + object AlreadyEstablished : NoiseSessionError("Session already established") +} diff --git a/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Blake2bMessageDigest.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Blake2bMessageDigest.java new file mode 100644 index 00000000..1d754d47 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/Blake2sMessageDigest.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Blake2sMessageDigest.java new file mode 100644 index 00000000..4f3f98c6 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/ChaChaCore.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/ChaChaCore.java new file mode 100644 index 00000000..cadcb89f --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/Curve25519.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Curve25519.java new file mode 100644 index 00000000..4b06562a --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/Curve448.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Curve448.java new file mode 100644 index 00000000..715b8549 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/GHASH.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/GHASH.java new file mode 100644 index 00000000..386438ae --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.crypto; + +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/NewHope.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/NewHope.java new file mode 100644 index 00000000..df1d8360 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/NewHopeTor.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/NewHopeTor.java new file mode 100644 index 00000000..45c6c774 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/Poly1305.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/Poly1305.java new file mode 100644 index 00000000..820539f8 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.crypto; + +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.protocol.Destroyable; + +/** + * Simple implementation of the Poly1305 message authenticator. + */ +public final class Poly1305 implements Destroyable { + + // 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/bitchat/android/noise/southernstorm/crypto/RijndaelAES.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/RijndaelAES.java new file mode 100644 index 00000000..a34d07c2 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/SHA256MessageDigest.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/SHA256MessageDigest.java new file mode 100644 index 00000000..0ef581df --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/SHA512MessageDigest.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/SHA512MessageDigest.java new file mode 100644 index 00000000..e1f0fcdf --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.crypto; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/crypto/package-info.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/package-info.java new file mode 100644 index 00000000..2f12fb80 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/crypto/package-info.java @@ -0,0 +1,12 @@ + +/** + * Fallback implementations of cryptographic primitives. + * + * This package provides plain Java implementations of the + * cryptographic primitives that Noise requires which do not + * normally come with standard JDK's. + * + * Applications that use Noise won't normally use these classes + * directly. + */ +package com.bitchat.android.noise.southernstorm.crypto; diff --git a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMFallbackCipherState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMFallbackCipherState.java new file mode 100644 index 00000000..683dcfc4 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.protocol; + +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +import com.bitchat.android.noise.southernstorm.crypto.GHASH; +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.java new file mode 100644 index 00000000..7795cbdb --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/ChaChaPolyCipherState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/ChaChaPolyCipherState.java new file mode 100644 index 00000000..bbb8c37b --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.protocol; + +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +import com.bitchat.android.noise.southernstorm.crypto.ChaChaCore; +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/CipherState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/CipherState.java new file mode 100644 index 00000000..95796740 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/CipherStatePair.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/CipherStatePair.java new file mode 100644 index 00000000..88563fce --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/Curve25519DHState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Curve25519DHState.java new file mode 100644 index 00000000..95b81e5a --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.protocol; + +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/Curve448DHState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Curve448DHState.java new file mode 100644 index 00000000..2ab449f6 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.protocol; + +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/DHState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/DHState.java new file mode 100644 index 00000000..2b2477ce --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/DHStateHybrid.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/DHStateHybrid.java new file mode 100644 index 00000000..1446684b --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/Destroyable.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Destroyable.java new file mode 100644 index 00000000..1ab0061c --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/HandshakeState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java new file mode 100644 index 00000000..91fec78a --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java @@ -0,0 +1,1290 @@ +/* + * 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.bitchat.android.noise.southernstorm.protocol; + +import android.util.Log; + +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 static final String TAG = "AndroidHandshake"; + + 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]; + + /** + * Converts a byte array to hex string for logging (matching iOS hex format) + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * 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"); + } + + // Log the symmetric state BEFORE any mixing operations (matching iOS) + Log.d(TAG, "=== ANDROID HANDSHAKE START - INITIAL STATE ==="); + Log.d(TAG, "Protocol: " + symmetric.getProtocolName()); + Log.d(TAG, "Role: " + (isInitiator ? "INITIATOR" : "RESPONDER")); + Log.d(TAG, "Initial symmetric hash: " + bytesToHex(symmetric.getHandshakeHash())); + + // Hash the prologue value. + Log.d(TAG, "Mixing empty prologue"); + if (prologue != null) + symmetric.mixHash(prologue, 0, prologue.length); + else + symmetric.mixHash(emptyPrologue, 0, 0); + Log.d(TAG, "Hash after empty prologue: " + bytesToHex(symmetric.getHandshakeHash())); + + // 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) { + Log.d(TAG, "XX pattern - no pre-message keys to mix"); + 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 { + Log.d(TAG, "XX pattern - no pre-message keys to mix"); + 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); + } + + // Log final state after all initialization (matching iOS) + Log.d(TAG, "=== ANDROID HANDSHAKE START - FINAL STATE ==="); + Log.d(TAG, "Final symmetric hash after mixPreMessageKeys(): " + bytesToHex(symmetric.getHandshakeHash())); + Log.d(TAG, "==========================================="); + + // 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/bitchat/android/noise/southernstorm/protocol/NewHopeDHState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/NewHopeDHState.java new file mode 100644 index 00000000..bb3c275f --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.protocol; + +import java.util.Arrays; + +import com.bitchat.android.noise.southernstorm.crypto.NewHope; +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/Noise.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Noise.java new file mode 100644 index 00000000..c8fb7196 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.protocol; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; + +import com.bitchat.android.noise.southernstorm.crypto.Blake2bMessageDigest; +import com.bitchat.android.noise.southernstorm.crypto.Blake2sMessageDigest; +import com.bitchat.android.noise.southernstorm.crypto.SHA256MessageDigest; +import com.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/Pattern.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/Pattern.java new file mode 100644 index 00000000..2a625c90 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/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.bitchat.android.noise.southernstorm.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/bitchat/android/noise/southernstorm/protocol/SymmetricState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java new file mode 100644 index 00000000..0e70f9f3 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java @@ -0,0 +1,532 @@ +/* + * 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.bitchat.android.noise.southernstorm.protocol; + +import android.util.Log; + +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 static final String TAG = "AndroidSymmetric"; + + private String name; + private CipherState cipher; + private MessageDigest hash; + private byte[] ck; + private byte[] h; + private byte[] prev_h; + + /** + * Converts a byte array to hex string for logging (matching iOS hex format) + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * 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); + + // LOGGING: Initial symmetric state after protocol name initialization (matching iOS) + Log.d(TAG, "=== ANDROID SYMMETRIC STATE INITIALIZED ==="); + Log.d(TAG, "Protocol: " + protocolName); + Log.d(TAG, "Initial hash (h): " + bytesToHex(h)); + Log.d(TAG, "Initial chaining key (ck): " + bytesToHex(ck)); + Log.d(TAG, "Hash length: " + h.length); + Log.d(TAG, "========================================="); + } + + /** + * 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) + { + // LOGGING: Before mixKey operation (matching iOS) + byte[] inputData = new byte[length]; + System.arraycopy(data, offset, inputData, 0, length); + Log.d(TAG, "*** Android mixKey() BEFORE ***"); + Log.d(TAG, "Input data (" + length + " bytes): " + bytesToHex(inputData)); + Log.d(TAG, "Current CK: " + bytesToHex(ck)); + Log.d(TAG, "Current Hash: " + bytesToHex(h)); + + 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); + } + + // LOGGING: After mixKey operation (matching iOS) + Log.d(TAG, "*** Android mixKey() AFTER ***"); + Log.d(TAG, "New CK: " + bytesToHex(ck)); + Log.d(TAG, "Hash unchanged: " + bytesToHex(h)); + Log.d(TAG, "Cipher now has key: " + (cipher.getMACLength() > 0)); + } + + /** + * 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) + { + // LOGGING: Before mixHash operation (matching iOS) + byte[] inputData = new byte[length]; + System.arraycopy(data, offset, inputData, 0, length); + Log.d(TAG, "*** Android mixHash() BEFORE ***"); + Log.d(TAG, "Input data (" + length + " bytes): " + bytesToHex(inputData)); + Log.d(TAG, "Current Hash: " + bytesToHex(h)); + + hashTwo(h, 0, h.length, data, offset, length, h, 0, h.length); + + // LOGGING: After mixHash operation (matching iOS) + Log.d(TAG, "*** Android mixHash() AFTER ***"); + Log.d(TAG, "New Hash: " + bytesToHex(h)); + } + + /** + * 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/bitchat/android/noise/southernstorm/protocol/package-info.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/package-info.java new file mode 100644 index 00000000..8027d7ca --- /dev/null +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/package-info.java @@ -0,0 +1,7 @@ + +/** + * Provides classes for communicating via the Noise protocol. + * + * Reference: http://noiseprotocol.org + */ +package com.bitchat.android.noise.southernstorm.protocol; diff --git a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt index bb39a775..a065c332 100644 --- a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt +++ b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt @@ -7,21 +7,35 @@ import java.nio.ByteOrder import java.util.* /** - * Message types - exact same as iOS version + * Message types - exact same as iOS version with Noise Protocol support */ enum class MessageType(val value: UByte) { ANNOUNCE(0x01u), - KEY_EXCHANGE(0x02u), + // 0x02 was legacy keyExchange - removed LEAVE(0x03u), - MESSAGE(0x04u), + MESSAGE(0x04u), // All user messages (private and broadcast) FRAGMENT_START(0x05u), FRAGMENT_CONTINUE(0x06u), FRAGMENT_END(0x07u), - CHANNEL_ANNOUNCE(0x08u), - CHANNEL_RETENTION(0x09u), - DELIVERY_ACK(0x0Au), - DELIVERY_STATUS_REQUEST(0x0Bu), - READ_RECEIPT(0x0Cu); + CHANNEL_ANNOUNCE(0x08u), // Announce password-protected channel status + CHANNEL_RETENTION(0x09u), // Announce channel retention status + DELIVERY_ACK(0x0Au), // Acknowledge message received + DELIVERY_STATUS_REQUEST(0x0Bu), // Request delivery status update + READ_RECEIPT(0x0Cu), // Message has been read/viewed + + // Noise Protocol messages - exact same as iOS + NOISE_HANDSHAKE_INIT(0x10u), // Noise handshake initiation + NOISE_HANDSHAKE_RESP(0x11u), // Noise handshake response + NOISE_ENCRYPTED(0x12u), // Noise encrypted transport message + NOISE_IDENTITY_ANNOUNCE(0x13u), // Announce static public key for discovery + CHANNEL_KEY_VERIFY_REQUEST(0x14u), // Request key verification for a channel + CHANNEL_KEY_VERIFY_RESPONSE(0x15u), // Response to key verification request + CHANNEL_PASSWORD_UPDATE(0x16u), // Distribute new password to channel members + CHANNEL_METADATA(0x17u), // Announce channel creator and metadata + + // Protocol version negotiation + VERSION_HELLO(0x20u), // Initial version announcement + VERSION_ACK(0x21u); // Version acknowledgment companion object { fun fromValue(value: UByte): MessageType? { @@ -74,7 +88,7 @@ data class BitchatPacket( ) : this( version = 1u, type = type, - senderID = senderID.toByteArray(), + senderID = hexStringToByteArray(senderID), recipientID = null, timestamp = (System.currentTimeMillis()).toULong(), payload = payload, @@ -90,6 +104,27 @@ data class BitchatPacket( fun fromBinaryData(data: ByteArray): BitchatPacket? { return BinaryProtocol.decode(data) } + + /** + * Convert hex string peer ID to binary data (8 bytes) - exactly same as iOS + */ + private fun hexStringToByteArray(hexString: String): ByteArray { + val result = ByteArray(8) { 0 } // Initialize with zeros, exactly 8 bytes + var tempID = hexString + var index = 0 + + while (tempID.length >= 2 && index < 8) { + val hexByte = tempID.substring(0, 2) + val byte = hexByte.toIntOrNull(16)?.toByte() + if (byte != null) { + result[index] = byte + } + tempID = tempID.substring(2) + index++ + } + + return result + } } override fun equals(other: Any?): Boolean { @@ -219,7 +254,12 @@ object BinaryProtocol { val result = ByteArray(buffer.position()) buffer.rewind() buffer.get(result) - return result + + // Apply padding to standard block sizes for traffic analysis resistance + val optimalSize = MessagePadding.optimalBlockSize(result.size) + val paddedData = MessagePadding.pad(result, optimalSize) + + return paddedData } catch (e: Exception) { return null @@ -228,9 +268,12 @@ object BinaryProtocol { fun decode(data: ByteArray): BitchatPacket? { try { - if (data.size < HEADER_SIZE + SENDER_ID_SIZE) return null + // Remove padding first - exactly same as iOS + val unpaddedData = MessagePadding.unpad(data) - val buffer = ByteBuffer.wrap(data).apply { order(ByteOrder.BIG_ENDIAN) } + if (unpaddedData.size < HEADER_SIZE + SENDER_ID_SIZE) return null + + val buffer = ByteBuffer.wrap(unpaddedData).apply { order(ByteOrder.BIG_ENDIAN) } // Header val version = buffer.get().toUByte() @@ -256,7 +299,7 @@ object BinaryProtocol { if (hasRecipient) expectedSize += RECIPIENT_ID_SIZE if (hasSignature) expectedSize += SIGNATURE_SIZE - if (data.size < expectedSize) return null + if (unpaddedData.size < expectedSize) return null // SenderID val senderID = ByteArray(SENDER_ID_SIZE) @@ -310,25 +353,3 @@ object BinaryProtocol { } } } - -/** - * Compression utilities - temporarily disabled for initial build - */ -object CompressionUtil { - private const val COMPRESSION_THRESHOLD = 100 // bytes - - fun shouldCompress(data: ByteArray): Boolean { - // Temporarily disabled compression - return false - } - - fun compress(data: ByteArray): ByteArray? { - // Temporarily disabled compression - return null - } - - fun decompress(compressedData: ByteArray, originalSize: Int): ByteArray? { - // Temporarily disabled compression - return null - } -} diff --git a/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt b/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt new file mode 100644 index 00000000..6e3ea80a --- /dev/null +++ b/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt @@ -0,0 +1,86 @@ +package com.bitchat.android.protocol + +import java.io.ByteArrayOutputStream +import java.util.zip.Deflater +import java.util.zip.Inflater + +/** + * Compression utilities - LZ4-like functionality using Deflater/Inflater + * Android doesn't have native LZ4, so we use Java's built-in compression + */ +object CompressionUtil { + private const val COMPRESSION_THRESHOLD = 100 // bytes - same as iOS + + /** + * Helper to check if compression is worth it - exact same logic as iOS + */ + fun shouldCompress(data: ByteArray): Boolean { + // Don't compress if: + // 1. Data is too small + // 2. Data appears to be already compressed (high entropy) + if (data.size < COMPRESSION_THRESHOLD) return false + + // Simple entropy check - count unique bytes + val byteFrequency = mutableMapOf() + for (byte in data) { + byteFrequency[byte] = (byteFrequency[byte] ?: 0) + 1 + } + + // If we have very high byte diversity, data is likely already compressed + val uniqueByteRatio = byteFrequency.size.toDouble() / minOf(data.size, 256).toDouble() + return uniqueByteRatio < 0.9 // Compress if less than 90% unique bytes + } + + /** + * Compress data using Deflater (closest to LZ4 available on Android) + */ + fun compress(data: ByteArray): ByteArray? { + // Skip compression for small data + if (data.size < COMPRESSION_THRESHOLD) return null + + try { + val deflater = Deflater(Deflater.BEST_SPEED) // Fast compression like LZ4 + deflater.setInput(data) + deflater.finish() + + val buffer = ByteArray(data.size + 16) // Some overhead space + val compressedSize = deflater.deflate(buffer) + deflater.end() + + // Only return if compression was beneficial + if (compressedSize > 0 && compressedSize < data.size) { + return buffer.copyOfRange(0, compressedSize) + } + + return null + } catch (e: Exception) { + return null + } + } + + /** + * Decompress data using Inflater + */ + fun decompress(compressedData: ByteArray, originalSize: Int): ByteArray? { + try { + val inflater = Inflater() + inflater.setInput(compressedData) + + val result = ByteArray(originalSize) + val decompressedSize = inflater.inflate(result) + inflater.end() + + if (decompressedSize > 0) { + return if (decompressedSize == originalSize) { + result + } else { + result.copyOfRange(0, decompressedSize) + } + } + + return null + } catch (e: Exception) { + return null + } + } +} diff --git a/app/src/main/java/com/bitchat/android/protocol/MessagePadding.kt b/app/src/main/java/com/bitchat/android/protocol/MessagePadding.kt new file mode 100644 index 00000000..20457d2c --- /dev/null +++ b/app/src/main/java/com/bitchat/android/protocol/MessagePadding.kt @@ -0,0 +1,77 @@ +package com.bitchat.android.protocol + +import java.security.SecureRandom + +/** + * Privacy-preserving padding utilities - exact same as iOS version + * Provides traffic analysis resistance by normalizing message sizes + */ +object MessagePadding { + // Standard block sizes for padding - exact same as iOS + private val blockSizes = listOf(256, 512, 1024, 2048) + + /** + * Find optimal block size for data - exact same logic as iOS + */ + fun optimalBlockSize(dataSize: Int): Int { + // Account for encryption overhead (~16 bytes for AES-GCM tag) + val totalSize = dataSize + 16 + + // Find smallest block that fits + for (blockSize in blockSizes) { + if (totalSize <= blockSize) { + return blockSize + } + } + + // For very large messages, just use the original size + // (will be fragmented anyway) + return dataSize + } + + /** + * Add PKCS#7-style padding to reach target size - exact same as iOS + */ + fun pad(data: ByteArray, targetSize: Int): ByteArray { + if (data.size >= targetSize) return data + + val paddingNeeded = targetSize - data.size + + // PKCS#7 only supports padding up to 255 bytes + // If we need more padding than that, don't pad - return original data + if (paddingNeeded > 255) return data + + val result = ByteArray(targetSize) + + // Copy original data + System.arraycopy(data, 0, result, 0, data.size) + + // Standard PKCS#7 padding - fill with random bytes then add padding length + val randomBytes = ByteArray(paddingNeeded - 1) + SecureRandom().nextBytes(randomBytes) + + // Copy random bytes + System.arraycopy(randomBytes, 0, result, data.size, paddingNeeded - 1) + + // Last byte tells how much padding was added + result[result.size - 1] = paddingNeeded.toByte() + + return result + } + + /** + * Remove padding from data - exact same as iOS + */ + fun unpad(data: ByteArray): ByteArray { + if (data.isEmpty()) return data + + // Last byte tells us how much padding to remove + val paddingLength = data[data.size - 1].toInt() and 0xFF + if (paddingLength <= 0 || paddingLength > data.size) { + // Invalid padding, return original data + return data + } + + return data.copyOfRange(0, data.size - paddingLength) + } +} diff --git a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt index 50662361..a4919293 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt @@ -156,13 +156,15 @@ fun ChatHeaderContent( val favoritePeers by viewModel.favoritePeers.observeAsState(emptySet()) val fingerprint = viewModel.privateChatManager.getPeerFingerprint(selectedPrivatePeer) val isFavorite = favoritePeers.contains(fingerprint) + val hasEncryption = viewModel.meshService.shouldShowEncryptionIcon(selectedPrivatePeer) - Log.d("ChatHeader", "Header recomposing: peer=$selectedPrivatePeer, fingerprint=$fingerprint, isFav=$isFavorite") + Log.d("ChatHeader", "Header recomposing: peer=$selectedPrivatePeer, fingerprint=$fingerprint, isFav=$isFavorite, encrypted=$hasEncryption") PrivateChatHeader( peerID = selectedPrivatePeer, peerNicknames = viewModel.meshService.getPeerNicknames(), isFavorite = isFavorite, + hasEncryption = hasEncryption, onBackClick = onBackClick, onToggleFavorite = { viewModel.toggleFavorite(selectedPrivatePeer) } ) @@ -195,6 +197,7 @@ private fun PrivateChatHeader( peerID: String, peerNicknames: Map, isFavorite: Boolean, + hasEncryption: Boolean, onBackClick: () -> Unit, onToggleFavorite: () -> Unit ) { @@ -243,6 +246,18 @@ private fun PrivateChatHeader( modifier = Modifier.size(16.dp), tint = Color(0xFFFF9500) // Orange to match private message theme ) + + // Show encryption status icon if session is established + if (hasEncryption) { + Spacer(modifier = Modifier.width(2.dp)) + Icon( + imageVector = Icons.Filled.Security, + contentDescription = "End-to-end encrypted", + modifier = Modifier.size(14.dp), + tint = Color(0xFF00C851) // Green to indicate verified encryption + ) + } + Spacer(modifier = Modifier.width(4.dp)) Text( text = peerNickname, diff --git a/app/src/main/java/com/bitchat/android/util/BinaryEncodingUtils.kt b/app/src/main/java/com/bitchat/android/util/BinaryEncodingUtils.kt new file mode 100644 index 00000000..549f5971 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/util/BinaryEncodingUtils.kt @@ -0,0 +1,365 @@ +package com.bitchat.android.util + +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.* + +/** + * Binary encoding utilities for efficient protocol messages + * Compatible with iOS version BinaryEncodingUtils.swift + */ + +// MARK: - Hex Encoding/Decoding Extensions + +fun ByteArray.hexEncodedString(): String { + if (this.isEmpty()) { + return "" + } + return this.joinToString("") { "%02x".format(it) } +} + +fun String.dataFromHexString(): ByteArray? { + val len = this.length / 2 + val data = ByteArray(len) + var index = 0 + + for (i in 0 until len) { + val hexByte = this.substring(i * 2, i * 2 + 2) + val byte = hexByte.toIntOrNull(16)?.toByte() ?: return null + data[index++] = byte + } + + return data +} + +// MARK: - Binary Encoding Utilities + +class BinaryDataBuilder { + private val _buffer = mutableListOf() + + // Make buffer accessible for direct manipulation when needed + val buffer: MutableList get() = _buffer + + // MARK: Writing + + fun appendUInt8(value: UByte) { + buffer.add(value.toByte()) + } + + fun appendUInt16(value: UShort) { + buffer.add(((value.toInt() shr 8) and 0xFF).toByte()) + buffer.add((value.toInt() and 0xFF).toByte()) + } + + fun appendUInt32(value: UInt) { + buffer.add(((value.toLong() shr 24) and 0xFF).toByte()) + buffer.add(((value.toLong() shr 16) and 0xFF).toByte()) + buffer.add(((value.toLong() shr 8) and 0xFF).toByte()) + buffer.add((value.toLong() and 0xFF).toByte()) + } + + fun appendUInt64(value: ULong) { + for (i in 7 downTo 0) { + buffer.add(((value.toLong() shr (i * 8)) and 0xFF).toByte()) + } + } + + fun appendString(string: String, maxLength: Int = 255) { + val data = string.toByteArray(Charsets.UTF_8) + val length = minOf(data.size, maxLength) + + if (maxLength <= 255) { + buffer.add(length.toByte()) + } else { + appendUInt16(length.toUShort()) + } + + buffer.addAll(data.take(length).toList()) + } + + fun appendData(data: ByteArray, maxLength: Int = 65535) { + val length = minOf(data.size, maxLength) + + if (maxLength <= 255) { + buffer.add(length.toByte()) + } else { + appendUInt16(length.toUShort()) + } + + buffer.addAll(data.take(length).toList()) + } + + fun appendDate(date: Date) { + val timestamp = (date.time).toULong() // milliseconds + appendUInt64(timestamp) + } + + fun appendUUID(uuid: String) { + // Convert UUID string to 16 bytes + val uuidData = ByteArray(16) + + val cleanUUID = uuid.replace("-", "") + var index = 0 + + for (i in 0 until 16) { + if (index + 1 < cleanUUID.length) { + val hexByte = cleanUUID.substring(index, index + 2) + uuidData[i] = hexByte.toIntOrNull(16)?.toByte() ?: 0 + index += 2 + } + } + + buffer.addAll(uuidData.toList()) + } + + fun toByteArray(): ByteArray { + return buffer.toByteArray() + } +} + +// MARK: - Binary Data Reading Extensions + +class BinaryDataReader(private val data: ByteArray) { + private var offset = 0 + + // MARK: Reading + + fun readUInt8(): UByte? { + if (offset >= data.size) return null + val value = data[offset].toUByte() + offset += 1 + return value + } + + fun readUInt16(): UShort? { + if (offset + 2 > data.size) return null + val value = ((data[offset].toUByte().toInt() shl 8) or + (data[offset + 1].toUByte().toInt())).toUShort() + offset += 2 + return value + } + + fun readUInt32(): UInt? { + if (offset + 4 > data.size) return null + val value = ((data[offset].toUByte().toUInt() shl 24) or + (data[offset + 1].toUByte().toUInt() shl 16) or + (data[offset + 2].toUByte().toUInt() shl 8) or + (data[offset + 3].toUByte().toUInt())) + offset += 4 + return value + } + + fun readUInt64(): ULong? { + if (offset + 8 > data.size) return null + var value = 0UL + for (i in 0 until 8) { + value = (value shl 8) or data[offset + i].toUByte().toULong() + } + offset += 8 + return value + } + + fun readString(maxLength: Int = 255): String? { + val length: Int = if (maxLength <= 255) { + readUInt8()?.toInt() ?: return null + } else { + readUInt16()?.toInt() ?: return null + } + + if (offset + length > data.size) return null + + val stringData = data.sliceArray(offset until offset + length) + offset += length + + return String(stringData, Charsets.UTF_8) + } + + fun readData(maxLength: Int = 65535): ByteArray? { + val length: Int = if (maxLength <= 255) { + readUInt8()?.toInt() ?: return null + } else { + readUInt16()?.toInt() ?: return null + } + + if (offset + length > data.size) return null + + val data = this.data.sliceArray(offset until offset + length) + offset += length + + return data + } + + fun readDate(): Date? { + val timestamp = readUInt64() ?: return null + return Date(timestamp.toLong()) + } + + fun readUUID(): String? { + if (offset + 16 > data.size) return null + + val uuidData = data.sliceArray(offset until offset + 16) + offset += 16 + + // Convert 16 bytes to UUID string format + val uuid = uuidData.joinToString("") { "%02x".format(it) } + + // Insert hyphens at proper positions: 8-4-4-4-12 + val result = StringBuilder() + for ((index, char) in uuid.withIndex()) { + if (index == 8 || index == 12 || index == 16 || index == 20) { + result.append("-") + } + result.append(char) + } + + return result.toString().uppercase() + } + + fun readFixedBytes(count: Int): ByteArray? { + if (offset + count > data.size) return null + + val data = this.data.sliceArray(offset until offset + count) + offset += count + + return data + } + + // Get current offset position + val currentOffset: Int get() = offset +} + +// MARK: - Binary Message Protocol + +interface BinaryEncodable { + fun toBinaryData(): ByteArray +} + +// MARK: - Message Type Registry + +enum class BinaryMessageType(val value: UByte) { + DELIVERY_ACK(0x01u), + READ_RECEIPT(0x02u), + CHANNEL_KEY_VERIFY_REQUEST(0x03u), + CHANNEL_KEY_VERIFY_RESPONSE(0x04u), + CHANNEL_PASSWORD_UPDATE(0x05u), + CHANNEL_METADATA(0x06u), + VERSION_HELLO(0x07u), + VERSION_ACK(0x08u), + NOISE_IDENTITY_ANNOUNCEMENT(0x09u), + NOISE_MESSAGE(0x0Au); + + companion object { + fun fromValue(value: UByte): BinaryMessageType? { + return values().find { it.value == value } + } + } +} + +// Extension functions for ByteArray to support iOS-style data manipulation +fun ByteArray.readUInt8(at: IntArray): UByte? { + val offset = at[0] + if (offset >= this.size) return null + val value = this[offset].toUByte() + at[0] += 1 + return value +} + +fun ByteArray.readUInt16(at: IntArray): UShort? { + val offset = at[0] + if (offset + 2 > this.size) return null + val value = ((this[offset].toUByte().toInt() shl 8) or + (this[offset + 1].toUByte().toInt())).toUShort() + at[0] += 2 + return value +} + +fun ByteArray.readUInt32(at: IntArray): UInt? { + val offset = at[0] + if (offset + 4 > this.size) return null + val value = ((this[offset].toUByte().toUInt() shl 24) or + (this[offset + 1].toUByte().toUInt() shl 16) or + (this[offset + 2].toUByte().toUInt() shl 8) or + (this[offset + 3].toUByte().toUInt())) + at[0] += 4 + return value +} + +fun ByteArray.readUInt64(at: IntArray): ULong? { + val offset = at[0] + if (offset + 8 > this.size) return null + var value = 0UL + for (i in 0 until 8) { + value = (value shl 8) or this[offset + i].toUByte().toULong() + } + at[0] += 8 + return value +} + +fun ByteArray.readString(at: IntArray, maxLength: Int = 255): String? { + val length: Int = if (maxLength <= 255) { + readUInt8(at)?.toInt() ?: return null + } else { + readUInt16(at)?.toInt() ?: return null + } + + val offset = at[0] + if (offset + length > this.size) return null + + val stringData = this.sliceArray(offset until offset + length) + at[0] += length + + return String(stringData, Charsets.UTF_8) +} + +fun ByteArray.readData(at: IntArray, maxLength: Int = 65535): ByteArray? { + val length: Int = if (maxLength <= 255) { + readUInt8(at)?.toInt() ?: return null + } else { + readUInt16(at)?.toInt() ?: return null + } + + val offset = at[0] + if (offset + length > this.size) return null + + val data = this.sliceArray(offset until offset + length) + at[0] += length + + return data +} + +fun ByteArray.readDate(at: IntArray): Date? { + val timestamp = readUInt64(at) ?: return null + return Date(timestamp.toLong()) +} + +fun ByteArray.readUUID(at: IntArray): String? { + val offset = at[0] + if (offset + 16 > this.size) return null + + val uuidData = this.sliceArray(offset until offset + 16) + at[0] += 16 + + // Convert 16 bytes to UUID string format + val uuid = uuidData.joinToString("") { "%02x".format(it) } + + // Insert hyphens at proper positions: 8-4-4-4-12 + val result = StringBuilder() + for ((index, char) in uuid.withIndex()) { + if (index == 8 || index == 12 || index == 16 || index == 20) { + result.append("-") + } + result.append(char) + } + + return result.toString().uppercase() +} + +fun ByteArray.readFixedBytes(at: IntArray, count: Int): ByteArray? { + val offset = at[0] + if (offset + count > this.size) return null + + val data = this.sliceArray(offset until offset + count) + at[0] += count + + return data +} diff --git a/app/src/main/java/com/bitchat/android/util/ByteArrayExtensions.kt b/app/src/main/java/com/bitchat/android/util/ByteArrayExtensions.kt new file mode 100644 index 00000000..b2acf112 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/util/ByteArrayExtensions.kt @@ -0,0 +1,8 @@ +package com.bitchat.android.util + +/** + * Extension function to convert a ByteArray to a hexadecimal string. + */ +fun ByteArray.toHexString(): String { + return this.joinToString("") { "%02x".format(it) } +} diff --git a/app/src/main/java/com/bitchat/android/util/ByteArrayWrapper.kt b/app/src/main/java/com/bitchat/android/util/ByteArrayWrapper.kt new file mode 100644 index 00000000..da078d3d --- /dev/null +++ b/app/src/main/java/com/bitchat/android/util/ByteArrayWrapper.kt @@ -0,0 +1,26 @@ +package com.bitchat.android.util + +import java.util.Arrays + +/** + * A wrapper class for ByteArray to allow it to be used as a key in HashMaps. + * The default ByteArray does not override equals() and hashCode() based on content. + * + * @param bytes The byte array to wrap. + */ +data class ByteArrayWrapper(val bytes: ByteArray) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as ByteArrayWrapper + return Arrays.equals(bytes, other.bytes) + } + + override fun hashCode(): Int { + return Arrays.hashCode(bytes) + } + + fun toHexString(): String { + return bytes.joinToString("") { "%02x".format(it) } + } +}