Add color hash function (#264)
This commit is contained in:
parent
774f75fec9
commit
47ea7a90ad
|
@ -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],
|
||||
])
|
||||
})
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue