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

96.15% Statements 25/26
90% Branches 9/10
100% Functions 1/1
96% Lines 24/25

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 6541x 41x 41x     41x 41x 41x                       41x 25x   25x   25x     22x 22x 22x   22x           22x 22x       22x 20x 2x 2x         22x               22x 2x     22x    
import { getPublicKey, getSharedSecret, utils } from '@noble/secp256k1';
import { bytesToHex, bytesToBase64, concatByteArrays } from 'micro-stacks/common';
import { getRandomBytes } from '../common/random-bytes';
 
import type { CipherObject, EncryptECIESOptions } from '../common/types';
import { aes256CbcEncrypt } from 'micro-stacks/crypto-aes';
import { sharedSecretToKeys } from '../common/shared-secret';
import { hmacSha256 } from 'micro-stacks/crypto-hmac-sha';
 
/**
 * Encrypt content to elliptic curve publicKey using ECIES
 * @return Object containing:
 *  iv (initialization vector, hex encoding),
 *  cipherText (cipher text either hex or base64 encoded),
 *  mac (message authentication code, hex encoded),
 *  ephemeral public key (hex encoded),
 *  wasString (boolean indicating with or not to return a buffer or string on decrypt)
 * @see https://cryptobook.nakov.com/asymmetric-key-ciphers/ecies-public-key-encryption
 */
export async function encryptECIES(options: EncryptECIESOptions): Promise<CipherObject> {
  const { publicKey, content, cipherTextEncoding = 'hex', wasString } = options;
  const ephemeralPrivateKey = utils.randomPrivateKey();
  const ephemeralPublicKey = getPublicKey(ephemeralPrivateKey, true);
 
  let sharedSecret = getSharedSecret(ephemeralPrivateKey, publicKey, true) as Uint8Array;
 
  // Trim the compressed mode prefix byte
  sharedSecret = sharedSecret.slice(1);
  const sharedKeys = sharedSecretToKeys(sharedSecret);
  const initializationVector = getRandomBytes(16);
 
  const cipherText = await aes256CbcEncrypt(
    initializationVector,
    sharedKeys.encryptionKey,
    content
  );
 
  const macData = concatByteArrays([initializationVector, ephemeralPublicKey, cipherText]);
  const mac = hmacSha256(sharedKeys.hmacKey, macData);
 
  let cipherTextString: string;
 
  if (!cipherTextEncoding || cipherTextEncoding === 'hex') {
    cipherTextString = bytesToHex(cipherText);
  } else if (cipherTextEncoding === 'base64') {
    cipherTextString = bytesToBase64(cipherText);
  } else E{
    throw new Error(`Unexpected cipherTextEncoding "${cipherTextEncoding}"`);
  }
 
  const result: CipherObject = {
    iv: bytesToHex(initializationVector),
    ephemeralPK: bytesToHex(ephemeralPublicKey),
    cipherText: cipherTextString,
    mac: bytesToHex(mac),
    wasString,
  };
 
  if (cipherTextEncoding && cipherTextEncoding !== 'hex') {
    result.cipherTextEncoding = cipherTextEncoding;
  }
 
  return result;
}