make recoverPublicKey universal

This commit is contained in:
Pavel Prichodko 2022-06-09 14:40:08 +02:00 committed by Felicio Mununga
parent e8a8cc5e12
commit 025787a7ec
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
5 changed files with 81 additions and 35 deletions

View File

@ -1,4 +1,4 @@
import { recoverPublicKeyFromMetadata } from '~/src/utils/recover-public-key-from-metadata'
import { recoverPublicKey } from '~/src/utils/recover-public-key'
import { ApplicationMetadataMessage } from '../../../protos/application-metadata-message'
import { ChatMessage } from '../../../protos/chat-message'
@ -48,7 +48,10 @@ export function handleChannelChatMessage(
return
}
const publicKey = recoverPublicKeyFromMetadata(decodedMetadata)
const publicKey = recoverPublicKey(
decodedMetadata.signature,
decodedMetadata.payload
)
const decodedPayload = ChatMessage.decode(messageToDecode)

View File

@ -2,7 +2,7 @@
// todo?: rename to handle-message
import { bytesToHex } from 'ethereum-cryptography/utils'
import { recoverPublicKeyFromMetadata } from '~/src/utils/recover-public-key-from-metadata'
import { recoverPublicKey } from '~/src/utils/recover-public-key'
import { ApplicationMetadataMessage } from '../../../protos/application-metadata-message'
import {
@ -63,7 +63,10 @@ export function handleChannelChatMessage(
// break
// }
const publicKey = recoverPublicKeyFromMetadata(decodedMetadata)
const publicKey = recoverPublicKey(
decodedMetadata.signature,
decodedMetadata.payload
)
// todo: merge and process other types of messages
// TODO?: ignore messages which are messageType !== COMMUNITY_CHAT

View File

@ -1,24 +0,0 @@
import { keccak256 } from 'ethereum-cryptography/keccak'
import { recoverPublicKey } from 'ethereum-cryptography/secp256k1'
import type { ApplicationMetadataMessage } from '../../protos/application-metadata-message'
/**
* returns the public key of the signer
* msg must be the 32-byte keccak hash of the message to be signed.
* sig must be a 65-byte compact ECDSA signature containing the recovery id as the last element.
*/
export function recoverPublicKeyFromMetadata(
metadata: ApplicationMetadataMessage
): Uint8Array {
const signature = metadata.signature.slice(0, 64)
const recoveryId = metadata.signature.slice(-1)
const pk = recoverPublicKey(
keccak256(metadata.payload),
signature,
Number(recoveryId)
)
return pk
}

View File

@ -1,10 +1,25 @@
import { recoverPublicKeyFromMetadata } from './recover-public-key-from-metadata'
import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils'
import type { ApplicationMetadataMessage } from '../../protos/application-metadata-message'
import { Account } from '../account'
import { recoverPublicKey } from './recover-public-key'
describe('TODO: recoverPublicKeyFromMetadata', () => {
import type { ApplicationMetadataMessage } from '~/protos/application-metadata-message'
describe('recoverPublicKey', () => {
it('should recover public key', async () => {
const metadataFixture = {
const payload = utf8ToBytes('hello')
const account = new Account()
const signature = await account.sign(payload)
expect(bytesToHex(recoverPublicKey(signature, payload))).toEqual(
account.publicKey
)
})
it('should recover public key from fixture', async () => {
const metadataFixture: ApplicationMetadataMessage = {
type: 'TYPE_EMOJI_REACTION' as ApplicationMetadataMessage.Type,
signature: new Uint8Array([
250, 132, 234, 119, 159, 124, 98, 93, 197, 108, 99, 52, 186, 234, 142,
101, 147, 180, 50, 190, 102, 61, 219, 189, 95, 124, 29, 74, 43, 46, 106,
@ -25,8 +40,7 @@ describe('TODO: recoverPublicKeyFromMetadata', () => {
102, 55, 99, 48, 98, 55, 55, 97, 55, 99, 48, 97, 53, 101, 98, 97, 53,
102, 97, 57, 100, 52, 100, 57, 49, 98, 97, 56, 32, 5, 40, 2,
]),
type: 'TYPE_EMOJI_REACTION',
} as unknown as ApplicationMetadataMessage
}
const publicKeySnapshot = new Uint8Array([
4, 172, 65, 157, 172, 154, 139, 187, 88, 130, 90, 60, 222, 96, 238, 240,
@ -36,8 +50,33 @@ describe('TODO: recoverPublicKeyFromMetadata', () => {
99, 24, 17,
])
const result = recoverPublicKeyFromMetadata(metadataFixture)
const result = recoverPublicKey(
metadataFixture.signature,
metadataFixture.payload
)
expect(result).toEqual(publicKeySnapshot)
})
it('should not recover public key with different payload', async () => {
const payload = utf8ToBytes('1')
const account = new Account()
const signature = await account.sign(payload)
const payload2 = utf8ToBytes('2')
expect(recoverPublicKey(signature, payload2)).not.toEqual(account.publicKey)
})
it('should throw error when signature length is not 65 bytes', async () => {
const payload = utf8ToBytes('hello')
const signature = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
])
// TODO: use toThrowErrorMatchingInlineSnapshot
expect(recoverPublicKey(signature, payload)).toThrow(
'Signature must be 65 bytes long'
)
})
})

View File

@ -0,0 +1,25 @@
import { keccak256 } from 'ethereum-cryptography/keccak'
import { recoverPublicKey as secpRecoverPublicKey } from 'ethereum-cryptography/secp256k1'
/**
* returns the public key of the signer
* msg must be the 32-byte keccak hash of the message to be signed.
* sig must be a 65-byte compact ECDSA signature containing the recovery id as the last element.
*/
export function recoverPublicKey(
sig: Uint8Array,
payload: Uint8Array
): Uint8Array {
if (sig.length !== 65) {
throw new Error('Signature must be 65 bytes long')
}
if (sig[64] >= 4) {
throw new Error('Recovery id must be less than 4')
}
const signature = sig.slice(0, 64)
const recoveryId = sig.slice(-1)
return secpRecoverPublicKey(keccak256(payload), signature, Number(recoveryId))
}