Use the new avatar color hash algorithm.

This commit is contained in:
Greyson Parrelli
2025-03-05 12:35:33 -05:00
committed by Michelle Tang
parent 19bf6f95c7
commit f0f25ae12e
3 changed files with 47 additions and 16 deletions

View File

@@ -5,7 +5,9 @@
package org.thoughtcrime.securesms.conversation.colors
import org.signal.core.util.CryptoUtil
import org.thoughtcrime.securesms.groups.GroupId
import org.whispersystems.signalservice.api.push.ServiceId
/**
* Stolen from iOS. Utilizes a simple hash to map different characteristics to an avatar color index.
@@ -17,13 +19,13 @@ object AvatarColorHash {
*
* Uppercase is necessary here because iOS utilizes uppercase UUIDs by default.
*/
fun forAddress(serviceId: String?, e164: String?): AvatarColor {
if (!serviceId.isNullOrEmpty()) {
return forSeed(serviceId.toString().uppercase())
fun forAddress(serviceId: ServiceId?, e164: String?): AvatarColor {
if (serviceId != null) {
return forData(serviceId.toByteArray())
}
if (!e164.isNullOrEmpty()) {
return forSeed(e164)
return forData(e164.toByteArray(Charsets.UTF_8))
}
return AvatarColor.A100
@@ -33,22 +35,15 @@ object AvatarColorHash {
return forData(group.decodedId)
}
fun forSeed(seed: String): AvatarColor {
return forData(seed.toByteArray())
}
@JvmStatic
fun forCallLink(rootKey: ByteArray): AvatarColor {
return forIndex(rootKey.first().toInt())
}
private fun forData(data: ByteArray): AvatarColor {
var hash = 0
for (value in data) {
hash = hash.rotateLeft(3) xor value.toInt()
}
return forIndex(hash)
val hash = CryptoUtil.sha256(data)
val firstByte: Byte = hash[0]
return forIndex(firstByte.toInt())
}
private fun forIndex(index: Int): AvatarColor {

View File

@@ -4098,7 +4098,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
PNI_COLUMN to pni?.toString(),
PNI_SIGNATURE_VERIFIED to pniVerified.toInt(),
STORAGE_SERVICE_ID to Base64.encodeWithPadding(StorageSyncHelper.generateKey()),
AVATAR_COLOR to AvatarColorHash.forAddress((aci ?: pni)?.toString(), e164).serialize()
AVATAR_COLOR to AvatarColorHash.forAddress((aci ?: pni), e164).serialize()
)
if (pni != null || aci != null) {
@@ -4155,7 +4155,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
if (isInsert) {
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.proto.signalAci?.toString() ?: contact.proto.signalPni?.toString(), contact.proto.e164).serialize())
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.proto.signalAci ?: contact.proto.signalPni, contact.proto.e164).serialize())
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.colors
import org.junit.Assert.assertEquals
import org.junit.Test
import org.signal.core.util.Base64
import org.thoughtcrime.securesms.groups.GroupId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
class AvatarColorHashTest {
@Test
fun `hash test vector - ACI`() {
assertEquals(AvatarColor.A140, AvatarColorHash.forAddress(ACI.parseOrThrow("a025bf78-653e-44e0-beb9-deb14ba32487"), null))
}
@Test
fun `hash test vector - PNI`() {
assertEquals(AvatarColor.A200, AvatarColorHash.forAddress(PNI.parseOrThrow("11a175e3-fe31-4eda-87da-e0bf2a2e250b"), null))
}
@Test
fun `hash test vector - E164`() {
assertEquals(AvatarColor.A150, AvatarColorHash.forAddress(null, "+12135550124"))
}
@Test
fun `hash test vector - GroupId`() {
assertEquals(AvatarColor.A130, AvatarColorHash.forGroupId(GroupId.V2.push(Base64.decode("BwJRIdomqOSOckHjnJsknNCibCZKJFt+RxLIpa9CWJ4="))))
}
}