Home Reference Source Test

src/hasher/sm3.mjs

'use strict';

import Hasher32be from "./hasher32be";
import {rotateLeft} from "../tools/tools";


/**
 * Calculates [SM3](https://tools.ietf.org/id/draft-oscca-cfrg-sm3-02.html) hash
 *
 * @example <caption>Calculates SM3 hash from string "message" - ES6 style</caption>
 * import Sm3 from "crypto-api/src/hasher/sm3";
 * import {toHex} from "crypto-api/src/encoder/hex";
 *
 * let hasher = new Sm3();
 * hasher.update('message');
 * console.log(toHex(hasher.finalize()));
 *
 * @example <caption>Calculates SM3 hash from UTF string "message" - ES6 style</caption>
 * import Sm3 from "crypto-api/src/hasher/sm3";
 * import {toHex} from "crypto-api/src/encoder/hex";
 * import {fromUtf} from "crypto-api/src/encoder/utf";
 *
 * let hasher = new Sm3();
 * hasher.update(fromUtf('message'));
 * console.log(toHex(hasher.finalize()));
 *
 * @example <caption>Calculates SM3 hash from string "message" - ES5 style</caption>
 * <script src="https://nf404.github.io/crypto-api/crypto-api.min.js"></script>
 * <script>
 *   var hasher = CryptoApi.getHasher('sm3');
 *   hasher.update('message');
 *   console.log(CryptoApi.encoder.toHex(hasher.finalize()));
 * </script>
 *
 * @example <caption>Calculates SM3 hash from UTF string "message" - ES5 style</caption>
 * <script src="https://nf404.github.io/crypto-api/crypto-api.min.js"></script>
 * <script>
 *   console.log(CryptoApi.hash('sm3', 'message'));
 * </script>
 */
class Sm3 extends Hasher32be {
  /**
   * @param {Object} [options]
   * @param {number} [options.rounds=64] - Number of rounds (Must be greater than 16)
   * @param {number} [options.length=256] - Length of hash result
   */
  constructor(options) {
    options = options || {};
    options.length = options.length || 256;
    options.rounds = options.rounds || 64;
    super(options);

    /**
     * Working variable (only for speed optimization)
     * @private
     * @ignore
     * @type {number[]}
     */
    this.W = new Array(132);
  }

  /**
   * Reset hasher to initial state
   */
  reset() {
    super.reset();
    this.state.hash = [
      0x7380166f | 0, 0x4914b2b9 | 0, 0x172442d7 | 0, 0xda8a0600 | 0,
      0xa96f30bc | 0, 0x163138aa | 0, 0xe38dee4d | 0, 0xb0fb0e4e | 0
    ];
  }

  /**
   * @protected
   * @ignore
   * @param {number} x
   * @returns {number}
   */
  static p0 (x) {
    return x ^ rotateLeft(x, 9) ^ rotateLeft(x, 17)
  }

  /**
   * @protected
   * @ignore
   * @param {number} x
   * @returns {number}
   */
  static p1 (x) {
    return x ^ rotateLeft(x, 15) ^ rotateLeft(x, 23)
  }

  /**
   * @protected
   * @ignore
   * @param {number} i
   * @returns {number}
   */
  static tj (i) {
    return i < 16 ? 0x79cc4519 : 0x7a879d8a;
  }

  /**
   * @protected
   * @ignore
   * @param {number} i
   * @param {number} a
   * @param {number} b
   * @param {number} c
   * @returns {number}
   */
  static ffj(i, a, b, c) {
    return i < 16 ? a ^ b ^ c : (a & b) | (a & c) | (b & c)
  }

  /**
   * @protected
   * @ignore
   * @param {number} i
   * @param {number} e
   * @param {number} f
   * @param {number} g
   * @returns {number}
   */
  static ggj(i, e, f, g) {
    return i < 16 ? e ^ f ^ g : (e & f) | (~e & g)
  }

  /**
   * Process ready blocks
   *
   * @protected
   * @ignore
   * @param {number[]} block - Block
   */
  processBlock(block) {
    // Working variables
    let a = this.state.hash[0] | 0;
    let b = this.state.hash[1] | 0;
    let c = this.state.hash[2] | 0;
    let d = this.state.hash[3] | 0;
    let e = this.state.hash[4] | 0;
    let f = this.state.hash[5] | 0;
    let g = this.state.hash[6] | 0;
    let h = this.state.hash[7] | 0;
    // Expand message
    for (let i = 0; i < 132; i++) {
      if (i < 16) {
        this.W[i] = block[i] | 0;
      } else if (i < 68) {
        this.W[i] = Sm3.p1(this.W[i - 16] ^ this.W[i - 9] ^ rotateLeft(this.W[i - 3], 15)) ^
          rotateLeft(this.W[i - 13], 7) ^ this.W[i - 6]
      } else {
        this.W[i] = this.W[i - 68] ^ this.W[i - 64]
      }
    }
    // Calculate hash
    for (let i = 0; i < this.options.rounds; i++) {
      let ss1 = rotateLeft((rotateLeft(a, 12) + e +  rotateLeft(Sm3.tj(i), i % 32)) | 0,7)
      let ss2 = ss1 ^ rotateLeft(a, 12)
      let tt1 = (Sm3.ffj(i, a, b, c) + d + ss2 + this.W[i + 68]) | 0
      let tt2 = (Sm3.ggj(i, e, f, g) + h + ss1 + this.W[i]) | 0

      d = c
      c = rotateLeft(b, 9)
      b = a
      a = tt1
      h = g
      g = rotateLeft(f, 19)
      f = e
      e = Sm3.p0(tt2)
    }

    this.state.hash[0] = this.state.hash[0] ^ a;
    this.state.hash[1] = this.state.hash[1] ^ b;
    this.state.hash[2] = this.state.hash[2] ^ c;
    this.state.hash[3] = this.state.hash[3] ^ d;
    this.state.hash[4] = this.state.hash[4] ^ e;
    this.state.hash[5] = this.state.hash[5] ^ f;
    this.state.hash[6] = this.state.hash[6] ^ g;
    this.state.hash[7] = this.state.hash[7] ^ h;
  }

  /**
   * Finalize hash and return result
   *
   * @returns {string}
   */
  finalize() {
    this.addPaddingISO7816(
      this.state.message.length < 56 ?
        56 - this.state.message.length | 0 :
        120 - this.state.message.length | 0);
    this.addLengthBits();
    this.process();
    return this.getStateHash((this.options.length / 32) | 0);
  }
}

export default Sm3;