Skip to content

Commit

Permalink
feat: import wallet by sk
Browse files Browse the repository at this point in the history
  • Loading branch information
siandreev committed Feb 7, 2025
1 parent 301801a commit b9b64a4
Show file tree
Hide file tree
Showing 21 changed files with 741 additions and 104 deletions.
50 changes: 48 additions & 2 deletions packages/core/src/entries/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,28 @@ export class AccountTonTestnet extends TonMnemonic {
}
}

export class AccountTonSK extends TonMnemonic {
public readonly type = 'sk';

static create(params: {
id: AccountId;
name: string;
emoji: string;
auth: AuthPassword | AuthKeychain;
activeTonWalletId: WalletId;
tonWallets: TonWalletStandard[];
}) {
return new AccountTonSK(
params.id,
params.name,
params.emoji,
params.auth,
params.activeTonWalletId,
params.tonWallets
);
}
}

export class AccountTonWatchOnly extends Clonable implements IAccount {
public readonly type = 'watch-only';

Expand Down Expand Up @@ -655,7 +677,11 @@ export class AccountTonMultisig extends Clonable implements IAccount {
}
}

export type AccountVersionEditable = AccountTonMnemonic | AccountTonOnly | AccountTonTestnet;
export type AccountVersionEditable =
| AccountTonMnemonic
| AccountTonOnly
| AccountTonTestnet
| AccountTonSK;

