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
This commit is contained in:
callebtc
2025-07-24 12:01:46 +02:00
committed by GitHub
parent 9e9231353b
commit c3c395832c
58 changed files with 15209 additions and 517 deletions
@@ -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<String, String>() // peerID -> fingerprint
// Storage for peer keys
private val peerPublicKeys = mutableMapOf<String, X25519PublicKeyParameters>()
private val peerSigningKeys = mutableMapOf<String, Ed25519PublicKeyParameters>()
private val peerIdentityKeys = mutableMapOf<String, Ed25519PublicKeyParameters>()
private val sharedSecrets = mutableMapOf<String, ByteArray>()
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<String> {
return establishedSessions.keys.toList()
}
/**
* Get sessions that need rekeying
*/
fun getSessionsNeedingRekey(): List<String> {
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")
}
}
@@ -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<ByteArray, ByteArray>? {
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)
}
}
@@ -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
@@ -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}")
@@ -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)
}
}
@@ -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 {
@@ -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<String, Int> = 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<String> {
// 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
}
}
/**
@@ -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<BroadcastRequest>(
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")
}
}
@@ -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<BitchatPacket> {
val data = packet.toBinaryData() ?: return emptyList()
if (data.size <= MAX_FRAGMENT_SIZE) {
if (data.size <= FRAGMENT_SIZE_THRESHOLD) {
return listOf(packet) // No fragmentation needed
}
@@ -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)
@@ -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<String, CompletableDeferred<Unit>>()
@OptIn(ObsoleteCoroutinesApi::class)
private fun getOrCreateActorForPeer(peerID: String) = processorScope.actor<RoutedPacket>(
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<String, kotlinx.coroutines.channels.SendChannel<RoutedPacket>>()
/**
* 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)
@@ -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)
@@ -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)
}
@@ -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}")
@@ -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
}
}
}
}
@@ -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
}
}
}
}
@@ -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
}
}
}
@@ -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
}
}
}
}
@@ -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<String, SecretKeySpec>()
// Channel passwords (for rekey operations)
private val channelPasswords = ConcurrentHashMap<String, String>()
// 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<String, String>? {
return try {
val json = String(data, Charsets.UTF_8)
val packet = com.google.gson.Gson().fromJson(json, Map::class.java) as Map<String, Any>
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<String> {
return channelKeys.keys.toSet()
}
/**
* Clear all channel data
*/
fun clear() {
channelKeys.clear()
channelPasswords.clear()
Log.d(TAG, "Cleared all channel encryption data")
}
}
@@ -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<String, String>() // peerID -> fingerprint
private val fingerprintToPeerID = ConcurrentHashMap<String, String>() // 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<String> {
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<ByteArray, ByteArray> {
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}")
}
@@ -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")
}
@@ -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<String, NoiseSession>()
// 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<String> {
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")
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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]);
}
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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);
}
}
File diff suppressed because it is too large Load Diff
@@ -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<PARAM_N;i++)
coeffs[i] = x[i];
return false;
}
private static void batcher84(char[] x, int offset)
{
int c, t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 16]) & (c >> 31); x[offset + 0] ^= t; x[offset + 16] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 48]) & (c >> 31); x[offset + 32] ^= t; x[offset + 48] ^= t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 32]) & (c >> 31); x[offset + 0] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 48]) & (c >> 31); x[offset + 16] ^= t; x[offset + 48] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 80]) & (c >> 31); x[offset + 64] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 112]) & (c >> 31); x[offset + 96] ^= t; x[offset + 112] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 96]) & (c >> 31); x[offset + 64] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 112]) & (c >> 31); x[offset + 80] ^= t; x[offset + 112] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 64]) & (c >> 31); x[offset + 0] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 96]) & (c >> 31); x[offset + 32] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 80]) & (c >> 31); x[offset + 16] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 112]) & (c >> 31); x[offset + 48] ^= t; x[offset + 112] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 144]) & (c >> 31); x[offset + 128] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 176]) & (c >> 31); x[offset + 160] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 160]) & (c >> 31); x[offset + 128] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 176]) & (c >> 31); x[offset + 144] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 208]) & (c >> 31); x[offset + 192] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 240]) & (c >> 31); x[offset + 224] ^= t; x[offset + 240] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 224]) & (c >> 31); x[offset + 192] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 240]) & (c >> 31); x[offset + 208] ^= t; x[offset + 240] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 192]) & (c >> 31); x[offset + 128] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 224]) & (c >> 31); x[offset + 160] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 208]) & (c >> 31); x[offset + 144] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 240]) & (c >> 31); x[offset + 176] ^= t; x[offset + 240] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 128]) & (c >> 31); x[offset + 0] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 192]) & (c >> 31); x[offset + 64] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 160]) & (c >> 31); x[offset + 32] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 224]) & (c >> 31); x[offset + 96] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 144]) & (c >> 31); x[offset + 16] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 208]) & (c >> 31); x[offset + 80] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 176]) & (c >> 31); x[offset + 48] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 240]) & (c >> 31); x[offset + 112] ^= t; x[offset + 240] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 272]) & (c >> 31); x[offset + 256] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 304]) & (c >> 31); x[offset + 288] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 288]) & (c >> 31); x[offset + 256] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 304]) & (c >> 31); x[offset + 272] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 336]) & (c >> 31); x[offset + 320] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 368]) & (c >> 31); x[offset + 352] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 352]) & (c >> 31); x[offset + 320] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 368]) & (c >> 31); x[offset + 336] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 320]) & (c >> 31); x[offset + 256] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 352]) & (c >> 31); x[offset + 288] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 336]) & (c >> 31); x[offset + 272] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 368]) & (c >> 31); x[offset + 304] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 400]) & (c >> 31); x[offset + 384] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 432]) & (c >> 31); x[offset + 416] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 416]) & (c >> 31); x[offset + 384] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 432]) & (c >> 31); x[offset + 400] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 464]) & (c >> 31); x[offset + 448] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 496]) & (c >> 31); x[offset + 480] ^= t; x[offset + 496] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 480]) & (c >> 31); x[offset + 448] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 496]) & (c >> 31); x[offset + 464] ^= t; x[offset + 496] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 448]) & (c >> 31); x[offset + 384] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 480]) & (c >> 31); x[offset + 416] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 464]) & (c >> 31); x[offset + 400] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 496]) & (c >> 31); x[offset + 432] ^= t; x[offset + 496] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 384]) & (c >> 31); x[offset + 256] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 448]) & (c >> 31); x[offset + 320] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 416]) & (c >> 31); x[offset + 288] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 480]) & (c >> 31); x[offset + 352] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 400]) & (c >> 31); x[offset + 272] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 464]) & (c >> 31); x[offset + 336] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 432]) & (c >> 31); x[offset + 304] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 496]) & (c >> 31); x[offset + 368] ^= t; x[offset + 496] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 256]) & (c >> 31); x[offset + 0] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 384]) & (c >> 31); x[offset + 128] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); x[offset + 128] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 320]) & (c >> 31); x[offset + 64] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 448]) & (c >> 31); x[offset + 192] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); x[offset + 192] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); x[offset + 192] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 288]) & (c >> 31); x[offset + 32] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 416]) & (c >> 31); x[offset + 160] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); x[offset + 160] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 352]) & (c >> 31); x[offset + 96] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 480]) & (c >> 31); x[offset + 224] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); x[offset + 224] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); x[offset + 224] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); x[offset + 224] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 272]) & (c >> 31); x[offset + 16] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 400]) & (c >> 31); x[offset + 144] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); x[offset + 144] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 336]) & (c >> 31); x[offset + 80] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 464]) & (c >> 31); x[offset + 208] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); x[offset + 208] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); x[offset + 208] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 304]) & (c >> 31); x[offset + 48] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 432]) & (c >> 31); x[offset + 176] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); x[offset + 176] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 368]) & (c >> 31); x[offset + 112] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 496]) & (c >> 31); x[offset + 240] ^= t; x[offset + 496] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); x[offset + 240] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); x[offset + 240] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); x[offset + 240] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); x[offset + 240] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 528]) & (c >> 31); x[offset + 512] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 560]) & (c >> 31); x[offset + 544] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 544]) & (c >> 31); x[offset + 512] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 560]) & (c >> 31); x[offset + 528] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 592]) & (c >> 31); x[offset + 576] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 624]) & (c >> 31); x[offset + 608] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 608]) & (c >> 31); x[offset + 576] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 624]) & (c >> 31); x[offset + 592] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 576]) & (c >> 31); x[offset + 512] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 608]) & (c >> 31); x[offset + 544] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 592]) & (c >> 31); x[offset + 528] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 624]) & (c >> 31); x[offset + 560] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 656]) & (c >> 31); x[offset + 640] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 688]) & (c >> 31); x[offset + 672] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 672]) & (c >> 31); x[offset + 640] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 688]) & (c >> 31); x[offset + 656] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 720]) & (c >> 31); x[offset + 704] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 752]) & (c >> 31); x[offset + 736] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 736]) & (c >> 31); x[offset + 704] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 752]) & (c >> 31); x[offset + 720] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 704]) & (c >> 31); x[offset + 640] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 736]) & (c >> 31); x[offset + 672] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 720]) & (c >> 31); x[offset + 656] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 752]) & (c >> 31); x[offset + 688] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 640]) & (c >> 31); x[offset + 512] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 704]) & (c >> 31); x[offset + 576] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 672]) & (c >> 31); x[offset + 544] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 736]) & (c >> 31); x[offset + 608] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 656]) & (c >> 31); x[offset + 528] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 720]) & (c >> 31); x[offset + 592] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 688]) & (c >> 31); x[offset + 560] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 752]) & (c >> 31); x[offset + 624] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 784]) & (c >> 31); x[offset + 768] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 816]) & (c >> 31); x[offset + 800] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 800]) & (c >> 31); x[offset + 768] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 816]) & (c >> 31); x[offset + 784] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 848]) & (c >> 31); x[offset + 832] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 880]) & (c >> 31); x[offset + 864] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 864]) & (c >> 31); x[offset + 832] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 880]) & (c >> 31); x[offset + 848] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 832]) & (c >> 31); x[offset + 768] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 864]) & (c >> 31); x[offset + 800] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 848]) & (c >> 31); x[offset + 784] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 880]) & (c >> 31); x[offset + 816] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 912]) & (c >> 31); x[offset + 896] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 944]) & (c >> 31); x[offset + 928] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 928]) & (c >> 31); x[offset + 896] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 944]) & (c >> 31); x[offset + 912] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 976]) & (c >> 31); x[offset + 960] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1008]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1008] ^= t;
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 992]) & (c >> 31); x[offset + 960] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1008]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1008] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 960]) & (c >> 31); x[offset + 896] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 992]) & (c >> 31); x[offset + 928] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 976]) & (c >> 31); x[offset + 912] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 1008]) & (c >> 31); x[offset + 944] ^= t; x[offset + 1008] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 896]) & (c >> 31); x[offset + 768] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 960]) & (c >> 31); x[offset + 832] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 928]) & (c >> 31); x[offset + 800] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 992]) & (c >> 31); x[offset + 864] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 912]) & (c >> 31); x[offset + 784] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 976]) & (c >> 31); x[offset + 848] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 944]) & (c >> 31); x[offset + 816] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 1008]) & (c >> 31); x[offset + 880] ^= t; x[offset + 1008] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 768]) & (c >> 31); x[offset + 512] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 896]) & (c >> 31); x[offset + 640] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); x[offset + 640] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 832]) & (c >> 31); x[offset + 576] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 960]) & (c >> 31); x[offset + 704] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); x[offset + 704] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); x[offset + 704] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 800]) & (c >> 31); x[offset + 544] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 928]) & (c >> 31); x[offset + 672] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); x[offset + 672] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 864]) & (c >> 31); x[offset + 608] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 992]) & (c >> 31); x[offset + 736] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); x[offset + 736] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); x[offset + 736] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); x[offset + 736] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 784]) & (c >> 31); x[offset + 528] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 912]) & (c >> 31); x[offset + 656] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); x[offset + 656] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 848]) & (c >> 31); x[offset + 592] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 976]) & (c >> 31); x[offset + 720] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); x[offset + 720] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); x[offset + 720] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 816]) & (c >> 31); x[offset + 560] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 944]) & (c >> 31); x[offset + 688] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); x[offset + 688] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 880]) & (c >> 31); x[offset + 624] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 1008]) & (c >> 31); x[offset + 752] ^= t; x[offset + 1008] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); x[offset + 752] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); x[offset + 752] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); x[offset + 752] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); x[offset + 752] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 512]) & (c >> 31); x[offset + 0] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 768]) & (c >> 31); x[offset + 256] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 512]) & (c >> 31); x[offset + 256] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 640]) & (c >> 31); x[offset + 128] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 896]) & (c >> 31); x[offset + 384] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 640]) & (c >> 31); x[offset + 384] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); x[offset + 128] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 512]) & (c >> 31); x[offset + 384] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); x[offset + 640] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 576]) & (c >> 31); x[offset + 64] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 832]) & (c >> 31); x[offset + 320] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 576]) & (c >> 31); x[offset + 320] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 704]) & (c >> 31); x[offset + 192] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 960]) & (c >> 31); x[offset + 448] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 704]) & (c >> 31); x[offset + 448] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); x[offset + 192] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 576]) & (c >> 31); x[offset + 448] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); x[offset + 704] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); x[offset + 192] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 512]) & (c >> 31); x[offset + 448] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); x[offset + 704] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 544]) & (c >> 31); x[offset + 32] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 800]) & (c >> 31); x[offset + 288] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 544]) & (c >> 31); x[offset + 288] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 672]) & (c >> 31); x[offset + 160] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 928]) & (c >> 31); x[offset + 416] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 672]) & (c >> 31); x[offset + 416] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); x[offset + 160] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 544]) & (c >> 31); x[offset + 416] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); x[offset + 672] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 608]) & (c >> 31); x[offset + 96] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 864]) & (c >> 31); x[offset + 352] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 608]) & (c >> 31); x[offset + 352] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 736]) & (c >> 31); x[offset + 224] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 992]) & (c >> 31); x[offset + 480] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 736]) & (c >> 31); x[offset + 480] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); x[offset + 224] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 608]) & (c >> 31); x[offset + 480] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); x[offset + 736] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); x[offset + 224] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 544]) & (c >> 31); x[offset + 480] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); x[offset + 736] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); x[offset + 224] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 512]) & (c >> 31); x[offset + 480] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); x[offset + 736] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 528]) & (c >> 31); x[offset + 16] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 784]) & (c >> 31); x[offset + 272] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 528]) & (c >> 31); x[offset + 272] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 656]) & (c >> 31); x[offset + 144] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 912]) & (c >> 31); x[offset + 400] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 656]) & (c >> 31); x[offset + 400] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); x[offset + 144] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 528]) & (c >> 31); x[offset + 400] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); x[offset + 656] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 592]) & (c >> 31); x[offset + 80] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 848]) & (c >> 31); x[offset + 336] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 592]) & (c >> 31); x[offset + 336] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 720]) & (c >> 31); x[offset + 208] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 976]) & (c >> 31); x[offset + 464] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 720]) & (c >> 31); x[offset + 464] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); x[offset + 208] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 592]) & (c >> 31); x[offset + 464] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); x[offset + 720] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); x[offset + 208] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 528]) & (c >> 31); x[offset + 464] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); x[offset + 720] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 560]) & (c >> 31); x[offset + 48] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 816]) & (c >> 31); x[offset + 304] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 560]) & (c >> 31); x[offset + 304] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 688]) & (c >> 31); x[offset + 176] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 944]) & (c >> 31); x[offset + 432] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 688]) & (c >> 31); x[offset + 432] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); x[offset + 176] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 560]) & (c >> 31); x[offset + 432] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); x[offset + 688] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 624]) & (c >> 31); x[offset + 112] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 880]) & (c >> 31); x[offset + 368] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 624]) & (c >> 31); x[offset + 368] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 752]) & (c >> 31); x[offset + 240] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 1008]) & (c >> 31); x[offset + 496] ^= t; x[offset + 1008] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 752]) & (c >> 31); x[offset + 496] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); x[offset + 240] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 624]) & (c >> 31); x[offset + 496] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); x[offset + 752] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); x[offset + 240] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 560]) & (c >> 31); x[offset + 496] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); x[offset + 752] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); x[offset + 240] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 528]) & (c >> 31); x[offset + 496] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); x[offset + 752] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); x[offset + 240] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 512]) & (c >> 31); x[offset + 496] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); x[offset + 752] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1040]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1072]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1056]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1072]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1104]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1136]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1120]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1136]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1088]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1120]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1104]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1136]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1168]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1200]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1184]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1200]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1232]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1264]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1248]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1264]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1216]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1248]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1232]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1264]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1152]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1216]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1184]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1248]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1168]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1232]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1200]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1264]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1280]; t = (x[offset + 1280] ^ x[offset + 1296]) & (c >> 31); x[offset + 1280] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1312]; t = (x[offset + 1312] ^ x[offset + 1328]) & (c >> 31); x[offset + 1312] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1280]; t = (x[offset + 1280] ^ x[offset + 1312]) & (c >> 31); x[offset + 1280] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1328]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1280]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1312]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1296]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1328]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 1024]) & (c >> 31); x[offset + 0] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 1024]) & (c >> 31); x[offset + 512] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 1280]) & (c >> 31); x[offset + 256] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 1280]) & (c >> 31); x[offset + 768] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 512]) & (c >> 31); x[offset + 256] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 1024]) & (c >> 31); x[offset + 768] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 1152]) & (c >> 31); x[offset + 128] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 1152]) & (c >> 31); x[offset + 640] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 640]) & (c >> 31); x[offset + 384] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 1152]) & (c >> 31); x[offset + 896] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); x[offset + 128] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 512]) & (c >> 31); x[offset + 384] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); x[offset + 640] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 1024]) & (c >> 31); x[offset + 896] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 1088]) & (c >> 31); x[offset + 64] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 1088]) & (c >> 31); x[offset + 576] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 576]) & (c >> 31); x[offset + 320] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 1088]) & (c >> 31); x[offset + 832] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 1216]) & (c >> 31); x[offset + 192] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 1216]) & (c >> 31); x[offset + 704] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 704]) & (c >> 31); x[offset + 448] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 1216]) & (c >> 31); x[offset + 960] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); x[offset + 192] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 576]) & (c >> 31); x[offset + 448] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); x[offset + 704] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 1088]) & (c >> 31); x[offset + 960] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); x[offset + 192] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 512]) & (c >> 31); x[offset + 448] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); x[offset + 704] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 1024]) & (c >> 31); x[offset + 960] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 1056]) & (c >> 31); x[offset + 32] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 1056]) & (c >> 31); x[offset + 544] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 1312]) & (c >> 31); x[offset + 288] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 1312]) & (c >> 31); x[offset + 800] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 544]) & (c >> 31); x[offset + 288] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 1056]) & (c >> 31); x[offset + 800] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 1184]) & (c >> 31); x[offset + 160] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 1184]) & (c >> 31); x[offset + 672] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 672]) & (c >> 31); x[offset + 416] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 1184]) & (c >> 31); x[offset + 928] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); x[offset + 160] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 544]) & (c >> 31); x[offset + 416] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); x[offset + 672] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 1056]) & (c >> 31); x[offset + 928] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 1120]) & (c >> 31); x[offset + 96] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 1120]) & (c >> 31); x[offset + 608] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 608]) & (c >> 31); x[offset + 352] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 1120]) & (c >> 31); x[offset + 864] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 1248]) & (c >> 31); x[offset + 224] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 1248]) & (c >> 31); x[offset + 736] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 736]) & (c >> 31); x[offset + 480] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1248]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); x[offset + 224] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 608]) & (c >> 31); x[offset + 480] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); x[offset + 736] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1120]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); x[offset + 224] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 544]) & (c >> 31); x[offset + 480] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); x[offset + 736] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1056]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1312] ^= t;
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); x[offset + 224] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 512]) & (c >> 31); x[offset + 480] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); x[offset + 736] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1024]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 1040]) & (c >> 31); x[offset + 16] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 1040]) & (c >> 31); x[offset + 528] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 1296]) & (c >> 31); x[offset + 272] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 1296]) & (c >> 31); x[offset + 784] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 528]) & (c >> 31); x[offset + 272] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 1040]) & (c >> 31); x[offset + 784] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 1168]) & (c >> 31); x[offset + 144] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 1168]) & (c >> 31); x[offset + 656] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 656]) & (c >> 31); x[offset + 400] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 1168]) & (c >> 31); x[offset + 912] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); x[offset + 144] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 528]) & (c >> 31); x[offset + 400] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); x[offset + 656] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 1040]) & (c >> 31); x[offset + 912] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 1104]) & (c >> 31); x[offset + 80] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 1104]) & (c >> 31); x[offset + 592] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 592]) & (c >> 31); x[offset + 336] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 1104]) & (c >> 31); x[offset + 848] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 1232]) & (c >> 31); x[offset + 208] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 1232]) & (c >> 31); x[offset + 720] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 720]) & (c >> 31); x[offset + 464] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1232]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); x[offset + 208] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 592]) & (c >> 31); x[offset + 464] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); x[offset + 720] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1104]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); x[offset + 208] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 528]) & (c >> 31); x[offset + 464] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); x[offset + 720] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1040]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 1072]) & (c >> 31); x[offset + 48] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 1072]) & (c >> 31); x[offset + 560] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 1328]) & (c >> 31); x[offset + 304] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 1328]) & (c >> 31); x[offset + 816] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 560]) & (c >> 31); x[offset + 304] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 1072]) & (c >> 31); x[offset + 816] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 1200]) & (c >> 31); x[offset + 176] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 1200]) & (c >> 31); x[offset + 688] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 688]) & (c >> 31); x[offset + 432] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 1200]) & (c >> 31); x[offset + 944] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); x[offset + 176] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 560]) & (c >> 31); x[offset + 432] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); x[offset + 688] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 1072]) & (c >> 31); x[offset + 944] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 1136]) & (c >> 31); x[offset + 112] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 1136]) & (c >> 31); x[offset + 624] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 624]) & (c >> 31); x[offset + 368] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 1136]) & (c >> 31); x[offset + 880] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 1264]) & (c >> 31); x[offset + 240] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 1264]) & (c >> 31); x[offset + 752] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 752]) & (c >> 31); x[offset + 496] ^= t; x[offset + 752] ^= t;
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1264]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1264] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); x[offset + 240] ^= t; x[offset + 368] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 624]) & (c >> 31); x[offset + 496] ^= t; x[offset + 624] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); x[offset + 752] ^= t; x[offset + 880] ^= t;
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1136]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1136] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); x[offset + 240] ^= t; x[offset + 304] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 560]) & (c >> 31); x[offset + 496] ^= t; x[offset + 560] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); x[offset + 752] ^= t; x[offset + 816] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1072]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1072] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1328] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); x[offset + 240] ^= t; x[offset + 272] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 528]) & (c >> 31); x[offset + 496] ^= t; x[offset + 528] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); x[offset + 752] ^= t; x[offset + 784] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1040]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1040] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1296] ^= t;
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); x[offset + 240] ^= t; x[offset + 256] ^= t;
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 512]) & (c >> 31); x[offset + 496] ^= t; x[offset + 512] ^= t;
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); x[offset + 752] ^= t; x[offset + 768] ^= t;
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1024]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1024] ^= t;
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1280] ^= t;
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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);
}
@@ -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();
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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));
}
}
@@ -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);
}
}
}
@@ -0,0 +1,7 @@
/**
* Provides classes for communicating via the Noise protocol.
*
* Reference: http://noiseprotocol.org
*/
package com.bitchat.android.noise.southernstorm.protocol;
@@ -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
}
}
@@ -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<Byte, Int>()
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
}
}
}
@@ -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)
}
}
@@ -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<String, String>,
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,
@@ -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<Byte>()
// Make buffer accessible for direct manipulation when needed
val buffer: MutableList<Byte> 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
}
@@ -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) }
}
@@ -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) }
}
}