Use chat keys in the UI (#274)
* add util for compressing public key * add chat keys to account and member * use compressPublicKey in color hash util * use chat keys in UI
This commit is contained in:
parent
dd67a504bf
commit
c45d0a61f0
|
@ -2,6 +2,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak'
|
||||||
import { getPublicKey, sign, utils } from 'ethereum-cryptography/secp256k1'
|
import { getPublicKey, sign, utils } from 'ethereum-cryptography/secp256k1'
|
||||||
import { bytesToHex, concatBytes } from 'ethereum-cryptography/utils'
|
import { bytesToHex, concatBytes } from 'ethereum-cryptography/utils'
|
||||||
|
|
||||||
|
import { compressPublicKey } from './utils/compress-public-key'
|
||||||
import { generateUsername } from './utils/generate-username'
|
import { generateUsername } from './utils/generate-username'
|
||||||
|
|
||||||
export class Account {
|
export class Account {
|
||||||
|
@ -13,12 +14,10 @@ export class Account {
|
||||||
constructor() {
|
constructor() {
|
||||||
const privateKey = utils.randomPrivateKey()
|
const privateKey = utils.randomPrivateKey()
|
||||||
const publicKey = getPublicKey(privateKey)
|
const publicKey = getPublicKey(privateKey)
|
||||||
const chatKey = getPublicKey(privateKey, true)
|
|
||||||
|
|
||||||
this.privateKey = bytesToHex(privateKey)
|
this.privateKey = bytesToHex(privateKey)
|
||||||
this.publicKey = bytesToHex(publicKey)
|
this.publicKey = bytesToHex(publicKey)
|
||||||
// TODO?: add 0x prefix to public key
|
this.chatKey = '0x' + compressPublicKey(this.publicKey)
|
||||||
this.chatKey = bytesToHex(chatKey)
|
|
||||||
this.username = generateUsername('0x' + this.publicKey)
|
this.username = generateUsername('0x' + this.publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { compressPublicKey } from '../utils/compress-public-key'
|
||||||
import { generateUsername } from '../utils/generate-username'
|
import { generateUsername } from '../utils/generate-username'
|
||||||
import { publicKeyToColorHash } from '../utils/public-key-to-color-hash'
|
import { publicKeyToColorHash } from '../utils/public-key-to-color-hash'
|
||||||
|
|
||||||
|
@ -5,13 +6,14 @@ import type { ColorHash } from '../utils/public-key-to-color-hash'
|
||||||
|
|
||||||
export class Member {
|
export class Member {
|
||||||
publicKey: string
|
publicKey: string
|
||||||
|
chatKey: string
|
||||||
username: string
|
username: string
|
||||||
colorHash: ColorHash
|
colorHash: ColorHash
|
||||||
|
|
||||||
constructor(publicKey: string) {
|
constructor(publicKey: string) {
|
||||||
this.publicKey = publicKey
|
this.publicKey = publicKey
|
||||||
|
this.chatKey = '0x' + compressPublicKey(publicKey)
|
||||||
this.username = generateUsername(publicKey)
|
this.username = generateUsername(publicKey)
|
||||||
// TODO: can it fail?
|
this.colorHash = publicKeyToColorHash(publicKey)
|
||||||
this.colorHash = publicKeyToColorHash(publicKey)!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { getPublicKey, utils } from 'ethereum-cryptography/secp256k1'
|
||||||
|
import { bytesToHex } from 'ethereum-cryptography/utils'
|
||||||
|
|
||||||
|
import { compressPublicKey } from './compress-public-key'
|
||||||
|
|
||||||
|
describe('compressPublicKey', () => {
|
||||||
|
it('should return compressed public key', () => {
|
||||||
|
const privateKey = utils.randomPrivateKey()
|
||||||
|
|
||||||
|
const publicKey = bytesToHex(getPublicKey(privateKey))
|
||||||
|
const compressedPublicKey = bytesToHex(getPublicKey(privateKey, true))
|
||||||
|
|
||||||
|
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should accept public key with a base prefix', () => {
|
||||||
|
const privateKey = utils.randomPrivateKey()
|
||||||
|
|
||||||
|
const publicKey = '0x' + bytesToHex(getPublicKey(privateKey))
|
||||||
|
const compressedPublicKey = bytesToHex(getPublicKey(privateKey, true))
|
||||||
|
|
||||||
|
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error if public key is not a valid hex', () => {
|
||||||
|
expect(() => {
|
||||||
|
compressPublicKey('not a valid public key')
|
||||||
|
}).toThrowErrorMatchingInlineSnapshot(`"Invalid public key"`)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as secp from 'ethereum-cryptography/secp256k1'
|
||||||
|
|
||||||
|
export function compressPublicKey(publicKey: string): string {
|
||||||
|
try {
|
||||||
|
const pk = publicKey.replace(/^0[xX]/, '') // ensures hexadecimal digits without "base prefix"
|
||||||
|
return secp.Point.fromHex(pk).toHex(true)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Invalid public key')
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,16 @@
|
||||||
import * as secp256k1 from 'ethereum-cryptography/secp256k1'
|
import * as secp256k1 from 'ethereum-cryptography/secp256k1'
|
||||||
|
|
||||||
|
import { compressPublicKey } from './compress-public-key'
|
||||||
|
|
||||||
export type ColorHash = number[][]
|
export type ColorHash = number[][]
|
||||||
|
|
||||||
const COLOR_HASH_COLORS_COUNT = 32
|
const COLOR_HASH_COLORS_COUNT = 32
|
||||||
const COLOR_HASH_SEGMENT_MAX_LENGTH = 5
|
const COLOR_HASH_SEGMENT_MAX_LENGTH = 5
|
||||||
|
|
||||||
export function publicKeyToColorHash(publicKey: string): ColorHash | undefined {
|
export function publicKeyToColorHash(publicKey: string): ColorHash {
|
||||||
const publicKeyHex = publicKey.replace(/^0[xX]/, '') // ensures hexadecimal digits without "base prefix"
|
const compressedPublicKey = compressPublicKey(publicKey)
|
||||||
|
|
||||||
let compressedPublicKeyDigits: string
|
const colorHashHex = compressedPublicKey.slice(43, 63)
|
||||||
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(
|
const colorHash = hexToColorHash(
|
||||||
colorHashHex,
|
colorHashHex,
|
||||||
COLOR_HASH_COLORS_COUNT,
|
COLOR_HASH_COLORS_COUNT,
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const DisconnectDialog = (props: Props) => {
|
||||||
<Flex direction="column" align="center" gap="2">
|
<Flex direction="column" align="center" gap="2">
|
||||||
<Avatar size={64} />
|
<Avatar size={64} />
|
||||||
<Heading weight="600">{account.username}</Heading>
|
<Heading weight="600">{account.username}</Heading>
|
||||||
<Text color="gray">Chatkey: {account.publicKey}</Text>
|
<Text color="gray">Chatkey: {account.chatKey}</Text>
|
||||||
<EmojiHash />
|
<EmojiHash />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Dialog.Body>
|
</Dialog.Body>
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface Props {
|
||||||
|
|
||||||
export const MemberItem = (props: Props) => {
|
export const MemberItem = (props: Props) => {
|
||||||
const { member, indicator, verified, untrustworthy } = props
|
const { member, indicator, verified, untrustworthy } = props
|
||||||
const { publicKey, username, colorHash } = member
|
const { chatKey, username, colorHash } = member
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="2" align="center" css={{ height: 56 }}>
|
<Flex gap="2" align="center" css={{ height: 56 }}>
|
||||||
|
@ -65,7 +65,7 @@ export const MemberItem = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<EthAddress size={10} color="gray">
|
<EthAddress size={10} color="gray">
|
||||||
{publicKey}
|
{chatKey}
|
||||||
</EthAddress>
|
</EthAddress>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const UserItem = (props: Props) => {
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<EthAddress size={10} color="gray">
|
<EthAddress size={10} color="gray">
|
||||||
{account.publicKey}
|
{account.chatKey}
|
||||||
</EthAddress>
|
</EthAddress>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
|
@ -2,20 +2,24 @@ import React from 'react'
|
||||||
|
|
||||||
import { Avatar, Dialog, EmojiHash, Heading, Text } from '~/src/system'
|
import { Avatar, Dialog, EmojiHash, Heading, Text } from '~/src/system'
|
||||||
|
|
||||||
|
import type { Member } from '~/src/protocol'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
member: Member
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add all states of contact, wait for desktop release
|
// TODO: Add all states of contact, wait for desktop release
|
||||||
export const UserProfileDialog = (props: Props) => {
|
export const UserProfileDialog = (props: Props) => {
|
||||||
const { name, ...dialogProps } = props
|
const { member, ...dialogProps } = props
|
||||||
|
|
||||||
|
const { username, colorHash, chatKey } = member
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title={`${name}'s Profile`} size="640" {...dialogProps}>
|
<Dialog title={`${username}'s Profile`} size="640" {...dialogProps}>
|
||||||
<Dialog.Body align="center">
|
<Dialog.Body align="center">
|
||||||
<Avatar size="80" />
|
<Avatar size="80" name={username} colorHash={colorHash} />
|
||||||
<Heading size="22">{name}</Heading>
|
<Heading size="22">{username}</Heading>
|
||||||
<Text>Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377</Text>
|
<Text>Chatkey: {chatKey}</Text>
|
||||||
<EmojiHash />
|
<EmojiHash />
|
||||||
</Dialog.Body>
|
</Dialog.Body>
|
||||||
<Dialog.Actions>
|
<Dialog.Actions>
|
||||||
|
|
Loading…
Reference in New Issue