Add color hash function (#264)
This commit is contained in:
parent
1734f0edeb
commit
9b1347d233
|
@ -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