diff --git a/packages/walletkit/package.json b/packages/walletkit/package.json index e78c81721..8b8c5b4fb 100644 --- a/packages/walletkit/package.json +++ b/packages/walletkit/package.json @@ -61,6 +61,7 @@ "@tonconnect/protocol": "^2.3.0", "@tonconnect/sdk": "^3.2.0", "@truecarry/tlb-abi": "^0.2.0", + "bip39": "^3.1.0", "lru-cache": "^11.1.0", "node-hid": "^3.2.0", "vite": "^7.1.3" diff --git a/packages/walletkit/src/utils/mnemonic.ts b/packages/walletkit/src/utils/mnemonic.ts index aff0a7d9b..4cc14d3f5 100644 --- a/packages/walletkit/src/utils/mnemonic.ts +++ b/packages/walletkit/src/utils/mnemonic.ts @@ -1,18 +1,31 @@ -import { mnemonicToWalletKey, mnemonicNew } from '@ton/crypto'; +import { mnemonicToWalletKey, mnemonicNew, keyPairFromSeed, deriveEd25519Path } from '@ton/crypto'; +import { mnemonicToSeed as bip39MnemonicToSeed } from 'bip39'; import { WalletKitError, ERROR_CODES } from '../errors'; +async function bip39ToPrivateKey(mnemonic: string[]) { + const seed = await bip39MnemonicToSeed(mnemonic.join(' ')); + const TON_DERIVATION_PATH = [44, 607, 0]; + const seedContainer = await deriveEd25519Path(seed, TON_DERIVATION_PATH); + return keyPairFromSeed(seedContainer.subarray(0, 32)); +} + +/** + * Convert a mnemonic to a key pair + * @param mnemonic - The mnemonic to convert, can be an array of strings or a string. 12 or 24 words + * @param mnemonicType - The type of mnemonic to convert, can be 'ton' or 'bip39' + * @returns The key pair + */ export async function MnemonicToKeyPair( mnemonic: string | string[], mnemonicType: 'ton' | 'bip39' = 'ton', ): Promise<{ publicKey: Uint8Array; secretKey: Uint8Array }> { const mnemonicArray = Array.isArray(mnemonic) ? mnemonic : mnemonic.split(' '); - if (mnemonicArray.length !== 24) { + + if (mnemonicArray.length !== 12 && mnemonicArray.length !== 24) { throw new WalletKitError( ERROR_CODES.VALIDATION_ERROR, - `Invalid mnemonic length: expected 24 words, got ${mnemonicArray.length}`, - undefined, - { receivedLength: mnemonicArray.length, expectedLength: 24 }, + `Invalid mnemonic length: expected 12 or 24 words, got ${mnemonicArray.length}`, ); } @@ -24,7 +37,13 @@ export async function MnemonicToKeyPair( }; } - // TODO bip39 support + if (mnemonicType === 'bip39') { + const key = await bip39ToPrivateKey(mnemonicArray); + return { + publicKey: new Uint8Array(key.publicKey), + secretKey: new Uint8Array(key.secretKey), + }; + } throw new WalletKitError( ERROR_CODES.VALIDATION_ERROR, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7682fdef0..487ec5d9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,6 +242,9 @@ importers: '@truecarry/tlb-abi': specifier: ^0.2.0 version: 0.2.0(@ton/core@0.61.0(@ton/crypto@3.3.0)) + bip39: + specifier: ^3.1.0 + version: 3.1.0 lru-cache: specifier: ^11.1.0 version: 11.1.0 @@ -2380,6 +2383,9 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bip39@3.1.0: + resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -8798,6 +8804,10 @@ snapshots: dependencies: file-uri-to-path: 1.0.0 + bip39@3.1.0: + dependencies: + '@noble/hashes': 1.8.0 + bl@4.1.0: dependencies: buffer: 5.7.1