Skip to content

Commit

Permalink
feat: process stacks ledger keys
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo authored and kyranjamie committed Nov 2, 2023
1 parent bd77be5 commit 7e71b91
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { bytesToHex } from '@stacks/common';
import StacksApp from '@zondax/ledger-stacks';
import ecdsaFormat from 'ecdsa-sig-formatter';

import { getIdentityDerivationPath } from '../../utils/stacks-ledger-utils';
import { getIdentityDerivationPath } from '@shared/crypto/stacks/stacks.utils';

function reformatDerSignatureToJose(derSignature: Uint8Array) {
// Stacks authentication uses `ES256k`, however `ecdsa-sig-formatter` doesn't
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ function LedgerRequestBitcoinKeys() {
navigate('/', { replace: true });
},
async pullKeysFromDevice(app) {
const { keys } = await pullBitcoinKeysFromLedgerDevice(app)({
const { keys } = await pullBitcoinKeysFromLedgerDevice(
app,
latestDeviceResponse?.targetId
)({
network: bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.network),
onRequestKey(index) {
if (index <= 4) {
Expand All @@ -47,15 +50,6 @@ function LedgerRequestBitcoinKeys() {
},
});
dispatch(bitcoinKeysSlice.actions.addKeys(keys));
const targetId = latestDeviceResponse?.targetId;

if (targetId) {
dispatch(
bitcoinKeysSlice.actions.addTargetId({
targetId,
})
);
}
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ interface PullBitcoinKeysFromLedgerDeviceArgs {
onRequestKey?(keyIndex: number): void;
network: NetworkModes;
}
export function pullBitcoinKeysFromLedgerDevice(bitcoinApp: BitcoinApp) {
export function pullBitcoinKeysFromLedgerDevice(bitcoinApp: BitcoinApp, targetId = '') {
return async ({ onRequestKey, network }: PullBitcoinKeysFromLedgerDeviceArgs) => {
const amountOfKeysToExtractFromDevice = 5;
const fingerprint = await bitcoinApp.getMasterFingerprint();
const keys: { id: string; path: string; policy: string }[] = [];
const keys: { id: string; path: string; policy: string; targetId: string }[] = [];
for (let accountIndex = 0; accountIndex < amountOfKeysToExtractFromDevice; accountIndex++) {
onRequestKey?.(accountIndex);
const { path, policy } = await getNativeSegwitExtendedPublicKey({
Expand All @@ -67,7 +67,7 @@ export function pullBitcoinKeysFromLedgerDevice(bitcoinApp: BitcoinApp) {
network,
accountIndex,
});
keys.push({ id: createWalletIdDecoratedPath(path, 'default'), path, policy });
keys.push({ id: createWalletIdDecoratedPath(path, 'default'), path, policy, targetId });
}
for (let accountIndex = 0; accountIndex < amountOfKeysToExtractFromDevice; accountIndex++) {
onRequestKey?.(accountIndex + 5);
Expand All @@ -77,7 +77,7 @@ export function pullBitcoinKeysFromLedgerDevice(bitcoinApp: BitcoinApp) {
network,
accountIndex,
});
keys.push({ id: createWalletIdDecoratedPath(path, 'default'), path, policy });
keys.push({ id: createWalletIdDecoratedPath(path, 'default'), path, policy, targetId });
}
await delay(250);
return { status: 'success', keys };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,10 @@ function LedgerRequestStacksKeys() {
resp.publicKeys.map(keys => ({
...keys,
id: keys.path.replace('m', defaultWalletKeyId),
targetId: latestDeviceResponse?.targetId || '',
}))
)
);
const targetId = latestDeviceResponse?.targetId;

if (targetId) {
dispatch(
stacksKeysSlice.actions.addTargetId({
targetId,
})
);
}
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { bytesToHex } from '@noble/hashes/utils';
import * as secp from '@noble/secp256k1';
import StacksApp from '@zondax/ledger-stacks';

import {
getIdentityDerivationPath,
getStxDerivationPath,
} from '@shared/crypto/stacks/stacks.utils';

import { delay } from '@app/common/utils';

import {
StacksAppKeysResponseItem,
getIdentityDerivationPath,
getStxDerivationPath,
requestPublicKeyForStxAccount,
} from '../../utils/stacks-ledger-utils';

Expand Down
1 change: 1 addition & 0 deletions src/app/features/ledger/utils/bitcoin-ledger-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface BitcoinLedgerAccountDetails {
id: string;
path: string;
policy: string;
targetId: string;
}

export async function connectLedgerBitcoinApp() {
Expand Down
16 changes: 1 addition & 15 deletions src/app/features/ledger/utils/stacks-ledger-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import StacksApp, { LedgerError, ResponseSign, ResponseVersion } from '@zondax/ledger-stacks';
import { compare } from 'compare-versions';

import { getStxDerivationPath, stxDerivationWithAccount } from '@shared/crypto/stacks/stacks.utils';
import { RouteUrls } from '@shared/route-urls';

import {
Expand All @@ -19,21 +20,6 @@ import {
} from './generic-ledger-utils';
import { versionObjectToVersionString } from './generic-ledger-utils';

const stxDerivationWithAccount = `m/44'/5757'/0'/0/{account}`;

const stxIdentityDerivationWithAccount = `m/888'/0'/{account}'`;

function getAccountIndexFromDerivationPathFactory(derivationPath: string) {
return (account: number) => derivationPath.replace('{account}', account.toString());
}

export const getStxDerivationPath =
getAccountIndexFromDerivationPathFactory(stxDerivationWithAccount);

export const getIdentityDerivationPath = getAccountIndexFromDerivationPathFactory(
stxIdentityDerivationWithAccount
);

export function requestPublicKeyForStxAccount(app: StacksApp) {
return async (index: number) =>
app.getAddressAndPubKey(
Expand Down
6 changes: 2 additions & 4 deletions src/app/store/ledger/ledger-chain-key-storage-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { defaultWalletKeyId } from '@shared/utils';

interface RequiresId {
id: string;
targetId: string;
}

export function generateLedgerChainKeyStorageSlice<KeyDetails extends RequiresId>(name: string) {
type KeyDetailsWithWalletId = KeyDetails & { walletId: string };

const adapter = createEntityAdapter<KeyDetailsWithWalletId>();

const initialState = { targetId: '', ...adapter.getInitialState() };
const initialState = adapter.getInitialState();

const slice = createSlice({
name: name + 'Keys',
Expand All @@ -24,9 +25,6 @@ export function generateLedgerChainKeyStorageSlice<KeyDetails extends RequiresId
payload.map(key => ({ ...key, walletId: defaultWalletKeyId }))
);
},
addTargetId(state, { payload }: PayloadAction<{ targetId: string }>) {
return { ...state, targetId: payload.targetId };
},
signOut(state) {
adapter.removeAll(state as any);
},
Expand Down
3 changes: 2 additions & 1 deletion src/app/store/ledger/ledger.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function useHasLedgerKeys() {

export function useLedgerDeviceTargetId() {
return useSelector(
(state: RootState) => state.ledger.stacks.targetId || state.ledger.bitcoin.targetId
(state: RootState) =>
state.ledger.stacks.entities[0]?.targetId || state.ledger.bitcoin.entities[0]?.targetId || ''
);
}
2 changes: 1 addition & 1 deletion src/app/store/ledger/stacks/stacks-key.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function selectStacksKeysSlice(state: RootState) {

// ts-unused-exports:disable-next-line
export const { slice: stacksKeysSlice, adapter } = generateLedgerChainKeyStorageSlice<
StacksAppKeysResponseItem & { id: string }
StacksAppKeysResponseItem & { id: string; targetId: string }
>('stacks');

const selectors = adapter.getSelectors(selectStacksKeysSlice);
Expand Down
13 changes: 13 additions & 0 deletions src/shared/crypto/stacks/stacks.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const stxDerivationWithAccount = `m/44'/5757'/0'/0/{account}`;
const stxIdentityDerivationWithAccount = `m/888'/0'/{account}'`;

function getAccountIndexFromDerivationPathFactory(derivationPath: string) {
return (account: number) => derivationPath.replace('{account}', account.toString());
}

export const getStxDerivationPath =
getAccountIndexFromDerivationPathFactory(stxDerivationWithAccount);

export const getIdentityDerivationPath = getAccountIndexFromDerivationPathFactory(
stxIdentityDerivationWithAccount
);
55 changes: 40 additions & 15 deletions src/shared/storage/redux-pesist.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { PersistConfig, createMigrate, getStoredState } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';

import { getStxDerivationPath } from '@shared/crypto/stacks/stacks.utils';
import { defaultWalletKeyId } from '@shared/utils';

import type { RootState } from '@app/store';

import { analytics } from '../utils/analytics';
Expand Down Expand Up @@ -97,30 +100,53 @@ async function migrateToUsingNoSerialization() {
return storage;
}

function processStacksLedgerKeys(stacksKeys: any) {
const newStacksKeys = stacksKeys.entities.default.publicKeys.reduce(
(acc: { ids: string[]; entities: Record<string, any> }, item: any, index: number) => {
const path = getStxDerivationPath(index);
const id = path.replace('m', defaultWalletKeyId);

acc.ids.push(id);
acc.entities[id] = {
...item,
path,
id,
walletId: 'default',
targetId: '',
};

return acc;
},
{ ids: [], entities: {} }
);

return newStacksKeys;
}

async function migrateToRenameKeysStoreModule(state: Promise<any>) {
const resolvedState = await Promise.resolve(state);

const newStore = JSON.parse(
JSON.stringify({
...resolvedState,
softwareKeys: resolvedState.keys,
ledger: {
...resolvedState.ledger,
},
})
);
const newStore = {
...resolvedState,
softwareKeys: resolvedState.keys,
ledger: {
...resolvedState.ledger,
},
};

// add stacks ledger keys to new place
if (resolvedState.keys.entities.default.type === 'ledger') {
newStore.ledger = { ...resolvedState.ledger, stacks: resolvedState.keys };
if (resolvedState.keys?.entities.default.type === 'ledger') {
newStore.ledger = {
...resolvedState.ledger,
stacks: processStacksLedgerKeys(resolvedState.keys),
};
}

// add default bitcoin ledger state
if (!newStore.ledger.bitcoin) {
newStore.ledger.bitcoin = {
ids: [],
entities: {},
targetId: '',
};
}

Expand All @@ -129,7 +155,6 @@ async function migrateToRenameKeysStoreModule(state: Promise<any>) {
newStore.ledger.stacks = {
ids: [],
entities: {},
targetId: '',
};
}

Expand All @@ -145,14 +170,14 @@ interface UntypedDeserializeOption {
export const persistConfig: PersistConfig<RootState> & UntypedDeserializeOption = {
key: 'root',
stateReconciler: autoMergeLevel2,
version: 1,
version: 2,
storage,
serialize: false,
migrate: createMigrate({
0: async () => {
return migrateToUsingNoSerialization();
},
1: async (state: any) => {
2: async (state: any) => {
return migrateToRenameKeysStoreModule(state);
},
debug: true,
Expand Down
Loading

0 comments on commit 7e71b91

Please sign in to comment.