Skip to content

Commit

Permalink
Merge pull request #1923 from oasisprotocol/mz/privKey-2
Browse files Browse the repository at this point in the history
Allow to use eth private key that starts with 0x - version 2
  • Loading branch information
buberdds committed May 15, 2024
2 parents 61181fe + 046cee0 commit f2204a5
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 22 deletions.
1 change: 1 addition & 0 deletions .changelog/1923.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow to use eth private key that starts with 0x
38 changes: 37 additions & 1 deletion playwright/tests/paraTimes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { privateKey, privateKeyAddress } from '../../src/utils/__fixtures__/test-inputs'
import { privateKey, privateKeyAddress, ethAccount } from '../../src/utils/__fixtures__/test-inputs'
import { fillPrivateKeyWithoutPassword } from '../utils/fillPrivateKey'
import { warnSlowApi } from '../utils/warnSlowApi'
import { mockApi } from '../utils/mockApi'
Expand Down Expand Up @@ -33,4 +33,40 @@ test.describe('ParaTimes', () => {
await page.getByRole('button', { name: /Next/i }).click()
await expect(page.getByPlaceholder('0x...')).toHaveValue('')
})

test('should validate eth private key', async ({ page }) => {
const validKey = ethAccount.privateKey
const validKeyWithPrefix = `0x${validKey}`
const invalidKey = validKey.replace('c', 'g')
const invalidKeyWithPrefix = `0x${invalidKey}`

async function testPrivateKeyValidation(key, expected) {
await page.getByPlaceholder('Enter Ethereum-compatible private key').fill(key)
await page.getByRole('button', { name: 'Next' }).click()
await expect(page.getByText(expected)).toBeVisible()
}

await page.goto('/open-wallet/private-key')
await fillPrivateKeyWithoutPassword(page, {
privateKey: privateKey,
privateKeyAddress: privateKeyAddress,
persistenceCheckboxChecked: false,
persistenceCheckboxDisabled: false,
})
await page.getByTestId('nav-paratime').click()
await page.getByRole('button', { name: /Withdraw/i }).click()
await page.getByRole('button', { name: 'Select a ParaTime' }).click()
await expect(page.getByRole('listbox')).toBeVisible()
await page.getByRole('listbox').locator('button', { hasText: 'Sapphire' }).click()
await page.getByRole('button', { name: 'Next' }).click()
await page.getByPlaceholder(privateKeyAddress).fill(privateKeyAddress)
// valid eth private keys
await testPrivateKeyValidation(validKey, /enter the amount/)
await page.getByRole('button', { name: 'Back' }).click()
await testPrivateKeyValidation(validKeyWithPrefix, /enter the amount/)
await page.getByRole('button', { name: 'Back' }).click()
// invalid eth private keys
await testPrivateKeyValidation(invalidKey, /private key is invalid/)
await testPrivateKeyValidation(invalidKeyWithPrefix, /private key is invalid/)
})
})
2 changes: 1 addition & 1 deletion src/app/lib/eth-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as oasis from '@oasisprotocol/client'
import * as oasisRT from '@oasisprotocol/client-rt'
import { bytesToHex, isValidPrivate, privateToAddress, toChecksumAddress } from '@ethereumjs/util'
export { isValidAddress as isValidEthAddress } from '@ethereumjs/util'
export { isValidAddress as isValidEthAddress, stripHexPrefix } from '@ethereumjs/util'

