Add color hash function (#264)

This commit is contained in:
Felicio Mununga 2022-06-10 19:30:23 +02:00 committed by GitHub
parent 1734f0edeb
commit 9b1347d233
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
2 changed files with 148 additions and 0 deletions

View File

@ -0,0 +1,63 @@
import {
hexToColorHash,
publicKeyToColorHash,
} from './public-key-to-color-hash'
test('returns color hash from public key', () => {
expect(
publicKeyToColorHash(
'0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
)
).toEqual([
[3, 30],
[2, 10],
[5, 5],
[3, 14],
[5, 4],
[4, 19],
[3, 16],
[4, 0],
[5, 28],
[4, 13],
[4, 15],
])
})
test('returns undefined for invalid public keys', () => {
expect(publicKeyToColorHash('abc')).toBeUndefined()
expect(publicKeyToColorHash('0x01')).toBeUndefined()
expect(
publicKeyToColorHash(
'0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
)
).toBeUndefined()
expect(
publicKeyToColorHash(
'0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
)
).toBeUndefined()
})
test('returns color hash from hex', () => {
expect(hexToColorHash('0', 4, 4)).toEqual([[1, 0]])
expect(hexToColorHash('1', 4, 4)).toEqual([[1, 1]])
expect(hexToColorHash('4', 4, 4)).toEqual([[2, 0]])
expect(hexToColorHash('F', 4, 4)).toEqual([[4, 3]])
})
test('returns color hash from hex with redecued collision resistance', () => {
expect(hexToColorHash('FF', 4, 4)).toEqual([
[4, 3],
[4, 0],
])
expect(hexToColorHash('FC', 4, 4)).toEqual([
[4, 3],
[4, 0],
])
expect(hexToColorHash('FFFF', 4, 4)).toEqual([
[4, 3],
[4, 0],
[4, 3],
[4, 0],
])
})

View File

@ -0,0 +1,85 @@
import * as secp256k1 from 'ethereum-cryptography/secp256k1'
type ColorHash = number[][]
const COLOR_HASH_COLORS_COUNT = 32
const COLOR_HASH_SEGMENT_MAX_LENGTH = 5
export function publicKeyToColorHash(publicKey: string): ColorHash | undefined {
const publicKeyHex = publicKey.replace(/^0[xX]/, '') // ensures hexadecimal digits without "base prefix"
let compressedPublicKeyDigits: string
try {
compressedPublicKeyDigits =
secp256k1.Point.fromHex(publicKeyHex).toHex(true) // validates and adds "sign prefix" too
} catch (error) {
return undefined
}
const colorHashHex = compressedPublicKeyDigits.slice(43, 63)
const colorHash = hexToColorHash(
colorHashHex,
COLOR_HASH_COLORS_COUNT,
COLOR_HASH_SEGMENT_MAX_LENGTH
)
return colorHash
}
export function hexToColorHash(
hex: string,
colorsCount: number,
segmentLength: number
): ColorHash {
const colorIndices = numberToIndices(
BigInt(`0x${hex}`),
BigInt(colorsCount * segmentLength)
)
const colorHash = colorIndicesToColorHash(colorIndices, colorsCount)
return colorHash
}
function numberToIndices(number: bigint, base: bigint): bigint[] {
const indices: bigint[] = []
let nextNumber = number
if (nextNumber === 0n) {
return [0n]
}
while (nextNumber > 0n) {
const modulo = secp256k1.utils.mod(nextNumber, base)
nextNumber = nextNumber / base // truncates fractional results
indices.push(modulo)
}
return indices.reverse()
}
function colorIndicesToColorHash(
colorIndices: bigint[],
colorsCount: number
): ColorHash {
const colorHash: ColorHash = []
let previousColorIndex: number | undefined = undefined
for (const currentColorIndex of colorIndices) {
const colorLength = Math.ceil((Number(currentColorIndex) + 1) / colorsCount)
const nextColorIndex = Number(currentColorIndex % BigInt(colorsCount))
let colorIndex: number
if (nextColorIndex !== previousColorIndex) {
colorIndex = nextColorIndex
} else {
colorIndex = Number((currentColorIndex + 1n) % BigInt(colorsCount))
}
previousColorIndex = colorIndex
colorHash.push([colorLength, colorIndex])
}
return colorHash
}