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:
Pavel 2022-06-14 14:28:08 +02:00 committed by GitHub
parent dd67a504bf
commit c45d0a61f0
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
9 changed files with 65 additions and 26 deletions

View File

@ -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)
} }

View File

@ -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)!
} }
} }

View File

@ -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"`)
})
})

View File

@ -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')
}
}

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>