All files / src/wallet-sdk/encryption decrypt-mnemonic.ts

84.37% Statements 27/32
50% Branches 2/4
100% Functions 1/1
84.37% Lines 27/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 591x 1x 1x     1x 1x 1x 1x                   1x         3x 3x 3x 3x 3x 3x   3x 3x 3x 3x   3x 2x   2x 2x   2x       2x 2x           2x       2x    
import { bytesToHex, concatByteArrays, hexToBytes } from 'micro-stacks/common';
import { entropyToMnemonic, validateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
 
// micro-stacks crypto
import { aes128CbcDecrypt } from 'micro-stacks/crypto-aes';
import { hashSha256 } from 'micro-stacks/crypto-sha';
import { hmacSha256 } from 'micro-stacks/crypto-hmac-sha';
import { createPbkdf2 } from 'micro-stacks/crypto-pbkdf2';
 
/**
 * Decrypt an encrypted mnemonic phrase with a password.
 * @param encryptedMnemonic - Uint8Array or hex-encoded string of the encrypted mnemonic
 * @param password - Password for data
 * @return the raw mnemonic phrase
 * @private
 * @ignore
 */
export async function decryptMnemonic(
  encryptedMnemonic: Uint8Array | string,
  password: string
): Promise<string> {
  const dataBuffer =
    typeof encryptedMnemonic === 'string' ? hexToBytes(encryptedMnemonic) : encryptedMnemonic;
  const salt = dataBuffer.slice(0, 16);
  const hmacSig = dataBuffer.slice(16, 48); // 32 bytes
  const cipherText = dataBuffer.slice(48);
  const hmacPayload = concatByteArrays([salt, cipherText]);
  const pbkdf2 = await createPbkdf2();
 
  const keysAndIV = await pbkdf2.derive(password, salt, 100000, 48, 'sha512');
  const encKey = keysAndIV.slice(0, 16);
  const macKey = keysAndIV.slice(16, 32);
  const iv = keysAndIV.slice(32, 48);
 
  const decryptedResult = await aes128CbcDecrypt(iv, encKey, cipherText);
  const hmacDigest = hmacSha256(macKey, hmacPayload);
 
  const hmacSigHash = hashSha256(hmacSig);
  const hmacDigestHash = hashSha256(hmacDigest);
 
  Iif (bytesToHex(hmacSigHash) !== bytesToHex(hmacDigestHash))
    throw new Error('Wrong password (HMAC mismatch)');
 
  let mnemonic: string;
  try {
    mnemonic = entropyToMnemonic(decryptedResult, wordlist);
  } catch (error) {
    console.error('Error thrown by `entropyToMnemonic`');
    console.error(error);
    throw new Error('Wrong password (invalid plaintext)');
  }
  Iif (!validateMnemonic(mnemonic, wordlist)) {
    throw new Error('Wrong password (invalid plaintext)');
  }
 
  return mnemonic;
}