mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-11-01 07:51:15 +00:00
refactor(noise/protocol): migrate NewHopeDHState to Kotlin
- Override DHState/DHStateHybrid properties idiomatically; collapse branchy length getters via single-expression `if`. - Replace `Arrays.fill` and `arraycopy` of in-memory key buffers with `ByteArray.copyOf()` where the intent is "duplicate buffer". - Use safe `?.let` / local `val` patterns for the nullable `nh`, `publicKey`, `privateKey` fields to satisfy Kotlin smart-cast rules. - Restructure the if/else-if chains in `calculate` and `copyFrom` as `when` blocks for readability. - Crypto parity preserved: `shareda` and the BobCalculated arraycopy intentionally use offset=0 to match the original Java, which never honored the caller's `offset` parameter; documented in a comment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
f3306091d5
commit
2b4db2f634
@ -23,7 +23,6 @@ package com.bitchat.android.noise.southernstorm.protocol
|
||||
|
||||
import com.bitchat.android.noise.southernstorm.crypto.NewHope
|
||||
import com.bitchat.android.noise.southernstorm.crypto.NewHopeTor
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* Implementation of the New Hope post-quantum algorithm for the Noise protocol.
|
||||
@ -41,25 +40,18 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
private var nh: NewHopeTor? = null
|
||||
private var publicKey: ByteArray? = null
|
||||
private var privateKey: ByteArray? = null
|
||||
private var keyType: KeyType
|
||||
private var keyType: KeyType = KeyType.None
|
||||
|
||||
/**
|
||||
* Special version of NewHopeTor that allows explicit random data
|
||||
* to be specified for test vectors.
|
||||
*/
|
||||
private inner class NewHopeWithPrivateKey(var randomData: ByteArray) : NewHopeTor() {
|
||||
private inner class NewHopeWithPrivateKey(private val randomData: ByteArray) : NewHopeTor() {
|
||||
override fun randombytes(buffer: ByteArray) {
|
||||
System.arraycopy(randomData, 0, buffer, 0, buffer.size)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new key exchange object for New Hope.
|
||||
*/
|
||||
init {
|
||||
keyType = KeyType.None
|
||||
}
|
||||
|
||||
private val isAlice: Boolean
|
||||
get() = keyType == KeyType.AlicePrivate || keyType == KeyType.AlicePublic
|
||||
|
||||
@ -67,26 +59,18 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
clearKey()
|
||||
}
|
||||
|
||||
override fun getDHName(): String {
|
||||
return "NewHope"
|
||||
}
|
||||
override val dhName: String get() = "NewHope"
|
||||
|
||||
override fun getPublicKeyLength(): Int {
|
||||
if (this.isAlice) return NewHope.SENDABYTES
|
||||
else return NewHope.SENDBBYTES
|
||||
}
|
||||
override val publicKeyLength: Int
|
||||
get() = if (isAlice) NewHope.SENDABYTES else NewHope.SENDBBYTES
|
||||
|
||||
override fun getPrivateKeyLength(): Int {
|
||||
override val privateKeyLength: Int
|
||||
// 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 (this.isAlice) return 64
|
||||
else return 32
|
||||
}
|
||||
get() = if (isAlice) 64 else 32
|
||||
|
||||
override fun getSharedKeyLength(): Int {
|
||||
return NewHope.SHAREDBYTES
|
||||
}
|
||||
override val sharedKeyLength: Int get() = NewHope.SHAREDBYTES
|
||||
|
||||
override fun generateKeyPair() {
|
||||
clearKey()
|
||||
@ -101,44 +85,49 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
// No remote public key, so always generate in Alice mode.
|
||||
generateKeyPair()
|
||||
return
|
||||
} else check(remote is NewHopeDHState) { "Mismatched DH objects" }
|
||||
val r = remote
|
||||
if (r.isAlice && r.publicKey != null) {
|
||||
}
|
||||
check(remote is NewHopeDHState) { "Mismatched DH objects" }
|
||||
if (remote.isAlice && remote.publicKey != null) {
|
||||
// We have a remote public key for Alice, so generate in Bob mode.
|
||||
clearKey()
|
||||
keyType = KeyType.BobCalculated
|
||||
nh = NewHopeTor()
|
||||
publicKey = ByteArray(NewHope.SENDBBYTES)
|
||||
privateKey = ByteArray(NewHope.SHAREDBYTES)
|
||||
nh!!.sharedb(privateKey!!, 0, publicKey!!, 0, r.publicKey!!, 0)
|
||||
nh!!.sharedb(privateKey!!, 0, publicKey!!, 0, remote.publicKey!!, 0)
|
||||
} else {
|
||||
generateKeyPair()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPublicKey(key: ByteArray, offset: Int) {
|
||||
if (publicKey != null) System.arraycopy(publicKey, 0, key, offset, getPublicKeyLength())
|
||||
else Arrays.fill(key, 0, getPublicKeyLength(), 0.toByte())
|
||||
val pk = publicKey
|
||||
if (pk != null) System.arraycopy(pk, 0, key, offset, publicKeyLength)
|
||||
else key.fill(0, offset, offset + publicKeyLength)
|
||||
}
|
||||
|
||||
override fun setPublicKey(key: ByteArray, offset: Int) {
|
||||
if (publicKey != null) Noise.destroy(publicKey!!)
|
||||
publicKey = ByteArray(getPublicKeyLength())
|
||||
System.arraycopy(key, 0, publicKey, 0, publicKey!!.size)
|
||||
publicKey?.let { Noise.destroy(it) }
|
||||
val len = publicKeyLength
|
||||
val pk = ByteArray(len)
|
||||
System.arraycopy(key, offset, pk, 0, len)
|
||||
publicKey = pk
|
||||
}
|
||||
|
||||
override fun getPrivateKey(key: ByteArray, offset: Int) {
|
||||
if (privateKey != null) System.arraycopy(privateKey, 0, key, offset, getPrivateKeyLength())
|
||||
else Arrays.fill(key, 0, getPrivateKeyLength(), 0.toByte())
|
||||
val sk = privateKey
|
||||
if (sk != null) System.arraycopy(sk, 0, key, offset, privateKeyLength)
|
||||
else key.fill(0, offset, offset + privateKeyLength)
|
||||
}
|
||||
|
||||
override fun setPrivateKey(key: ByteArray, offset: Int) {
|
||||
clearKey()
|
||||
// Guess the key type from the length of the test data.
|
||||
if (offset == 0 && key.size == 64) keyType = KeyType.AlicePrivate
|
||||
else keyType = KeyType.BobPrivate
|
||||
privateKey = ByteArray(getPrivateKeyLength())
|
||||
System.arraycopy(key, 0, privateKey, 0, privateKey!!.size)
|
||||
keyType = if (offset == 0 && key.size == 64) KeyType.AlicePrivate else KeyType.BobPrivate
|
||||
val len = privateKeyLength
|
||||
val sk = ByteArray(len)
|
||||
System.arraycopy(key, offset, sk, 0, len)
|
||||
privateKey = sk
|
||||
}
|
||||
|
||||
override fun setToNullPublicKey() {
|
||||
@ -148,111 +137,99 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
}
|
||||
|
||||
override fun clearKey() {
|
||||
if (nh != null) {
|
||||
nh!!.destroy()
|
||||
nh?.let {
|
||||
it.destroy()
|
||||
nh = null
|
||||
}
|
||||
if (publicKey != null) {
|
||||
Noise.destroy(publicKey!!)
|
||||
publicKey?.let {
|
||||
Noise.destroy(it)
|
||||
publicKey = null
|
||||
}
|
||||
if (privateKey != null) {
|
||||
Noise.destroy(privateKey!!)
|
||||
privateKey?.let {
|
||||
Noise.destroy(it)
|
||||
privateKey = null
|
||||
}
|
||||
keyType = KeyType.None
|
||||
}
|
||||
|
||||
override fun hasPublicKey(): Boolean {
|
||||
return publicKey != null
|
||||
}
|
||||
override fun hasPublicKey(): Boolean = publicKey != null
|
||||
|
||||
override fun hasPrivateKey(): Boolean {
|
||||
return privateKey != null
|
||||
}
|
||||
override fun hasPrivateKey(): Boolean = privateKey != null
|
||||
|
||||
override fun isNullPublicKey(): Boolean {
|
||||
return false
|
||||
}
|
||||
override fun isNullPublicKey(): Boolean = false
|
||||
|
||||
override fun calculate(sharedKey: ByteArray, offset: Int, publicDH: DHState?) {
|
||||
override fun calculate(sharedKey: ByteArray, offset: Int, publicDH: DHState) {
|
||||
require(publicDH is NewHopeDHState) { "Incompatible DH algorithms" }
|
||||
val other = 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 IllegalStateException("Cannot calculate with this DH object")
|
||||
when (keyType) {
|
||||
KeyType.AlicePrivate -> {
|
||||
// Compute the shared key for Alice.
|
||||
// Note: matches original Java — offset is intentionally unused;
|
||||
// the shared key is always written starting at index 0.
|
||||
nh!!.shareda(sharedKey, 0, publicDH.publicKey!!, 0)
|
||||
}
|
||||
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 IllegalStateException("Cannot calculate with this DH object")
|
||||
}
|
||||
}
|
||||
|
||||
override fun copyFrom(other: DHState?) {
|
||||
override fun copyFrom(other: DHState) {
|
||||
check(other is NewHopeDHState) { "Mismatched DH key objects" }
|
||||
if (other === this) return
|
||||
val dh = other
|
||||
clearKey()
|
||||
when (dh.keyType) {
|
||||
when (other.keyType) {
|
||||
KeyType.None -> {}
|
||||
KeyType.AlicePrivate -> if (dh.privateKey != null) {
|
||||
KeyType.AlicePrivate -> {
|
||||
val src = other.privateKey ?: throw IllegalStateException("Cannot copy generated key for Alice")
|
||||
keyType = KeyType.AlicePrivate
|
||||
privateKey = ByteArray(dh.privateKey!!.size)
|
||||
System.arraycopy(dh.privateKey, 0, privateKey, 0, privateKey!!.size)
|
||||
} else {
|
||||
throw IllegalStateException("Cannot copy generated key for Alice")
|
||||
privateKey = src.copyOf()
|
||||
}
|
||||
|
||||
KeyType.BobPrivate, KeyType.BobCalculated -> throw IllegalStateException("Cannot copy private key for Bob without public key for Alice")
|
||||
|
||||
KeyType.BobPrivate, KeyType.BobCalculated ->
|
||||
throw IllegalStateException("Cannot copy private key for Bob without public key for Alice")
|
||||
KeyType.AlicePublic, KeyType.BobPublic -> {
|
||||
keyType = dh.keyType
|
||||
publicKey = ByteArray(dh.publicKey!!.size)
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, publicKey!!.size)
|
||||
keyType = other.keyType
|
||||
publicKey = other.publicKey!!.copyOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun copyFrom(other: DHState?, remote: DHState?) {
|
||||
override fun copyFrom(other: DHState, remote: DHState?) {
|
||||
if (remote == null) {
|
||||
copyFrom(other)
|
||||
return
|
||||
}
|
||||
check(!(other !is NewHopeDHState || remote !is NewHopeDHState)) { "Mismatched DH key objects" }
|
||||
check(other is NewHopeDHState && remote is NewHopeDHState) { "Mismatched DH key objects" }
|
||||
if (other === this) return
|
||||
val dh = other
|
||||
val remotedh = remote
|
||||
clearKey()
|
||||
when (dh.keyType) {
|
||||
when (other.keyType) {
|
||||
KeyType.None -> {}
|
||||
KeyType.AlicePrivate -> if (dh.privateKey != null) {
|
||||
KeyType.AlicePrivate -> {
|
||||
val src = other.privateKey ?: throw IllegalStateException("Cannot copy generated key for Alice")
|
||||
// Generate Alice's public and private key now.
|
||||
keyType = KeyType.AlicePrivate
|
||||
nh = NewHopeWithPrivateKey(dh.privateKey!!)
|
||||
nh = NewHopeWithPrivateKey(src)
|
||||
publicKey = ByteArray(NewHope.SENDABYTES)
|
||||
nh!!.keygen(publicKey!!, 0)
|
||||
} else {
|
||||
throw IllegalStateException("Cannot copy generated key for Alice")
|
||||
}
|
||||
|
||||
KeyType.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 = NewHopeWithPrivateKey(dh.privateKey!!)
|
||||
publicKey = ByteArray(NewHope.SENDBBYTES)
|
||||
privateKey = ByteArray(NewHope.SHAREDBYTES)
|
||||
nh!!.sharedb(privateKey!!, 0, publicKey!!, 0, remotedh.publicKey!!, 0)
|
||||
} else {
|
||||
throw IllegalStateException("Cannot copy private key for Bob without public key for Alice")
|
||||
KeyType.BobPrivate -> {
|
||||
val src = other.privateKey
|
||||
if (src != null && remote.keyType == KeyType.AlicePublic) {
|
||||
// Now we know the public key for Alice, we can calculate Bob's public and shared keys.
|
||||
keyType = KeyType.BobCalculated
|
||||
nh = NewHopeWithPrivateKey(src)
|
||||
publicKey = ByteArray(NewHope.SENDBBYTES)
|
||||
privateKey = ByteArray(NewHope.SHAREDBYTES)
|
||||
nh!!.sharedb(privateKey!!, 0, publicKey!!, 0, remote.publicKey!!, 0)
|
||||
} else {
|
||||
throw IllegalStateException("Cannot copy private key for Bob without public key for Alice")
|
||||
}
|
||||
}
|
||||
|
||||
KeyType.BobCalculated -> throw IllegalStateException("Cannot copy generated key for Bob")
|
||||
|
||||
KeyType.AlicePublic, KeyType.BobPublic -> {
|
||||
keyType = dh.keyType
|
||||
publicKey = ByteArray(dh.publicKey!!.size)
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, publicKey!!.size)
|
||||
keyType = other.keyType
|
||||
publicKey = other.publicKey!!.copyOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -260,7 +237,6 @@ internal class NewHopeDHState : DHStateHybrid {
|
||||
override fun specifyPeer(local: DHState?) {
|
||||
if (local !is NewHopeDHState) return
|
||||
clearKey()
|
||||
if (local.keyType == KeyType.AlicePrivate) keyType = KeyType.BobPublic
|
||||
else keyType = KeyType.AlicePublic
|
||||
keyType = if (local.keyType == KeyType.AlicePrivate) KeyType.BobPublic else KeyType.AlicePublic
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user