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 { bytesToHex, concatBytes } from 'ethereum-cryptography/utils'
|
||||
|
||||
import { compressPublicKey } from './utils/compress-public-key'
|
||||
import { generateUsername } from './utils/generate-username'
|
||||
|
||||
export class Account {
|
||||
|
@ -13,12 +14,10 @@ export class Account {
|
|||
constructor() {
|
||||
const privateKey = utils.randomPrivateKey()
|
||||
const publicKey = getPublicKey(privateKey)
|
||||
const chatKey = getPublicKey(privateKey, true)
|
||||
|
||||
this.privateKey = bytesToHex(privateKey)
|
||||
this.publicKey = bytesToHex(publicKey)
|
||||
// TODO?: add 0x prefix to public key
|
||||
this.chatKey = bytesToHex(chatKey)
|
||||
this.chatKey = '0x' + compressPublicKey(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 { 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 {
|
||||
publicKey: string
|
||||
chatKey: string
|
||||
username: string
|
||||
colorHash: ColorHash
|
||||
|
||||
constructor(publicKey: string) {
|
||||
this.publicKey = publicKey
|
||||
this.chatKey = '0x' + compressPublicKey(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 { compressPublicKey } from './compress-public-key'
|
||||
|
||||
export 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"
|
||||
export function publicKeyToColorHash(publicKey: string): ColorHash {
|
||||
const compressedPublicKey = compressPublicKey(publicKey)
|
||||
|
||||
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 colorHashHex = compressedPublicKey.slice(43, 63)
|
||||
const colorHash = hexToColorHash(
|
||||
colorHashHex,
|
||||
COLOR_HASH_COLORS_COUNT,
|
||||
|
|
|
@ -20,7 +20,7 @@ export const DisconnectDialog = (props: Props) => {
|
|||
<Flex direction="column" align="center" gap="2">
|
||||
<Avatar size={64} />
|
||||
<Heading weight="600">{account.username}</Heading>
|
||||
<Text color="gray">Chatkey: {account.publicKey}</Text>
|
||||
<Text color="gray">Chatkey: {account.chatKey}</Text>
|
||||
<EmojiHash />
|
||||
</Flex>
|
||||
</Dialog.Body>
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Props {
|
|||
|
||||
export const MemberItem = (props: Props) => {
|
||||
const { member, indicator, verified, untrustworthy } = props
|
||||
const { publicKey, username, colorHash } = member
|
||||
const { chatKey, username, colorHash } = member
|
||||
|
||||
return (
|
||||
<Flex gap="2" align="center" css={{ height: 56 }}>
|
||||
|
@ -65,7 +65,7 @@ export const MemberItem = (props: Props) => {
|
|||
)}
|
||||
</Flex>
|
||||
<EthAddress size={10} color="gray">
|
||||
{publicKey}
|
||||
{chatKey}
|
||||
</EthAddress>
|
||||
</div>
|
||||
</Flex>
|
||||
|
|
|
@ -25,7 +25,7 @@ export const UserItem = (props: Props) => {
|
|||
</Text>
|
||||
</Flex>
|
||||
<EthAddress size={10} color="gray">
|
||||
{account.publicKey}
|
||||
{account.chatKey}
|
||||
</EthAddress>
|
||||
</div>
|
||||
</Flex>
|
||||
|
|
|
@ -2,20 +2,24 @@ import React from 'react'
|
|||
|
||||
import { Avatar, Dialog, EmojiHash, Heading, Text } from '~/src/system'
|
||||
|
||||
import type { Member } from '~/src/protocol'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
member: Member
|
||||
}
|
||||
|
||||
// TODO: Add all states of contact, wait for desktop release
|
||||
export const UserProfileDialog = (props: Props) => {
|
||||
const { name, ...dialogProps } = props
|
||||
const { member, ...dialogProps } = props
|
||||
|
||||
const { username, colorHash, chatKey } = member
|
||||
|
||||
return (
|
||||
<Dialog title={`${name}'s Profile`} size="640" {...dialogProps}>
|
||||
<Dialog title={`${username}'s Profile`} size="640" {...dialogProps}>
|
||||
<Dialog.Body align="center">
|
||||
<Avatar size="80" />
|
||||
<Heading size="22">{name}</Heading>
|
||||
<Text>Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377</Text>
|
||||
<Avatar size="80" name={username} colorHash={colorHash} />
|
||||
<Heading size="22">{username}</Heading>
|
||||
<Text>Chatkey: {chatKey}</Text>
|
||||
<EmojiHash />
|
||||
</Dialog.Body>
|
||||
<Dialog.Actions>
|
||||
|
|
Loading…
Reference in New Issue