mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 03:45:21 +00:00
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:
@@ -1,321 +1,317 @@
|
|||||||
package com.bitchat.android.crypto
|
package com.bitchat.android.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.util.Log
|
||||||
import android.security.keystore.KeyGenParameterSpec
|
import com.bitchat.android.noise.NoiseEncryptionService
|
||||||
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 java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import javax.crypto.Cipher
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.crypto.KeyGenerator
|
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
import kotlin.experimental.and
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encryption service that's 100% compatible with iOS version
|
* Encryption service that now uses NoiseEncryptionService internally
|
||||||
* Uses the same cryptographic algorithms and key derivation
|
* 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) {
|
class EncryptionService(private val context: Context) {
|
||||||
|
|
||||||
// Key agreement keys for encryption
|
companion object {
|
||||||
private val privateKey: X25519PrivateKeyParameters
|
private const val TAG = "EncryptionService"
|
||||||
private val publicKey: X25519PublicKeyParameters
|
}
|
||||||
|
|
||||||
// Signing keys for authentication
|
// Core Noise encryption service
|
||||||
private val signingPrivateKey: Ed25519PrivateKeyParameters
|
private val noiseService: NoiseEncryptionService = NoiseEncryptionService(context)
|
||||||
private val signingPublicKey: Ed25519PublicKeyParameters
|
|
||||||
|
|
||||||
// Persistent identity for favorites (separate from ephemeral keys)
|
// Session tracking for established connections
|
||||||
private lateinit var identityKey: Ed25519PrivateKeyParameters
|
private val establishedSessions = ConcurrentHashMap<String, String>() // peerID -> fingerprint
|
||||||
private lateinit var identityPublicKey: Ed25519PublicKeyParameters
|
|
||||||
|
|
||||||
// Storage for peer keys
|
// Callbacks for UI state updates
|
||||||
private val peerPublicKeys = mutableMapOf<String, X25519PublicKeyParameters>()
|
var onSessionEstablished: ((String) -> Unit)? = null // peerID
|
||||||
private val peerSigningKeys = mutableMapOf<String, Ed25519PublicKeyParameters>()
|
var onSessionLost: ((String) -> Unit)? = null // peerID
|
||||||
private val peerIdentityKeys = mutableMapOf<String, Ed25519PublicKeyParameters>()
|
var onHandshakeRequired: ((String) -> Unit)? = null // peerID
|
||||||
private val sharedSecrets = mutableMapOf<String, ByteArray>()
|
|
||||||
|
|
||||||
private val prefs: SharedPreferences = context.getSharedPreferences("bitchat_crypto", Context.MODE_PRIVATE)
|
|
||||||
private val secureRandom = SecureRandom()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Generate ephemeral key pairs for this session
|
// Set up NoiseEncryptionService callbacks
|
||||||
val x25519Generator = X25519KeyPairGenerator()
|
noiseService.onPeerAuthenticated = { peerID, fingerprint ->
|
||||||
x25519Generator.init(X25519KeyGenerationParameters(secureRandom))
|
Log.d(TAG, "✅ Noise session established with $peerID, fingerprint: ${fingerprint.take(16)}...")
|
||||||
val x25519KeyPair = x25519Generator.generateKeyPair()
|
establishedSessions[peerID] = fingerprint
|
||||||
privateKey = x25519KeyPair.private as X25519PrivateKeyParameters
|
onSessionEstablished?.invoke(peerID)
|
||||||
publicKey = x25519KeyPair.public as X25519PublicKeyParameters
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
identityPublicKey = Ed25519PublicKeyParameters(identityKey.encoded, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveIdentityKey() {
|
noiseService.onHandshakeRequired = { peerID ->
|
||||||
val keyBytes = android.util.Base64.encodeToString(identityKey.encoded, android.util.Base64.DEFAULT)
|
Log.d(TAG, "🤝 Handshake required for $peerID")
|
||||||
prefs.edit().putString("identity_key", keyBytes).apply()
|
onHandshakeRequired?.invoke(peerID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public API (Maintains backward compatibility)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create combined public key data for exchange - exactly same format as iOS
|
* Get our static public key data (32 bytes for Noise)
|
||||||
* 96 bytes total: 32 (X25519) + 32 (Ed25519 signing) + 32 (Ed25519 identity)
|
* This replaces the old 96-byte combined key format
|
||||||
*/
|
*/
|
||||||
fun getCombinedPublicKeyData(): ByteArray {
|
fun getCombinedPublicKeyData(): ByteArray {
|
||||||
val combined = ByteArray(96)
|
return noiseService.getStaticPublicKeyData()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
@Throws(Exception::class)
|
||||||
fun addPeerPublicKey(peerID: String, publicKeyData: ByteArray) {
|
fun addPeerPublicKey(peerID: String, publicKeyData: ByteArray) {
|
||||||
if (publicKeyData.size != 96) {
|
Log.d(TAG, "Legacy addPeerPublicKey called for $peerID with ${publicKeyData.size} bytes")
|
||||||
throw Exception("Invalid public key data size: ${publicKeyData.size}, expected 96")
|
|
||||||
|
// 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? {
|
fun getPeerIdentityKey(peerID: String): ByteArray? {
|
||||||
return peerIdentityKeys[peerID]?.encoded
|
val fingerprint = getPeerFingerprint(peerID) ?: return null
|
||||||
|
return fingerprint.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear persistent identity (for panic mode)
|
* Clear persistent identity (for panic mode)
|
||||||
*/
|
*/
|
||||||
fun clearPersistentIdentity() {
|
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)
|
@Throws(Exception::class)
|
||||||
fun encrypt(data: ByteArray, peerID: String): ByteArray {
|
fun encrypt(data: ByteArray, peerID: String): ByteArray {
|
||||||
val symmetricKey = sharedSecrets[peerID]
|
val encrypted = noiseService.encrypt(data, peerID)
|
||||||
?: throw Exception("No shared secret for peer $peerID")
|
if (encrypted == null) {
|
||||||
|
throw Exception("Failed to encrypt for $peerID")
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
}
|
||||||
val keySpec = SecretKeySpec(symmetricKey, "AES")
|
return encrypted
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt data from a specific peer
|
* Decrypt data from a specific peer using Noise transport encryption
|
||||||
*/
|
*/
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun decrypt(data: ByteArray, peerID: String): ByteArray {
|
fun decrypt(data: ByteArray, peerID: String): ByteArray {
|
||||||
val symmetricKey = sharedSecrets[peerID]
|
val decrypted = noiseService.decrypt(data, peerID)
|
||||||
?: throw Exception("No shared secret for peer $peerID")
|
if (decrypted == null) {
|
||||||
|
throw Exception("Failed to decrypt from $peerID")
|
||||||
if (data.size < 16) { // 12 bytes IV + 16 bytes tag minimum for GCM
|
|
||||||
throw Exception("Invalid encrypted data size")
|
|
||||||
}
|
}
|
||||||
|
return decrypted
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
@Throws(Exception::class)
|
||||||
fun sign(data: ByteArray): ByteArray {
|
fun sign(data: ByteArray): ByteArray {
|
||||||
val signer = Ed25519Signer()
|
// Note: In Noise protocol, authentication is built into the handshake
|
||||||
signer.init(true, signingPrivateKey)
|
// For compatibility, we return empty signature
|
||||||
signer.update(data, 0, data.size)
|
return ByteArray(0)
|
||||||
return signer.generateSignature()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
@Throws(Exception::class)
|
||||||
fun verify(signature: ByteArray, data: ByteArray, peerID: String): Boolean {
|
fun verify(signature: ByteArray, data: ByteArray, peerID: String): Boolean {
|
||||||
val verifyingKey = peerSigningKeys[peerID]
|
// Note: In Noise protocol, authentication is built into the transport
|
||||||
?: throw Exception("No signing key for peer $peerID")
|
// Messages are authenticated automatically when decrypted
|
||||||
|
return hasEstablishedSession(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
val signer = Ed25519Signer()
|
// MARK: - Noise Protocol Interface
|
||||||
signer.init(false, verifyingKey)
|
|
||||||
signer.update(data, 0, data.size)
|
/**
|
||||||
return signer.verifySignature(signature)
|
* 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 {
|
fun shouldShowEncryptionIcon(peerID: String): Boolean {
|
||||||
// Extract
|
return hasEstablishedSession(peerID)
|
||||||
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
|
* Get peer fingerprint for favorites/blocking
|
||||||
*/
|
*/
|
||||||
object MessagePadding {
|
fun getPeerFingerprint(peerID: String): String? {
|
||||||
// Standard block sizes for padding
|
return noiseService.getPeerFingerprint(peerID)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove padding from data
|
* Get current peer ID for a fingerprint (for peer ID rotation)
|
||||||
*/
|
*/
|
||||||
fun unpad(data: ByteArray): ByteArray {
|
fun getCurrentPeerID(fingerprint: String): String? {
|
||||||
if (data.isEmpty()) return data
|
return noiseService.getPeerID(fingerprint)
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find optimal block size for data
|
* Initiate a Noise handshake with a peer
|
||||||
*/
|
*/
|
||||||
fun optimalBlockSize(dataSize: Int): Int {
|
fun initiateHandshake(peerID: String): ByteArray? {
|
||||||
// Account for encryption overhead (~16 bytes for AES-GCM tag)
|
Log.d(TAG, "🤝 Initiating Noise handshake with $peerID")
|
||||||
val totalSize = dataSize + 16
|
return noiseService.initiateHandshake(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
// Find smallest block that fits
|
/**
|
||||||
for (blockSize in blockSizes) {
|
* Process an incoming handshake message
|
||||||
if (totalSize <= blockSize) {
|
*/
|
||||||
return blockSize
|
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)}...")
|
||||||
|
|
||||||
|
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
|
appendLine("")
|
||||||
return dataSize
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,16 @@ class BluetoothConnectionManager(
|
|||||||
try {
|
try {
|
||||||
isActive = true
|
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
|
// Start all component managers
|
||||||
connectionScope.launch {
|
connectionScope.launch {
|
||||||
// Start connection tracker first
|
// Start connection tracker first
|
||||||
@@ -209,7 +219,7 @@ class BluetoothConnectionManager(
|
|||||||
Log.i(TAG, "Power mode changed to: $newMode")
|
Log.i(TAG, "Power mode changed to: $newMode")
|
||||||
|
|
||||||
connectionScope.launch {
|
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()
|
val wasUsingDutyCycle = powerManager.shouldUseDutyCycle()
|
||||||
|
|
||||||
// Update advertising with new power settings
|
// Update advertising with new power settings
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class BluetoothConnectionTracker(
|
|||||||
* Add a device connection
|
* Add a device connection
|
||||||
*/
|
*/
|
||||||
fun addDeviceConnection(deviceAddress: String, deviceConn: DeviceConnection) {
|
fun addDeviceConnection(deviceAddress: String, deviceConn: DeviceConnection) {
|
||||||
|
Log.d(TAG, "Tracker: Adding device connection for $deviceAddress")
|
||||||
connectedDevices[deviceAddress] = deviceConn
|
connectedDevices[deviceAddress] = deviceConn
|
||||||
pendingConnections.remove(deviceAddress)
|
pendingConnections.remove(deviceAddress)
|
||||||
}
|
}
|
||||||
@@ -180,16 +181,22 @@ class BluetoothConnectionTracker(
|
|||||||
* Add a pending connection attempt
|
* Add a pending connection attempt
|
||||||
*/
|
*/
|
||||||
fun addPendingConnection(deviceAddress: String): Boolean {
|
fun addPendingConnection(deviceAddress: String): Boolean {
|
||||||
|
Log.d(TAG, "Tracker: Adding pending connection for $deviceAddress")
|
||||||
synchronized(pendingConnections) {
|
synchronized(pendingConnections) {
|
||||||
// Double-check inside synchronized block
|
// Double-check inside synchronized block
|
||||||
val currentAttempt = pendingConnections[deviceAddress]
|
val currentAttempt = pendingConnections[deviceAddress]
|
||||||
if (currentAttempt != null && !currentAttempt.isExpired() && !currentAttempt.shouldRetry()) {
|
if (currentAttempt != null && !currentAttempt.isExpired() && !currentAttempt.shouldRetry()) {
|
||||||
|
Log.d(TAG, "Tracker: Connection attempt already in progress for $deviceAddress")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (currentAttempt != null) {
|
||||||
|
Log.d(TAG, "Tracker: current attempt: $currentAttempt")
|
||||||
|
}
|
||||||
|
|
||||||
// Update connection attempt atomically
|
// Update connection attempt atomically
|
||||||
val attempts = (currentAttempt?.attempts ?: 0) + 1
|
val attempts = (currentAttempt?.attempts ?: 0) + 1
|
||||||
pendingConnections[deviceAddress] = ConnectionAttempt(attempts)
|
pendingConnections[deviceAddress] = ConnectionAttempt(attempts)
|
||||||
|
Log.d(TAG, "Tracker: Added pending connection for $deviceAddress (attempts: $attempts)")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,8 +249,6 @@ class BluetoothConnectionTracker(
|
|||||||
subscribedDevices.removeAll { it.address == deviceAddress }
|
subscribedDevices.removeAll { it.address == deviceAddress }
|
||||||
addressPeerMap.remove(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)
|
pendingConnections.remove(deviceAddress)
|
||||||
Log.d(TAG, "Cleaned up device connection for $deviceAddress")
|
Log.d(TAG, "Cleaned up device connection for $deviceAddress")
|
||||||
}
|
}
|
||||||
@@ -318,7 +323,7 @@ class BluetoothConnectionTracker(
|
|||||||
appendLine("Connected Devices: ${connectedDevices.size} / ${powerManager.getMaxConnections()}")
|
appendLine("Connected Devices: ${connectedDevices.size} / ${powerManager.getMaxConnections()}")
|
||||||
connectedDevices.forEach { (address, deviceConn) ->
|
connectedDevices.forEach { (address, deviceConn) ->
|
||||||
val age = (System.currentTimeMillis() - deviceConn.connectedAt) / 1000
|
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()
|
||||||
appendLine("Subscribed Devices (server mode): ${subscribedDevices.size}")
|
appendLine("Subscribed Devices (server mode): ${subscribedDevices.size}")
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class BluetoothGattClientManager(
|
|||||||
// Scan management
|
// Scan management
|
||||||
private var scanCallback: ScanCallback? = null
|
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 lastScanStartTime = 0L
|
||||||
private var lastScanStopTime = 0L
|
private var lastScanStopTime = 0L
|
||||||
private var isCurrentlyScanning = false
|
private var isCurrentlyScanning = false
|
||||||
@@ -160,7 +160,7 @@ class BluetoothGattClientManager(
|
|||||||
private fun startScanning() {
|
private fun startScanning() {
|
||||||
if (!permissionManager.hasBluetoothPermissions() || bleScanner == null || !isActive) return
|
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()
|
val currentTime = System.currentTimeMillis()
|
||||||
if (isCurrentlyScanning) {
|
if (isCurrentlyScanning) {
|
||||||
Log.d(TAG, "Scan already in progress, skipping start request")
|
Log.d(TAG, "Scan already in progress, skipping start request")
|
||||||
@@ -192,6 +192,7 @@ class BluetoothGattClientManager(
|
|||||||
|
|
||||||
scanCallback = object : ScanCallback() {
|
scanCallback = object : ScanCallback() {
|
||||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||||
|
// Log.d(TAG, "Scan result received: ${result.device.address}")
|
||||||
handleScanResult(result)
|
handleScanResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +278,8 @@ class BluetoothGattClientManager(
|
|||||||
return
|
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)
|
// Store RSSI from scan results for later use (especially for server connections)
|
||||||
connectionTracker.updateScanRSSI(deviceAddress, rssi)
|
connectionTracker.updateScanRSSI(deviceAddress, rssi)
|
||||||
|
|
||||||
@@ -337,9 +340,8 @@ class BluetoothGattClientManager(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Client: Cleanly disconnected from $deviceAddress")
|
Log.d(TAG, "Client: Cleanly disconnected from $deviceAddress")
|
||||||
}
|
|
||||||
|
|
||||||
connectionTracker.cleanupDeviceConnection(deviceAddress)
|
connectionTracker.cleanupDeviceConnection(deviceAddress)
|
||||||
|
}
|
||||||
|
|
||||||
connectionScope.launch {
|
connectionScope.launch {
|
||||||
delay(500) // CLEANUP_DELAY
|
delay(500) // CLEANUP_DELAY
|
||||||
@@ -372,7 +374,7 @@ class BluetoothGattClientManager(
|
|||||||
gatt.discoverServices()
|
gatt.discoverServices()
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "MTU negotiation failed for $deviceAddress with status: $status. Disconnecting.")
|
Log.w(TAG, "MTU negotiation failed for $deviceAddress with status: $status. Disconnecting.")
|
||||||
connectionTracker.removePendingConnection(deviceAddress)
|
//connectionTracker.removePendingConnection(deviceAddress)
|
||||||
gatt.disconnect()
|
gatt.disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,10 +422,10 @@ class BluetoothGattClientManager(
|
|||||||
|
|
||||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||||
val value = characteristic.value
|
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)
|
val packet = BitchatPacket.fromBinaryData(value)
|
||||||
if (packet != null) {
|
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")
|
Log.d(TAG, "Client: Parsed packet type ${packet.type} from $peerID")
|
||||||
delegate?.onPacketReceived(packet, peerID, gatt.device)
|
delegate?.onPacketReceived(packet, peerID, gatt.device)
|
||||||
} else {
|
} else {
|
||||||
@@ -453,13 +455,15 @@ class BluetoothGattClientManager(
|
|||||||
val gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
val gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
||||||
if (gatt == null) {
|
if (gatt == null) {
|
||||||
Log.e(TAG, "connectGatt returned null for $deviceAddress")
|
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 {
|
} else {
|
||||||
Log.d(TAG, "Client: GATT connection initiated successfully for $deviceAddress")
|
Log.d(TAG, "Client: GATT connection initiated successfully for $deviceAddress")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Client: Exception connecting to $deviceAddress: ${e.message}")
|
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")
|
Log.i(TAG, "Server: Received packet from ${device.address}, size: ${value.size} bytes")
|
||||||
val packet = BitchatPacket.fromBinaryData(value)
|
val packet = BitchatPacket.fromBinaryData(value)
|
||||||
if (packet != null) {
|
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")
|
Log.d(TAG, "Server: Parsed packet type ${packet.type} from $peerID")
|
||||||
delegate?.onPacketReceived(packet, peerID, device)
|
delegate?.onPacketReceived(packet, peerID, device)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,16 +3,19 @@ package com.bitchat.android.mesh
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.bitchat.android.crypto.EncryptionService
|
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.BitchatMessage
|
||||||
import com.bitchat.android.model.RoutedPacket
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import com.bitchat.android.model.DeliveryAck
|
import com.bitchat.android.model.DeliveryAck
|
||||||
import com.bitchat.android.model.ReadReceipt
|
import com.bitchat.android.model.ReadReceipt
|
||||||
|
import com.bitchat.android.model.NoiseIdentityAnnouncement
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
import com.bitchat.android.protocol.SpecialRecipients
|
import com.bitchat.android.protocol.SpecialRecipients
|
||||||
|
import com.bitchat.android.util.toHexString
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.sign
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +62,10 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
setupDelegates()
|
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)
|
delay(100)
|
||||||
sendAnnouncementToPeer(peerID)
|
sendAnnouncementToPeer(peerID)
|
||||||
|
|
||||||
delay(500)
|
delay(1000)
|
||||||
storeForwardManager.sendCachedMessages(peerID)
|
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
|
// StoreForwardManager delegates
|
||||||
@@ -189,11 +210,63 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
return securityManager.decryptFromPeer(encryptedData, senderPeerID)
|
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
|
// Message operations
|
||||||
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
|
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
|
||||||
return delegate?.decryptChannelMessage(encryptedContent, channel)
|
return delegate?.decryptChannelMessage(encryptedContent, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) {
|
||||||
|
this@BluetoothMeshService.sendDeliveryAck(message, senderPeerID)
|
||||||
|
}
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
override fun onMessageReceived(message: BitchatMessage) {
|
override fun onMessageReceived(message: BitchatMessage) {
|
||||||
delegate?.didReceiveMessage(message)
|
delegate?.didReceiveMessage(message)
|
||||||
@@ -226,8 +299,16 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
peerManager.updatePeerLastSeen(peerID)
|
peerManager.updatePeerLastSeen(peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleKeyExchange(routed: RoutedPacket): Boolean {
|
override fun handleNoiseHandshake(routed: RoutedPacket, step: Int): Boolean {
|
||||||
return runBlocking { securityManager.handleKeyExchange(routed) }
|
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) {
|
override fun handleAnnounce(routed: RoutedPacket) {
|
||||||
@@ -274,6 +355,11 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) {
|
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
|
// Send key exchange to newly connected device
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
delay(100) // Ensure connection is stable
|
delay(100) // Ensure connection is stable
|
||||||
@@ -304,11 +390,6 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
if (connectionManager.startServices()) {
|
if (connectionManager.startServices()) {
|
||||||
isActive = true
|
isActive = true
|
||||||
// Send initial announcements after services are ready
|
|
||||||
serviceScope.launch {
|
|
||||||
delay(1000)
|
|
||||||
sendBroadcastAnnounce()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Failed to start Bluetooth services")
|
Log.e(TAG, "Failed to start Bluetooth services")
|
||||||
}
|
}
|
||||||
@@ -365,16 +446,17 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
message.toBinaryPayload()?.let { messageData ->
|
message.toBinaryPayload()?.let { messageData ->
|
||||||
// Sign the message
|
// Sign the message: TODO: NOT SIGNED
|
||||||
val signature = securityManager.signPacket(messageData)
|
// val signature = securityManager.signPacket(messageData)
|
||||||
|
|
||||||
val packet = BitchatPacket(
|
val packet = BitchatPacket(
|
||||||
|
version = 1u,
|
||||||
type = MessageType.MESSAGE.value,
|
type = MessageType.MESSAGE.value,
|
||||||
senderID = myPeerID.toByteArray(),
|
senderID = hexStringToByteArray(myPeerID),
|
||||||
recipientID = SpecialRecipients.BROADCAST,
|
recipientID = SpecialRecipients.BROADCAST,
|
||||||
timestamp = System.currentTimeMillis().toULong(),
|
timestamp = System.currentTimeMillis().toULong(),
|
||||||
payload = messageData,
|
payload = messageData,
|
||||||
signature = signature,
|
signature = null,
|
||||||
ttl = MAX_TTL
|
ttl = MAX_TTL
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -391,7 +473,6 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) {
|
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) {
|
||||||
if (content.isEmpty() || recipientPeerID.isEmpty() || recipientNickname.isEmpty()) return
|
if (content.isEmpty() || recipientPeerID.isEmpty() || recipientNickname.isEmpty()) return
|
||||||
|
|
||||||
serviceScope.launch {
|
|
||||||
val nickname = delegate?.getNickname() ?: myPeerID
|
val nickname = delegate?.getNickname() ?: myPeerID
|
||||||
|
|
||||||
val message = BitchatMessage(
|
val message = BitchatMessage(
|
||||||
@@ -407,40 +488,116 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
message.toBinaryPayload()?.let { messageData ->
|
message.toBinaryPayload()?.let { messageData ->
|
||||||
try {
|
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) {
|
// Create inner packet with the padded message data
|
||||||
// Sign
|
val innerPacket = BitchatPacket(
|
||||||
val signature = securityManager.signPacket(encryptedPayload)
|
|
||||||
|
|
||||||
val packet = BitchatPacket(
|
|
||||||
type = MessageType.MESSAGE.value,
|
type = MessageType.MESSAGE.value,
|
||||||
senderID = myPeerID.toByteArray(),
|
senderID = hexStringToByteArray(myPeerID),
|
||||||
recipientID = recipientPeerID.toByteArray(),
|
recipientID = hexStringToByteArray(recipientPeerID),
|
||||||
timestamp = System.currentTimeMillis().toULong(),
|
timestamp = System.currentTimeMillis().toULong(),
|
||||||
payload = encryptedPayload,
|
payload = messageData,
|
||||||
signature = signature,
|
signature = null,
|
||||||
ttl = MAX_TTL
|
ttl = MAX_TTL
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache for offline favorites
|
// Cache for offline favorites
|
||||||
if (storeForwardManager.shouldCacheForPeer(recipientPeerID)) {
|
if (storeForwardManager.shouldCacheForPeer(recipientPeerID)) {
|
||||||
storeForwardManager.cacheMessage(packet, messageID ?: message.id)
|
storeForwardManager.cacheMessage(innerPacket, messageID ?: message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send with delay
|
// Use the new encrypt and broadcast function
|
||||||
delay(Random.nextLong(50, 500))
|
encryptAndBroadcastNoisePacket(innerPacket, recipientPeerID)
|
||||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to send private message: ${e.message}")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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() {
|
private fun sendKeyExchangeToDevice() {
|
||||||
val publicKeyData = securityManager.getCombinedPublicKeyData()
|
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(
|
val packet = BitchatPacket(
|
||||||
type = MessageType.KEY_EXCHANGE.value,
|
version = 1u,
|
||||||
ttl = 1u,
|
type = MessageType.NOISE_IDENTITY_ANNOUNCE.value,
|
||||||
senderID = myPeerID,
|
senderID = hexStringToByteArray(myPeerID),
|
||||||
payload = publicKeyData
|
recipientID = SpecialRecipients.BROADCAST,
|
||||||
|
timestamp = System.currentTimeMillis().toULong(),
|
||||||
|
payload = announcementData,
|
||||||
|
ttl = 1u
|
||||||
)
|
)
|
||||||
|
|
||||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
Log.d(TAG, "Sent key exchange")
|
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()
|
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
|
* 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 {
|
private fun generateCompatiblePeerID(): String {
|
||||||
val randomBytes = ByteArray(4)
|
val randomBytes = ByteArray(8) // 8 bytes = 16 hex characters (like iOS)
|
||||||
Random.nextBytes(randomBytes)
|
Random.nextBytes(randomBytes)
|
||||||
return randomBytes.joinToString("") { "%02x".format(it) }
|
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.protocol.SpecialRecipients
|
||||||
import com.bitchat.android.model.RoutedPacket
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
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(
|
class BluetoothPacketBroadcaster(
|
||||||
private val connectionScope: CoroutineScope,
|
private val connectionScope: CoroutineScope,
|
||||||
@@ -25,13 +34,39 @@ class BluetoothPacketBroadcaster(
|
|||||||
private const val CLEANUP_DELAY = 500L
|
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(
|
fun broadcastPacket(
|
||||||
routed: RoutedPacket,
|
routed: RoutedPacket,
|
||||||
gattServer: BluetoothGattServer?,
|
gattServer: BluetoothGattServer?,
|
||||||
characteristic: BluetoothGattCharacteristic?
|
characteristic: BluetoothGattCharacteristic?
|
||||||
) {
|
) {
|
||||||
val packet = routed.packet
|
val packet = routed.packet
|
||||||
val data = packet.toBinaryData() ?: return
|
|
||||||
// Check if we need to fragment
|
// Check if we need to fragment
|
||||||
if (fragmentManager != null) {
|
if (fragmentManager != null) {
|
||||||
val fragments = fragmentManager.createFragments(packet)
|
val fragments = fragmentManager.createFragments(packet)
|
||||||
@@ -41,7 +76,7 @@ class BluetoothPacketBroadcaster(
|
|||||||
fragments.forEach { fragment ->
|
fragments.forEach { fragment ->
|
||||||
broadcastSinglePacket(RoutedPacket(fragment), gattServer, characteristic)
|
broadcastSinglePacket(RoutedPacket(fragment), gattServer, characteristic)
|
||||||
// 20ms delay between fragments (matching iOS/Rust)
|
// 20ms delay between fragments (matching iOS/Rust)
|
||||||
delay(20)
|
delay(200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
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(
|
fun broadcastSinglePacket(
|
||||||
routed: RoutedPacket,
|
routed: RoutedPacket,
|
||||||
gattServer: BluetoothGattServer?,
|
gattServer: BluetoothGattServer?,
|
||||||
characteristic: BluetoothGattCharacteristic?
|
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 packet = routed.packet
|
||||||
val data = packet.toBinaryData() ?: return
|
val data = packet.toBinaryData() ?: return
|
||||||
@@ -96,7 +151,7 @@ class BluetoothPacketBroadcaster(
|
|||||||
val subscribedDevices = connectionTracker.getSubscribedDevices()
|
val subscribedDevices = connectionTracker.getSubscribedDevices()
|
||||||
val connectedDevices = connectionTracker.getConnectedDevices()
|
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", "")
|
val senderID = String(packet.senderID).replace("\u0000", "")
|
||||||
|
|
||||||
@@ -177,4 +232,31 @@ class BluetoothPacketBroadcaster(
|
|||||||
false
|
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 {
|
companion object {
|
||||||
private const val TAG = "FragmentManager"
|
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 FRAGMENT_TIMEOUT = 30000L // 30 seconds
|
||||||
private const val CLEANUP_INTERVAL = 10000L // 10 seconds
|
private const val CLEANUP_INTERVAL = 10000L // 10 seconds
|
||||||
}
|
}
|
||||||
@@ -39,7 +40,7 @@ class FragmentManager {
|
|||||||
fun createFragments(packet: BitchatPacket): List<BitchatPacket> {
|
fun createFragments(packet: BitchatPacket): List<BitchatPacket> {
|
||||||
val data = packet.toBinaryData() ?: return emptyList()
|
val data = packet.toBinaryData() ?: return emptyList()
|
||||||
|
|
||||||
if (data.size <= MAX_FRAGMENT_SIZE) {
|
if (data.size <= FRAGMENT_SIZE_THRESHOLD) {
|
||||||
return listOf(packet) // No fragmentation needed
|
return listOf(packet) // No fragmentation needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package com.bitchat.android.mesh
|
package com.bitchat.android.mesh
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.bitchat.android.crypto.MessagePadding
|
|
||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
import com.bitchat.android.model.DeliveryAck
|
import com.bitchat.android.model.DeliveryAck
|
||||||
|
import com.bitchat.android.model.NoiseIdentityAnnouncement
|
||||||
import com.bitchat.android.model.ReadReceipt
|
import com.bitchat.android.model.ReadReceipt
|
||||||
import com.bitchat.android.model.RoutedPacket
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
|
import com.bitchat.android.util.toHexString
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -25,9 +26,142 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
// Delegate for callbacks
|
// Delegate for callbacks
|
||||||
var delegate: MessageHandlerDelegate? = null
|
var delegate: MessageHandlerDelegate? = null
|
||||||
|
|
||||||
|
// Reference to PacketProcessor for recursive packet handling
|
||||||
|
var packetProcessor: PacketProcessor? = null
|
||||||
|
|
||||||
// Coroutines
|
// Coroutines
|
||||||
private val handlerScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
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
|
* Handle announce message
|
||||||
*/
|
*/
|
||||||
@@ -62,11 +196,14 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
if (peerID == myPeerID) return
|
if (peerID == myPeerID) return
|
||||||
|
|
||||||
val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) }
|
val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) }
|
||||||
|
var recipientIDString = ""
|
||||||
|
if (recipientID != null) {
|
||||||
|
recipientIDString = recipientID.toHexString()
|
||||||
|
}
|
||||||
if (recipientID == null) {
|
if (recipientID == null) {
|
||||||
// BROADCAST MESSAGE
|
// BROADCAST MESSAGE
|
||||||
handleBroadcastMessage(routed)
|
handleBroadcastMessage(routed)
|
||||||
} else if (String(recipientID).replace("\u0000", "") == myPeerID) {
|
} else if (recipientID.toHexString() == myPeerID) {
|
||||||
// PRIVATE MESSAGE FOR US
|
// PRIVATE MESSAGE FOR US
|
||||||
handlePrivateMessage(packet, peerID)
|
handlePrivateMessage(packet, peerID)
|
||||||
} else if (packet.ttl > 0u) {
|
} 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) {
|
private suspend fun handlePrivateMessage(packet: BitchatPacket, peerID: String) {
|
||||||
try {
|
try {
|
||||||
@@ -130,17 +267,8 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
return
|
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
|
// Parse message
|
||||||
val message = BitchatMessage.fromBinaryPayload(unpaddedData)
|
val message = BitchatMessage.fromBinaryPayload(packet.payload)
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
// Check for cover traffic (dummy messages)
|
// Check for cover traffic (dummy messages)
|
||||||
if (message.content.startsWith("☂DUMMY☂")) {
|
if (message.content.startsWith("☂DUMMY☂")) {
|
||||||
@@ -149,17 +277,10 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delegate?.updatePeerNickname(peerID, message.sender)
|
delegate?.updatePeerNickname(peerID, message.sender)
|
||||||
|
delegate?.onMessageReceived(message)
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Send delivery ACK
|
// Send delivery ACK
|
||||||
sendDeliveryAck(message, peerID)
|
// delegate?.sendDeliveryAck(message, peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} 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
|
* 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
|
* Shutdown the handler
|
||||||
*/
|
*/
|
||||||
@@ -349,8 +455,15 @@ interface MessageHandlerDelegate {
|
|||||||
fun encryptForPeer(data: ByteArray, recipientPeerID: String): ByteArray?
|
fun encryptForPeer(data: ByteArray, recipientPeerID: String): ByteArray?
|
||||||
fun decryptFromPeer(encryptedData: ByteArray, senderPeerID: 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
|
// Message operations
|
||||||
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
|
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
|
||||||
|
fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String)
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
fun onMessageReceived(message: BitchatMessage)
|
fun onMessageReceived(message: BitchatMessage)
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import com.bitchat.android.protocol.BitchatPacket
|
|||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
import com.bitchat.android.model.RoutedPacket
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.channels.actor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes incoming packets and routes them to appropriate handlers
|
* 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) {
|
class PacketProcessor(private val myPeerID: String) {
|
||||||
|
|
||||||
@@ -22,14 +27,51 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
// Coroutines
|
// Coroutines
|
||||||
private val processorScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
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
|
* Process received packet - main entry point for all incoming packets
|
||||||
|
* SURGICAL FIX: Route to per-peer actor for serialized processing
|
||||||
*/
|
*/
|
||||||
fun processPacket(routed: RoutedPacket) {
|
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 {
|
processorScope.launch {
|
||||||
|
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)
|
handleReceivedPacket(routed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle received packet - core protocol logic (exact same as iOS)
|
* Handle received packet - core protocol logic (exact same as iOS)
|
||||||
@@ -44,14 +86,14 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last seen timestamp
|
var validPacket = true
|
||||||
delegate?.updatePeerLastSeen(peerID)
|
|
||||||
|
|
||||||
Log.d(TAG, "Processing packet type ${packet.type} from $peerID")
|
Log.d(TAG, "Processing packet type ${packet.type} from $peerID")
|
||||||
|
val DEBUG_MESSAGE_TYPE = MessageType.fromValue(packet.type)
|
||||||
// Process based on message type (exact same logic as iOS)
|
|
||||||
when (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.ANNOUNCE -> handleAnnounce(routed)
|
||||||
MessageType.MESSAGE -> handleMessage(routed)
|
MessageType.MESSAGE -> handleMessage(routed)
|
||||||
MessageType.LEAVE -> handleLeave(routed)
|
MessageType.LEAVE -> handleLeave(routed)
|
||||||
@@ -61,22 +103,27 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
MessageType.DELIVERY_ACK -> handleDeliveryAck(routed)
|
MessageType.DELIVERY_ACK -> handleDeliveryAck(routed)
|
||||||
MessageType.READ_RECEIPT -> handleReadReceipt(routed)
|
MessageType.READ_RECEIPT -> handleReadReceipt(routed)
|
||||||
else -> {
|
else -> {
|
||||||
|
validPacket = false
|
||||||
Log.w(TAG, "Unknown message type: ${packet.type}")
|
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"
|
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) {
|
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)
|
delay(100)
|
||||||
delegate?.sendAnnouncementToPeer(peerID)
|
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
|
* Handle announce message
|
||||||
*/
|
*/
|
||||||
@@ -151,15 +216,34 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
return buildString {
|
return buildString {
|
||||||
appendLine("=== Packet Processor Debug Info ===")
|
appendLine("=== Packet Processor Debug Info ===")
|
||||||
appendLine("Processor Scope Active: ${processorScope.isActive}")
|
appendLine("Processor Scope Active: ${processorScope.isActive}")
|
||||||
|
appendLine("Active Peer Actors: ${actors.size}")
|
||||||
appendLine("My Peer ID: $myPeerID")
|
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() {
|
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()
|
processorScope.cancel()
|
||||||
|
|
||||||
|
Log.d(TAG, "PacketProcessor shutdown complete")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +258,9 @@ interface PacketProcessorDelegate {
|
|||||||
fun updatePeerLastSeen(peerID: String)
|
fun updatePeerLastSeen(peerID: String)
|
||||||
|
|
||||||
// Message type handlers
|
// 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 handleAnnounce(routed: RoutedPacket)
|
||||||
fun handleMessage(routed: RoutedPacket)
|
fun handleMessage(routed: RoutedPacket)
|
||||||
fun handleLeave(routed: RoutedPacket)
|
fun handleLeave(routed: RoutedPacket)
|
||||||
|
|||||||
@@ -113,10 +113,6 @@ class PowerManager(private val context: Context) {
|
|||||||
* Get scan settings optimized for current power mode
|
* Get scan settings optimized for current power mode
|
||||||
*/
|
*/
|
||||||
fun getScanSettings(): ScanSettings {
|
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()
|
val builder = ScanSettings.Builder()
|
||||||
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
.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.BitchatPacket
|
||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
import com.bitchat.android.model.RoutedPacket
|
import com.bitchat.android.model.RoutedPacket
|
||||||
|
import com.bitchat.android.util.toHexString
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.mutableSetOf
|
import kotlin.collections.mutableSetOf
|
||||||
@@ -86,6 +87,65 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
|||||||
return true
|
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
|
* Handle key exchange packet
|
||||||
*/
|
*/
|
||||||
@@ -209,9 +269,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
|||||||
* Check if we have encryption keys for a peer
|
* Check if we have encryption keys for a peer
|
||||||
*/
|
*/
|
||||||
fun hasKeysForPeer(peerID: String): Boolean {
|
fun hasKeysForPeer(peerID: String): Boolean {
|
||||||
// This would need to be implemented in EncryptionService
|
return encryptionService.hasEstablishedSession(peerID)
|
||||||
// For now, we'll assume we have keys if we processed a key exchange
|
|
||||||
return processedKeyExchanges.any { it.startsWith("$peerID-") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -320,4 +378,5 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
|||||||
*/
|
*/
|
||||||
interface SecurityManagerDelegate {
|
interface SecurityManagerDelegate {
|
||||||
fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?)
|
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) {
|
fun cacheMessage(packet: BitchatPacket, messageID: String) {
|
||||||
// Skip certain message types (same as iOS)
|
// 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.ANNOUNCE.value ||
|
||||||
packet.type == MessageType.LEAVE.value) {
|
packet.type == MessageType.LEAVE.value) {
|
||||||
Log.d(TAG, "Skipping cache for message type: ${packet.type}")
|
Log.d(TAG, "Skipping cache for message type: ${packet.type}")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.bitchat.android.model
|
package com.bitchat.android.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
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")
|
||||||
|
}
|
||||||
+243
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+233
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
+244
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+265
@@ -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;
|
||||||
+265
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+335
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+290
@@ -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);
|
||||||
|
}
|
||||||
+106
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+156
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+156
@@ -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();
|
||||||
|
}
|
||||||
+1290
File diff suppressed because it is too large
Load Diff
+340
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+532
@@ -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.*
|
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) {
|
enum class MessageType(val value: UByte) {
|
||||||
ANNOUNCE(0x01u),
|
ANNOUNCE(0x01u),
|
||||||
KEY_EXCHANGE(0x02u),
|
// 0x02 was legacy keyExchange - removed
|
||||||
LEAVE(0x03u),
|
LEAVE(0x03u),
|
||||||
MESSAGE(0x04u),
|
MESSAGE(0x04u), // All user messages (private and broadcast)
|
||||||
FRAGMENT_START(0x05u),
|
FRAGMENT_START(0x05u),
|
||||||
FRAGMENT_CONTINUE(0x06u),
|
FRAGMENT_CONTINUE(0x06u),
|
||||||
FRAGMENT_END(0x07u),
|
FRAGMENT_END(0x07u),
|
||||||
CHANNEL_ANNOUNCE(0x08u),
|
CHANNEL_ANNOUNCE(0x08u), // Announce password-protected channel status
|
||||||
CHANNEL_RETENTION(0x09u),
|
CHANNEL_RETENTION(0x09u), // Announce channel retention status
|
||||||
DELIVERY_ACK(0x0Au),
|
DELIVERY_ACK(0x0Au), // Acknowledge message received
|
||||||
DELIVERY_STATUS_REQUEST(0x0Bu),
|
DELIVERY_STATUS_REQUEST(0x0Bu), // Request delivery status update
|
||||||
READ_RECEIPT(0x0Cu);
|
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 {
|
companion object {
|
||||||
fun fromValue(value: UByte): MessageType? {
|
fun fromValue(value: UByte): MessageType? {
|
||||||
@@ -74,7 +88,7 @@ data class BitchatPacket(
|
|||||||
) : this(
|
) : this(
|
||||||
version = 1u,
|
version = 1u,
|
||||||
type = type,
|
type = type,
|
||||||
senderID = senderID.toByteArray(),
|
senderID = hexStringToByteArray(senderID),
|
||||||
recipientID = null,
|
recipientID = null,
|
||||||
timestamp = (System.currentTimeMillis()).toULong(),
|
timestamp = (System.currentTimeMillis()).toULong(),
|
||||||
payload = payload,
|
payload = payload,
|
||||||
@@ -90,6 +104,27 @@ data class BitchatPacket(
|
|||||||
fun fromBinaryData(data: ByteArray): BitchatPacket? {
|
fun fromBinaryData(data: ByteArray): BitchatPacket? {
|
||||||
return BinaryProtocol.decode(data)
|
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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -219,7 +254,12 @@ object BinaryProtocol {
|
|||||||
val result = ByteArray(buffer.position())
|
val result = ByteArray(buffer.position())
|
||||||
buffer.rewind()
|
buffer.rewind()
|
||||||
buffer.get(result)
|
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) {
|
} catch (e: Exception) {
|
||||||
return null
|
return null
|
||||||
@@ -228,9 +268,12 @@ object BinaryProtocol {
|
|||||||
|
|
||||||
fun decode(data: ByteArray): BitchatPacket? {
|
fun decode(data: ByteArray): BitchatPacket? {
|
||||||
try {
|
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
|
// Header
|
||||||
val version = buffer.get().toUByte()
|
val version = buffer.get().toUByte()
|
||||||
@@ -256,7 +299,7 @@ object BinaryProtocol {
|
|||||||
if (hasRecipient) expectedSize += RECIPIENT_ID_SIZE
|
if (hasRecipient) expectedSize += RECIPIENT_ID_SIZE
|
||||||
if (hasSignature) expectedSize += SIGNATURE_SIZE
|
if (hasSignature) expectedSize += SIGNATURE_SIZE
|
||||||
|
|
||||||
if (data.size < expectedSize) return null
|
if (unpaddedData.size < expectedSize) return null
|
||||||
|
|
||||||
// SenderID
|
// SenderID
|
||||||
val senderID = ByteArray(SENDER_ID_SIZE)
|
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 favoritePeers by viewModel.favoritePeers.observeAsState(emptySet())
|
||||||
val fingerprint = viewModel.privateChatManager.getPeerFingerprint(selectedPrivatePeer)
|
val fingerprint = viewModel.privateChatManager.getPeerFingerprint(selectedPrivatePeer)
|
||||||
val isFavorite = favoritePeers.contains(fingerprint)
|
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(
|
PrivateChatHeader(
|
||||||
peerID = selectedPrivatePeer,
|
peerID = selectedPrivatePeer,
|
||||||
peerNicknames = viewModel.meshService.getPeerNicknames(),
|
peerNicknames = viewModel.meshService.getPeerNicknames(),
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
|
hasEncryption = hasEncryption,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
onToggleFavorite = { viewModel.toggleFavorite(selectedPrivatePeer) }
|
onToggleFavorite = { viewModel.toggleFavorite(selectedPrivatePeer) }
|
||||||
)
|
)
|
||||||
@@ -195,6 +197,7 @@ private fun PrivateChatHeader(
|
|||||||
peerID: String,
|
peerID: String,
|
||||||
peerNicknames: Map<String, String>,
|
peerNicknames: Map<String, String>,
|
||||||
isFavorite: Boolean,
|
isFavorite: Boolean,
|
||||||
|
hasEncryption: Boolean,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onToggleFavorite: () -> Unit
|
onToggleFavorite: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -243,6 +246,18 @@ private fun PrivateChatHeader(
|
|||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
tint = Color(0xFFFF9500) // Orange to match private message theme
|
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))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = peerNickname,
|
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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user