mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
refactor(nip44): use a content-addressed key object for the shared-key cache
Replaces the hex-string cache key with a small CacheKey holding the raw (privateKey, pubKey) references — no byte copy and no ~400-byte hex String per lookup. The precomputed Int hash is only a bucket selector; equals() does the authoritative full-content comparison, so collisions share a bucket and are disambiguated rather than returning the wrong peer's secret. Same value-type immutability contract already relied on by X25519KeyPair as a map key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PY7kpiTcFiDshYqBsQ5KVC
This commit is contained in:
+37
-16
@@ -21,37 +21,58 @@
|
||||
package com.vitorpamplona.quartz.nip44Encryption
|
||||
|
||||
import androidx.collection.LruCache
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
|
||||
class SharedKeyCache {
|
||||
// Keyed by the full (privateKey || pubKey) content, NOT by a 32-bit hashCode.
|
||||
// A hashCode key silently collides: two distinct peers whose (priv, pub) bytes
|
||||
// hash to the same Int would share a cache slot, so `get` could return one
|
||||
// peer's conversation key for a message meant for the other — a silent
|
||||
// wrong-key encrypt/decrypt. The polynomial hash that was used here collides
|
||||
// independently of the private key, so an attacker could grind a pubkey that
|
||||
// aliases a victim's contact. Keying on the raw bytes removes the collision.
|
||||
private val sharedKeyCache = LruCache<String, ByteArray>(200)
|
||||
// Keyed by the full (privateKey, pubKey) content via [CacheKey], NOT by a bare
|
||||
// 32-bit hashCode. Using a hashCode as the whole map key silently collides:
|
||||
// two distinct peers whose (priv, pub) bytes hash to the same Int would share a
|
||||
// slot, so `get` could return one peer's conversation key for a message meant
|
||||
// for the other — a silent wrong-key encrypt/decrypt (and the polynomial hash
|
||||
// that was used collides independently of the private key, so a pubkey aliasing
|
||||
// a victim's contact is grindable). [CacheKey] keeps the cheap Int hash only as
|
||||
// a bucket selector and disambiguates collisions with a full contentEquals, so
|
||||
// it stays correct while avoiding the per-lookup allocation of a hex String key.
|
||||
private val sharedKeyCache = LruCache<CacheKey, ByteArray>(200)
|
||||
|
||||
fun clearCache() {
|
||||
sharedKeyCache.evictAll()
|
||||
}
|
||||
|
||||
private fun cacheKey(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String = privateKey.toHexKey() + pubKey.toHexKey()
|
||||
|
||||
fun get(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray? = sharedKeyCache[cacheKey(privateKey, pubKey)]
|
||||
): ByteArray? = sharedKeyCache[CacheKey(privateKey, pubKey)]
|
||||
|
||||
fun add(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
secret: ByteArray,
|
||||
) {
|
||||
sharedKeyCache.put(cacheKey(privateKey, pubKey), secret)
|
||||
sharedKeyCache.put(CacheKey(privateKey, pubKey), secret)
|
||||
}
|
||||
|
||||
/**
|
||||
* Content-addressed cache key holding the raw key references (no byte copy, no
|
||||
* hex string). The precomputed [hash] is only a bucket selector — [equals] does
|
||||
* the authoritative full-content comparison, so hash collisions can never return
|
||||
* the wrong peer's secret. Callers must treat the passed arrays as immutable
|
||||
* (the same value-type contract [com.vitorpamplona.quartz.marmot.mls.crypto.X25519KeyPair]
|
||||
* relies on when used as a map key).
|
||||
*/
|
||||
private class CacheKey(
|
||||
val privateKey: ByteArray,
|
||||
val pubKey: ByteArray,
|
||||
) {
|
||||
private val hash = privateKey.contentHashCode() * 31 + pubKey.contentHashCode()
|
||||
|
||||
override fun hashCode(): Int = hash
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is CacheKey) return false
|
||||
return hash == other.hash &&
|
||||
privateKey.contentEquals(other.privateKey) &&
|
||||
pubKey.contentEquals(other.pubKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user