export type AccountTonWalletStandard =
| AccountVersionEditable
Expand All @@ -670,6 +696,7 @@ export function isAccountVersionEditable(account: Account): account is AccountVe
case 'mnemonic':
case 'ton-only':
case 'testnet':
case 'sk':
return true;
case 'ledger':
case 'keystone':
Expand All @@ -690,6 +717,7 @@ export function isAccountTonWalletStandard(account: Account): account is Account
case 'ton-only':
case 'mam':
case 'testnet':
case 'sk':
return true;
case 'watch-only':
case 'ton-multisig':
Expand All @@ -705,6 +733,7 @@ export function isAccountCanManageMultisigs(account: Account): boolean {
case 'ton-only':
case 'mam':
case 'ledger':
case 'sk':
return true;
case 'watch-only':
case 'ton-multisig':
Expand All @@ -723,6 +752,7 @@ export function isMnemonicAndPassword(
case 'mam':
case 'mnemonic':
case 'testnet':
case 'sk':
return true;
case 'ton-only':
case 'ledger':
Expand All @@ -746,6 +776,7 @@ export function getNetworkByAccount(account: Account): Network {
case 'watch-only':
case 'ton-multisig':
case 'keystone':
case 'sk':
return Network.MAINNET;
default:
assertUnreachable(account);
Expand All @@ -770,6 +801,7 @@ export function isAccountTronCompatible(
case 'watch-only':
case 'ton-multisig':
case 'keystone':
case 'sk':
return false;
default:
return assertUnreachable(account);
Expand All @@ -787,6 +819,7 @@ export function isAccountBip39(account: Account) {
case 'watch-only':
case 'ton-multisig':
case 'keystone':
case 'sk':
return false;
default:
return assertUnreachable(account);
Expand All @@ -809,7 +842,8 @@ const prototypes = {
'ton-only': AccountTonOnly.prototype,
'watch-only': AccountTonWatchOnly.prototype,
mam: AccountMAM.prototype,
'ton-multisig': AccountTonMultisig.prototype
'ton-multisig': AccountTonMultisig.prototype,
sk: AccountTonSK.prototype
} as const;

export function bindAccountToClass(accountStruct: Account): void {
Expand Down Expand Up @@ -844,3 +878,15 @@ export type AccountsFolderStored = {
name: string;
lastIsOpened: boolean;
};

export type AccountSecretMnemonic = {
type: 'mnemonic';
mnemonic: string[];
};

export type AccountSecretSK = {
type: 'sk';
sk: string;
};

export type AccountSecret = AccountSecretMnemonic | AccountSecretSK;
1 change: 1 addition & 0 deletions packages/core/src/entries/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ function accountAndWalletToString(account: Account, walletId: WalletId): string
case 'ton-only':
case 'mnemonic':
case 'testnet':
case 'sk':
if (account.allTonWallets.length === 1) {
return baseInfo;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/entries/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type MnemonicType = 'ton' | 'bip39';

export interface AuthPassword {
kind: 'password';
encryptedMnemonic: string;
encryptedSecret: string;
}

export interface AuthKeychain {
Expand Down
26 changes: 25 additions & 1 deletion packages/core/src/service/accountsStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export class AccountsStorage {
} else {
state.forEach(bindAccountToClass);
}

await this.migrateAccountSecret(state);
return state ?? defaultAccountState;
};

Expand Down Expand Up @@ -190,6 +192,28 @@ export class AccountsStorage {
)?.id || null
);
};

private migrateAccountSecret = async (accounts: Account[] | null) => {
if (!accounts) {
return;
}
let needUpdate = false;

accounts.forEach(account => {
if ('auth' in account && account.auth.kind === 'password') {
if ((account.auth as unknown as { encryptedMnemonic: string }).encryptedMnemonic) {
account.auth.encryptedSecret = (
account.auth as unknown as { encryptedMnemonic: string }
).encryptedMnemonic;
needUpdate = true;
}
}
});

if (needUpdate) {
await this.setAccounts(accounts);
}
};
}

export const accountsStorage = (storage: IStorage): AccountsStorage => new AccountsStorage(storage);
Expand Down Expand Up @@ -237,7 +261,7 @@ async function migrateToAccountsState(storage: IStorage): Promise<AccountsState

auth = {
kind: walletAuth.kind,
encryptedMnemonic
encryptedSecret: encryptedMnemonic
};
} else if (walletAuth.kind === 'keychain') {
auth = {
Expand Down
61 changes: 53 additions & 8 deletions packages/core/src/service/mnemonicService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,64 @@ import {
mnemonicToPrivateKey,
mnemonicValidate as validateStandardTonMnemonic
} from '@ton/crypto';
import { AuthPassword, MnemonicType } from '../entries/password';
import { decrypt } from './cryptoService';
import { MnemonicType } from '../entries/password';
import { decrypt, encrypt } from './cryptoService';
import { mnemonicToSeed, validateMnemonic as validBip39Mnemonic } from 'bip39';
import { deriveED25519Path } from './ed25519';
import { assertUnreachable } from '../utils/types';
import { AccountSecret } from '../entries/account';

export const decryptWalletMnemonic = async (state: { auth: AuthPassword }, password: string) => {
const mnemonic = (await decrypt(state.auth.encryptedMnemonic, password)).split(' ');
const isValid = await seeIfMnemonicValid(mnemonic);
if (!isValid) {
throw new Error('Wallet mnemonic not valid');
export const decryptWalletSecret = async (
encryptedSecret: string,
password: string
): Promise<AccountSecret> => {
const secret = await decrypt(encryptedSecret, password);
return walletSecretFromString(secret);

throw new Error('Wallet secret not valid');
};

export const walletSecretFromString = async (secret: string): Promise<AccountSecret> => {
const isValidMnemonic = await seeIfMnemonicValid(secret.split(' '));
if (isValidMnemonic) {
return {
type: 'mnemonic',
mnemonic: secret.split(' ')
};
}
return mnemonic;

if (isValidSK(secret)) {
return {
type: 'sk',
sk: secret
};
}

throw new Error('Wallet secret not valid');
};

export const walletSecretToString = (secret: AccountSecret): string => {
if (secret.type === 'mnemonic') {
return secret.mnemonic.join(' ');
}

if (secret.type === 'sk') {
return secret.sk;
}

assertUnreachable(secret);
};

export const encryptWalletSecret = async (
secret: AccountSecret,
password: string
): Promise<string> => {
const stringSecret = walletSecretToString(secret);
return encrypt(stringSecret, password);
};

export const isValidSK = (sk: string) => {
return /^[0-9a-fA-F]{128}$/.test(sk);
};

export const seeIfMnemonicValid = async (mnemonic: string[]) => {
Expand Down
17 changes: 7 additions & 10 deletions packages/core/src/service/passwordService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { IStorage } from '../Storage';
import { AccountTonMnemonic, isMnemonicAndPassword } from '../entries/account';
import { AuthPassword } from '../entries/password';
import { AccountsStorage } from './accountsStorage';
import { decrypt, encrypt } from './cryptoService';
import { decryptWalletMnemonic, seeIfMnemonicValid } from './mnemonicService';
import { decryptWalletSecret, encryptWalletSecret } from './mnemonicService';

export class PasswordStorage {
private readonly accountsStorage: AccountsStorage;
Expand All @@ -24,10 +23,8 @@ export class PasswordStorage {
throw new Error('None wallet has a password auth');
}

const mnemonic = (
await decrypt((accToCheck.auth as AuthPassword).encryptedMnemonic, password)
).split(' ');
return await seeIfMnemonicValid(mnemonic);
await decryptWalletSecret((accToCheck.auth as AuthPassword).encryptedSecret, password);
return true;
} catch (e) {
console.error(e);
return false;
Expand All @@ -46,12 +43,12 @@ export class PasswordStorage {

const updatedAccounts = await Promise.all(
accounts.map(async acc => {
const mnemonic = await decryptWalletMnemonic(
acc as { auth: AuthPassword },
const accountSecret = await decryptWalletSecret(
(acc.auth as AuthPassword).encryptedSecret,
oldPassword
);
(acc.auth as AuthPassword).encryptedMnemonic = await encrypt(
mnemonic.join(' '),
(acc.auth as AuthPassword).encryptedSecret = await encryptWalletSecret(
accountSecret,
newPassword
);
return acc.clone();
Expand Down
Loading

0 comments on commit b9b64a4

Please sign in to comment.