export const hexToBuffer = (value: string): Buffer => Buffer.from(value, 'hex')
export const isValidEthPrivateKey = (ethPrivateKey: string): boolean => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { TransactionTypes } from 'app/state/paratimes/types'
import { TransactionForm, TransactionTypes } from 'app/state/paratimes/types'
import { useParaTimes, ParaTimesHook } from '../../useParaTimes'
import { useParaTimesNavigation, ParaTimesNavigationHook } from '../../useParaTimesNavigation'
import { TransactionRecipient } from '..'
Expand All @@ -25,7 +25,7 @@ describe('<TransactionRecipient />', () => {
ticker: 'ROSE',
transactionForm: {
recipient: '',
ethPrivateKey: '',
ethPrivateKeyRaw: '',
},
usesOasisAddress: true,
} as ParaTimesHook
Expand Down Expand Up @@ -123,7 +123,7 @@ describe('<TransactionRecipient />', () => {
...mockUseParaTimesEVMcResult,
transactionForm: {
...mockUseParaTimesEVMcResult.transactionForm,
ethPrivateKey: '123',
ethPrivateKeyRaw: '123',
},
})
jest.mocked(useParaTimesNavigation).mockReturnValue({
Expand All @@ -144,7 +144,7 @@ describe('<TransactionRecipient />', () => {
...mockUseParaTimesEVMcResult,
transactionForm: {
...mockUseParaTimesEVMcResult.transactionForm,
ethPrivateKey: '----------------------------------------------------------------',
ethPrivateKeyRaw: '----------------------------------------------------------------',
},
})
jest.mocked(useParaTimesNavigation).mockReturnValue({
Expand All @@ -160,13 +160,18 @@ describe('<TransactionRecipient />', () => {
})

it('should navigate to amount selection step when address is valid', async () => {
const ethPrivateKey = mockUseParaTimesEVMcResult.evmAccounts[0].ethPrivateKey
const ethPrivateKeyWith0xPrefix = `0x${ethPrivateKey}`
const setTransactionForm = jest.fn()
const navigateToAmount = jest.fn()
jest.mocked(useParaTimes).mockReturnValue({
...mockUseParaTimesResult,
setTransactionForm,
transactionForm: {
recipient: 'oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe',
},
} as ParaTimesHook)
ethPrivateKeyRaw: ethPrivateKeyWith0xPrefix,
} as TransactionForm,
})
jest.mocked(useParaTimesNavigation).mockReturnValue({
...mockUseParaTimesNavigationResult,
navigateToAmount,
Expand All @@ -175,6 +180,11 @@ describe('<TransactionRecipient />', () => {

await userEvent.click(screen.getByRole('button', { name: 'Next' }))

expect(setTransactionForm).toHaveBeenCalledWith({
ethPrivateKey: ethPrivateKey,
ethPrivateKeyRaw: ethPrivateKeyWith0xPrefix,
recipient: 'oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe',
})
expect(navigateToAmount).toHaveBeenCalled()
})

Expand Down
29 changes: 18 additions & 11 deletions src/app/pages/ParaTimesPage/TransactionRecipient/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useParaTimes } from '../useParaTimes'
import { useParaTimesNavigation } from '../useParaTimesNavigation'
import { PasswordField } from 'app/components/PasswordField'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'
import { stripHexPrefix } from '../../../lib/eth-helpers'

export const TransactionRecipient = () => {
const { t } = useTranslation()
Expand Down Expand Up @@ -65,29 +66,35 @@ export const TransactionRecipient = () => {
onChange={nextValue =>
setTransactionForm({
...nextValue,
ethPrivateKey:
typeof nextValue.ethPrivateKey === 'object'
? (nextValue.ethPrivateKey as any).value // from suggestions
: nextValue.ethPrivateKey,
ethPrivateKeyRaw:
typeof nextValue.ethPrivateKeyRaw === 'object'
? (nextValue.ethPrivateKeyRaw as any).value // from suggestions
: nextValue.ethPrivateKeyRaw,
})
}
onSubmit={navigateToAmount}
onSubmit={formData => {
setTransactionForm({
...formData.value,
ethPrivateKey: stripHexPrefix(formData.value.ethPrivateKeyRaw),
})
navigateToAmount()
}}
value={transactionForm}
style={{ width: isMobile ? '100%' : '465px' }}
{...preventSavingInputsToUserData}
>
<Box margin={{ bottom: 'medium' }}>
{isEvmcParaTime && !isDepositing && (
<PasswordField
inputElementId="ethPrivateKey"
name="ethPrivateKey"
validate={ethPrivateKey =>
!isValidEthPrivateKeyLength(ethPrivateKey)
inputElementId="ethPrivateKeyRaw"
name="ethPrivateKeyRaw"
validate={ethPrivateKeyRaw =>
!isValidEthPrivateKeyLength(stripHexPrefix(ethPrivateKeyRaw))
? t(
'paraTimes.validation.invalidEthPrivateKeyLength',
'Private key should be 64 characters long',
)
: !isValidEthPrivateKey(ethPrivateKey)
: !isValidEthPrivateKey(stripHexPrefix(ethPrivateKeyRaw))
? t(
'paraTimes.validation.invalidEthPrivateKey',
'Ethereum-compatible private key is invalid',
Expand All @@ -98,7 +105,7 @@ export const TransactionRecipient = () => {
'paraTimes.recipient.ethPrivateKeyPlaceholder',
'Enter Ethereum-compatible private key',
)}
value={transactionForm.ethPrivateKey}
value={transactionForm.ethPrivateKeyRaw}
showTip={t('openWallet.privateKey.showPrivateKey', 'Show private key')}
hideTip={t('openWallet.privateKey.hidePrivateKey', 'Hide private key')}
suggestions={evmAccounts.map(acc => ({ label: acc.ethAddress, value: acc.ethPrivateKey }))}
Expand Down
1 change: 1 addition & 0 deletions src/app/state/paratimes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const initialState: ParaTimesState = {
confirmTransferToForeignAccount: false,
defaultFeeAmount: '',
ethPrivateKey: '',
ethPrivateKeyRaw: '',
feeAmount: '',
feeGas: '',
paraTime: undefined,
Expand Down
3 changes: 3 additions & 0 deletions src/app/state/paratimes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export interface TransactionForm {
confirmTransferToValidator: boolean
confirmTransferToForeignAccount: boolean
defaultFeeAmount: string
// compatible with oasisRT.signatureSecp256k1
ethPrivateKey: string
// provided by user and used in form inputs allowing back and forth form navigation
ethPrivateKeyRaw: string
feeAmount: string
feeGas: string
paraTime?: ParaTime
Expand Down
13 changes: 10 additions & 3 deletions src/utils/__fixtures__/test-inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const privateKeyUnlockedState = {
confirmTransferToForeignAccount: false,
defaultFeeAmount: '',
ethPrivateKey: '',
ethPrivateKeyRaw: '',
feeAmount: '',
feeGas: '',
paraTime: undefined,
Expand Down Expand Up @@ -213,6 +214,11 @@ export const walletExtensionV0PersistedState = {
},
} satisfies WalletExtensionV0State

export const ethAccount = {
address: '0xbA1b346233E5bB5b44f5B4aC6bF224069f427b18',
privateKey: '6593a788d944bb3e25357df140fac5b0e6273f1500a3b37d6513bf9e9807afe2',
}

export const walletExtensionV0UnlockedState = {
account: {
address: 'oasis1qq30ejf9puuc6qnrazmy9dmn7f3gessveum5wnr6',
Expand All @@ -235,9 +241,9 @@ export const walletExtensionV0UnlockedState = {
},
},
evmAccounts: {
'0xbA1b346233E5bB5b44f5B4aC6bF224069f427b18': {
ethAddress: '0xbA1b346233E5bB5b44f5B4aC6bF224069f427b18',
ethPrivateKey: '6593a788d944bb3e25357df140fac5b0e6273f1500a3b37d6513bf9e9807afe2',
[ethAccount.address]: {
ethAddress: ethAccount.address,
ethPrivateKey: ethAccount.privateKey,
},
},
createWallet: { checkbox: false, mnemonic: [] },
Expand Down Expand Up @@ -268,6 +274,7 @@ export const walletExtensionV0UnlockedState = {
confirmTransferToForeignAccount: false,
defaultFeeAmount: '',
ethPrivateKey: '',
ethPrivateKeyRaw: '',
feeAmount: '',
feeGas: '',
paraTime: undefined,
Expand Down

0 comments on commit f2204a5

Please sign in to comment.