All files / src/crypto/encryption decrypt-ecies.ts

94.44% Statements 34/36
81.81% Branches 9/11
100% Functions 2/2
94.11% Lines 32/34

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 59 60 61 62 63 64 65 66 67 6841x 41x 41x 41x 41x       41x 13x     13x 13x 416x   13x                             41x 14x   14x 13x 13x   13x 13x 13x       13x 12x 1x 1x         13x 13x 13x   13x 1x   12x   12x 10x   2x      
import { getSharedSecret } from '@noble/secp256k1';
import { hexToBytes, base64ToBytes, concatByteArrays } from 'micro-stacks/common';
import { aes256CbcDecrypt } from 'micro-stacks/crypto-aes';
import { sharedSecretToKeys } from '../common/shared-secret';
import { hmacSha256 } from 'micro-stacks/crypto-hmac-sha';
 
import type { DecryptECIESOptions } from '../common/types';
 
export function equalConstTime(b1: Uint8Array, b2: Uint8Array) {
  Iif (b1.length !== b2.length) {
    return false;
  }
  let res = 0;
  for (let i = 0; i < b1.length; i++) {
    res |= b1[i] ^ b2[i]; // jshint ignore:line
  }
  return res === 0;
}
 
/**
 * Decrypt content encrypted using ECIES
 *  * @param options {DecryptECIESOptions}
 *  iv (initialization vector), cipherText (cipher text),
 *  mac (message authentication code), ephemeralPublicKey
 *  wasString (boolean indicating with or not to return a byte array or string on decrypt)
 * @return plaintext
 * @throws {Error} if unable to decrypt
 * @private
 * @ignore
 */
 
export async function decryptECIES(options: DecryptECIESOptions): Promise<Uint8Array | string> {
  const { privateKey, cipherObject } = options;
 
  if (!cipherObject.ephemeralPK) throw Error('No ephemeralPK found in cipher object');
  const ephemeralPK = cipherObject.ephemeralPK;
  let sharedSecret = getSharedSecret(privateKey, ephemeralPK, true);
  // Trim the compressed mode prefix byte
  sharedSecret = sharedSecret.slice(1);
  const sharedKeys = sharedSecretToKeys(sharedSecret);
  const ivBuffer = hexToBytes(cipherObject.iv);
 
  let cipherTextBuffer: Uint8Array;
 
  if (!cipherObject.cipherTextEncoding || cipherObject.cipherTextEncoding === 'hex') {
    cipherTextBuffer = hexToBytes(cipherObject.cipherText);
  } else if (cipherObject.cipherTextEncoding === 'base64') {
    cipherTextBuffer = base64ToBytes(cipherObject.cipherText);
  } else E{
    throw new Error(`Unexpected cipherTextEncoding "${cipherObject.cipherText}"`);
  }
 
  const macData = concatByteArrays([ivBuffer, hexToBytes(ephemeralPK), cipherTextBuffer]);
  const actualMac = hmacSha256(sharedKeys.hmacKey, macData);
  const expectedMac = hexToBytes(cipherObject.mac);
 
  if (!equalConstTime(expectedMac, actualMac)) {
    throw new Error('Decryption failed: failure in MAC check');
  }
  const plainText = await aes256CbcDecrypt(ivBuffer, sharedKeys.encryptionKey, cipherTextBuffer);
 
  if (cipherObject.wasString) {
    return new TextDecoder().decode(plainText);
  } else {
    return plainText;
  }
}