Skip to content

Commit

Permalink
refactor(catalyst): split voting tx and voting key encryption generat…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
banklesss committed Aug 30, 2024
1 parent b30eaa1 commit 266894e
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import {useCatalyst} from '@yoroi/staking'
import {useTheme} from '@yoroi/theme'
import React from 'react'
import {ScrollView, StyleSheet, View, ViewProps} from 'react-native'
import {ActivityIndicator, ScrollView, StyleSheet, View, ViewProps} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import {useMutation, UseMutationOptions} from 'react-query'

import {Button, Spacer} from '../../../../components'
import {BACKSPACE, NumericKeyboard} from '../../../../components/NumericKeyboard'
import {Space} from '../../../../components/Space/Space'
import {generatePrivateKeyForCatalyst} from '../../../../yoroi-wallets/cardano/catalyst'
import {encryptWithPassword} from '../../../../yoroi-wallets/cardano/catalyst/catalystCipher'
import {useNavigateTo} from '../../CatalystNavigator'
import {Actions, Description, PinBox, Row, Stepper} from '../../common/components'
import {useStrings} from '../../common/strings'

export const ConfirmPin = () => {
const strings = useStrings()
const {isDark} = useTheme()
const styles = useStyles()
const {pin} = useCatalyst()
const {pin, votingKeyEncryptedChanged, catalystKeyHexChanged} = useCatalyst()
const navigateTo = useNavigateTo()
const [currentActivePin, setCurrentActivePin] = React.useState(1)

const {generateVotingKeys, isLoading} = useGenerateVotingKeys({
onSuccess: ({catalystKeyHex, votingKeyEncrypted}) => {
votingKeyEncryptedChanged(votingKeyEncrypted)
catalystKeyHexChanged(catalystKeyHex)

navigateTo.confirmTx()
},
})

const [pin1Value, setPin1Value] = React.useState<null | string>(null)
const [pin2Value, setPin2Value] = React.useState<null | string>(null)
const [pin3Value, setPin3Value] = React.useState<null | string>(null)
Expand Down Expand Up @@ -140,7 +153,7 @@ export const ConfirmPin = () => {
)

const onNext = () => {
navigateTo.confirmTx()
generateVotingKeys(pin)
}

const handleOnPress = React.useCallback(
Expand Down Expand Up @@ -208,17 +221,57 @@ export const ConfirmPin = () => {

<Padding>
<Actions>
<Button shelleyTheme onPress={() => onNext()} title={strings.continueButton} disabled={!done} />
<Button shelleyTheme onPress={() => onNext()} title={strings.continueButton} disabled={!done || isLoading} />
</Actions>
</Padding>

<Space height="lg" />

<NumericKeyboard onKeyDown={onKeyDown} />

{isLoading && (
<View style={styles.loading}>
<ActivityIndicator size="large" color={isDark ? 'white' : 'black'} />
</View>
)}
</SafeAreaView>
)
}

type GenerateKeysInput = string

interface GenerateKeysOutput {
catalystKeyHex: string
votingKeyEncrypted: string
}

type GenerateKeysError = Error

const useGenerateVotingKeys = (
options?: UseMutationOptions<GenerateKeysOutput, GenerateKeysError, GenerateKeysInput>,
) => {
const mutation = useMutation(async (pin: string) => {
const catalystKey = await generatePrivateKeyForCatalyst()
.then((key) => key.toRawKey())
.then((key) => key.asBytes())

const catalystKeyHex = Buffer.from(catalystKey).toString('hex')

const password = Buffer.from(pin.split('').map(Number))
const votingKeyEncrypted = await encryptWithPassword(password, catalystKey)

return {
catalystKeyHex,
votingKeyEncrypted,
}
}, options)

return {
...mutation,
generateVotingKeys: mutation.mutate,
}
}

// NOTE: keyboard horizontal padding is 0, yet bottom must respect safe-area-view
export const Padding = ({style, ...props}: ViewProps) => {
const styles = useStyles()
Expand All @@ -235,6 +288,14 @@ const useStyles = () => {
padding: {
...atoms.px_lg,
},
loading: {
...StyleSheet.absoluteFillObject,
backgroundColor: color.bg_color_max,
left: 0,
right: 0,
...atoms.align_center,
...atoms.justify_center,
},
})

return styles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,18 @@ export const ConfirmVotingTx = () => {
const styles = useStyles()
const strings = useStrings()
const {openModal, closeModal} = useModal()
const {votingKeyEncryptedChanged, pin} = useCatalyst()
const {catalystKeyHex, pin} = useCatalyst()
const navigateTo = useNavigateTo()

const onNext = () => {
navigateTo.qrCode()
}

if (pin === null) throw new Error('pin cannot be null')
if (catalystKeyHex === null) throw new Error('catalystKeyHex cannot be null')

const {wallet, meta} = useSelectedWallet()
const votingRegTx = useVotingRegTx(
{wallet, pin, supportsCIP36, addressMode: meta.addressMode},
{onSuccess: ({votingKeyEncrypted}) => votingKeyEncryptedChanged(votingKeyEncrypted)},
)
const votingRegTx = useVotingRegTx({wallet, supportsCIP36, catalystKeyHex, addressMode: meta.addressMode})

const [useUSB, setUseUSB] = useState(false)

Expand Down
15 changes: 2 additions & 13 deletions apps/wallet-mobile/src/yoroi-wallets/cardano/cardano-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ import {Cardano, CardanoMobile} from '../wallets'
import {AccountManager, accountManagerMaker, Addresses} from './account-manager/account-manager'
import * as legacyApi from './api/api'
import {calcLockedDeposit} from './assetUtils'
import {encryptWithPassword} from './catalyst/catalystCipher'
import {generatePrivateKeyForCatalyst} from './catalyst/catalystUtils'
import {createSwapCancellationLedgerPayload} from './common/signatureUtils'
import {cardanoConfig} from './constants/cardano-config'
import * as MAINNET from './constants/mainnet/constants'
Expand Down Expand Up @@ -439,23 +437,18 @@ export const makeCardanoWallet = (
}

async createVotingRegTx({
pin,
supportsCIP36,
addressMode,
catalystKeyHex,
}: {
pin: string
supportsCIP36: boolean
addressMode: Wallet.AddressMode
catalystKeyHex: string
}) {
if (implementationConfig.features.staking) {
const bytes = await generatePrivateKeyForCatalyst()
.then((key) => key.toRawKey())
.then((key) => key.asBytes())

const primaryTokenId = this.primaryTokenInfo.id

const catalystKeyHex = Buffer.from(bytes).toString('hex')

try {
const time = await this.checkServerStatus()
.then(({serverTime}) => serverTime || Date.now())
Expand Down Expand Up @@ -521,11 +514,7 @@ export const makeCardanoWallet = (
nonce,
}

const password = Buffer.from(pin.split('').map(Number))
const catalystKeyEncrypted = await encryptWithPassword(password, bytes)

return {
votingKeyEncrypted: catalystKeyEncrypted,
votingRegTx: await yoroiUnsignedTx({
unsignedTx,
networkConfig: NETWORK_CONFIG,
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ export interface YoroiWallet {

// Voting
createVotingRegTx(params: {
pin: string
supportsCIP36: boolean
addressMode: Wallet.AddressMode
}): Promise<{votingRegTx: YoroiUnsignedTx; votingKeyEncrypted: string}>
catalystKeyHex: string
}): Promise<{votingRegTx: YoroiUnsignedTx}>
fetchFundInfo(): Promise<FundInfoResponse>

// Staking
Expand Down
11 changes: 5 additions & 6 deletions apps/wallet-mobile/src/yoroi-wallets/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,30 +277,29 @@ export const useWithdrawalTx = (

export type VotingRegTxAndEncryptedKey = {
votingRegTx: YoroiUnsignedTx
votingKeyEncrypted: string
}

export const useVotingRegTx = (
{
wallet,
pin,
catalystKeyHex,
supportsCIP36,
addressMode,
}: {wallet: YoroiWallet; pin: string; supportsCIP36: boolean; addressMode: Wallet.AddressMode},
}: {wallet: YoroiWallet; catalystKeyHex: string; supportsCIP36: boolean; addressMode: Wallet.AddressMode},
options?: UseQueryOptions<
VotingRegTxAndEncryptedKey,
Error,
VotingRegTxAndEncryptedKey,
[string, 'voting-reg-tx', string]
[string, string, 'voting-reg-tx', string]
>,
) => {
const query = useQuery({
...options,
retry: false,
cacheTime: 0,
suspense: true,
queryKey: [wallet.id, 'voting-reg-tx', JSON.stringify({supportsCIP36})],
queryFn: () => wallet.createVotingRegTx({pin, supportsCIP36, addressMode}),
queryKey: [catalystKeyHex, wallet.id, 'voting-reg-tx', JSON.stringify({supportsCIP36})],
queryFn: () => wallet.createVotingRegTx({catalystKeyHex, supportsCIP36, addressMode}),
})

if (!query.data) throw new Error('invalid state')
Expand Down
5 changes: 5 additions & 0 deletions packages/staking/src/catalyst/translators/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export function CatalystProvider({
type: CatalystActionType.VotingKeyEncryptedChanged,
votingKeyEncrypted,
}),
catalystKeyHexChanged: (catalystKeyHex: CatalystState['catalystKeyHex']) =>
dispatch({
type: CatalystActionType.CatalystKeyHexChanged,
catalystKeyHex,
}),
reset: () =>
dispatch({
type: CatalystActionType.Reset,
Expand Down
26 changes: 23 additions & 3 deletions packages/staking/src/catalyst/translators/state.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,45 @@ describe('State Actions', () => {
})

it('VotingKeyEncryptedChanged', () => {
const votingKeyEncrypted = 'super-complex-voting-key'
const action: CatalystAction = {
type: CatalystActionType.VotingKeyEncryptedChanged,
votingKeyEncrypted: 'very-strong-key',
votingKeyEncrypted,
}

const state = catalystReducer(catalystDefaultState, action)
expect(state.votingKeyEncrypted).toBe('very-strong-key')
expect(state.votingKeyEncrypted).toBe(votingKeyEncrypted)
})

it('CatalystKeyHexChanged', () => {
const catalystKeyHex = 'super-complex-hex'
const action: CatalystAction = {
type: CatalystActionType.CatalystKeyHexChanged,
catalystKeyHex,
}

const state = catalystReducer(catalystDefaultState, action)
expect(state.catalystKeyHex).toBe(catalystKeyHex)
})

it('Reset', () => {
const votingKeyEncrypted = 'super-complex-voting-key'
const catalystKeyHex = 'super-complex-catalyst-key'
const action: CatalystAction = {
type: CatalystActionType.Reset,
}

const state = catalystReducer(
{...catalystDefaultState, pin: '1234', votingKeyEncrypted: 'qwert'},
{
...catalystDefaultState,
pin: '1234',
votingKeyEncrypted,
catalystKeyHex,
},
action,
)
expect(state.pin).toBe(null)
expect(state.votingKeyEncrypted).toBe(null)
expect(state.catalystKeyHex).toBe(null)
})
})
16 changes: 16 additions & 0 deletions packages/staking/src/catalyst/translators/state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ export const catalystReducer = (
draft.votingKeyEncrypted = action.votingKeyEncrypted
break

case CatalystActionType.CatalystKeyHexChanged:
draft.catalystKeyHex = action.catalystKeyHex
break

case CatalystActionType.Reset:
draft.pin = catalystDefaultState.pin
draft.votingKeyEncrypted = catalystDefaultState.votingKeyEncrypted
draft.catalystKeyHex = catalystDefaultState.catalystKeyHex
break

default:
Expand All @@ -29,13 +34,15 @@ export const catalystDefaultState: Readonly<CatalystState> = freeze(
{
pin: null,
votingKeyEncrypted: null,
catalystKeyHex: null,
},
true,
)

export type CatalystState = {
pin: string | null
votingKeyEncrypted: string | null
catalystKeyHex: string | null
}

export type CatalystAction =
Expand All @@ -44,13 +51,18 @@ export type CatalystAction =
type: CatalystActionType.VotingKeyEncryptedChanged
votingKeyEncrypted: CatalystState['votingKeyEncrypted']
}
| {
type: CatalystActionType.CatalystKeyHexChanged
catalystKeyHex: CatalystState['catalystKeyHex']
}
| {
type: CatalystActionType.Reset
}

export enum CatalystActionType {
PinChanged = 'pinChanged',
VotingKeyEncryptedChanged = 'votingKeyEncryptedChanged',
CatalystKeyHexChanged = 'catalystKeyHexChanged',
Reset = 'reset',
}

Expand All @@ -59,6 +71,9 @@ export type CatalystActions = {
votingKeyEncryptedChanged: (
votingKeyEncrypted: CatalystState['votingKeyEncrypted'],
) => void
catalystKeyHexChanged: (
catalystKeyHex: CatalystState['catalystKeyHex'],
) => void
reset: () => void
}

Expand All @@ -67,6 +82,7 @@ export const initialCatalystContext: CatalystState & CatalystActions = freeze(
...catalystDefaultState,
pinChanged: missingInit,
votingKeyEncryptedChanged: missingInit,
catalystKeyHexChanged: missingInit,
reset: missingInit,
},
true,
Expand Down

0 comments on commit 266894e

Please sign in to comment.