(********************************************************************)
(*                                                                  *)
(*  msgdigest.s7i  Message digest and secure hash algorithms.       *)
(*  Copyright (C) 2013, 2014, 2017 - 2021, 2024  Thomas Mertes      *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "bytedata.s7i";
include "bin32.s7i";
include "bin64.s7i";
include "float.s7i";
include "math.s7i";


const type: digestData16      is array [16] bin32;
const type: digestData64      is array [64] bin32;
const type: digestData80      is array [80] bin32;
const type: bin64digestData80 is array [80] bin64;


(**
 *  Compute a message digest with the MD4 message digest algorithm.
 *  The security of MD4 has been severely compromised. This function
 *  is provided for backward compatibility.
 *  @return the MD4 message digest (a string of 16 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: md4 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Specify the per-round shift amounts
    const array integer: shiftAmount is [] (
        3,  7, 11, 19,  3,  7, 11, 19,  3,  7, 11, 19,  3,  7, 11, 19,
        3,  5,  9, 13,  3,  5,  9, 13,  3,  5,  9, 13,  3,  5,  9, 13,
        3,  9, 11, 15,  3,  9, 11, 15,  3,  9, 11, 15,  3,  9, 11, 15);
    const array integer: idx is [] (
        1,  9,  5, 13,  3, 11,  7, 15,  2, 10,  6, 14,  4, 12,  8, 16);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var digestData16: m is digestData16.value;
    var integer: a0 is 16#67452301;   # a
    var integer: b0 is 16#efcdab89;   # b
    var integer: c0 is 16#98badcfe;   # c
    var integer: d0 is 16#10325476;   # d
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: f is bin32(0);
    var integer: g is 0;
    var bin32: temp is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit little-endian integer.
    message &:= bytes(8 * length, UNSIGNED, LE, 8);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit little-endian words.
      for index range 1 to 16 do
        m[index] := bin32(bytes2Int(message[wordIndex fixLen 4], UNSIGNED, LE));
        wordIndex +:= 4;
      end for;

      a := bin32(a0 mod 16#100000000);
      b := bin32(b0 mod 16#100000000);
      c := bin32(c0 mod 16#100000000);
      d := bin32(d0 mod 16#100000000);

      for index range 1 to 48 do
        if index <= 16 then
          f := d >< (b & (c >< d));
          g := index;
        elsif index <= 32 then
          f := bin32(ord(b & (c | d) | (c & d)) + 16#5a827999);
          g := (4 * index + 7) mod 15 + (index mdiv 32) * 15 + 1;
        else
          f := bin32(ord(b >< c >< d) + 16#6ed9eba1);
          g := idx[index - 32];
        end if;
        temp := d;
        d := c;
        c := b;
        b := rotLeft(bin32((ord(a) + ord(f) + ord(m[g])) mod 16#100000000),
                     shiftAmount[index]);
        a := temp;
      end for;

      # Add this chunk's hash to result so far:
      a0 +:= ord(a);
      b0 +:= ord(b);
      c0 +:= ord(c);
      d0 +:= ord(d);
    end while;

    # Produce the final hash value:
    digest := bytes(a0 mod 16#100000000, UNSIGNED, LE, 4) &
              bytes(b0 mod 16#100000000, UNSIGNED, LE, 4) &
              bytes(c0 mod 16#100000000, UNSIGNED, LE, 4) &
              bytes(d0 mod 16#100000000, UNSIGNED, LE, 4);
  end func;


# Use binary integer part of the sines of integers (Radians) as constants:
const func array integer: createMd5Table is func
  result
    var array integer: k is 64 times 0;
  local
    var integer: index is 0;
  begin
    for index range 1 to 64 do
      k[index] := trunc(abs(sin(flt(index))) * 2.0 ** 32);
    end for;
  end func;


(**
 *  Compute a message digest with the MD5 message digest algorithm.
 *  MD5 is considered to be cryptographically broken. This function
 *  is provided for backward compatibility.
 *  @return the MD5 message digest (a string of 16 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: md5 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Specify the per-round shift amounts
    const array integer: shiftAmount is [] (
        7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
        5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
        4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
        6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21);
    const array integer: k is createMd5Table;
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var digestData16: m is digestData16.value;
    var integer: a0 is 16#67452301;   # a
    var integer: b0 is 16#efcdab89;   # b
    var integer: c0 is 16#98badcfe;   # c
    var integer: d0 is 16#10325476;   # d
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: f is bin32(0);
    var integer: g is 0;
    var bin32: temp is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit little-endian integer.
    message &:= bytes(8 * length, UNSIGNED, LE, 8);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit little-endian words.
      for index range 1 to 16 do
        m[index] := bin32(bytes2Int(message[wordIndex fixLen 4], UNSIGNED, LE));
        wordIndex +:= 4;
      end for;

      a := bin32(a0 mod 16#100000000);
      b := bin32(b0 mod 16#100000000);
      c := bin32(c0 mod 16#100000000);
      d := bin32(d0 mod 16#100000000);

      for index range 1 to 64 do
        if index <= 16 then
          f := d >< (b & (c >< d));
          g := index;
        elsif index <= 32 then
          f := c >< (d & (b >< c));
          g := succ((5 * index - 4) mod 16);
        elsif index <= 48 then
          f := b >< c >< d;
          g := succ((3 * index + 2) mod 16);
        else
          f := c >< (b | (bin32(16#ffffffff) >< d));
          g := succ((7 * pred(index)) mod 16);
        end if;

        temp := d;
        d := c;
        c := b;
        b := bin32((ord(b) +
             ord(rotLeft(bin32((ord(a) + ord(f) + k[index] + ord(m[g])) mod 16#100000000),
                         shiftAmount[index]))) mod 16#100000000);
        a := temp;
      end for;

      # Add this chunk's hash to result so far:
      a0 +:= ord(a);
      b0 +:= ord(b);
      c0 +:= ord(c);
      d0 +:= ord(d);
    end while;

    # Produce the final hash value:
    digest := bytes(a0 mod 16#100000000, UNSIGNED, LE, 4) &
              bytes(b0 mod 16#100000000, UNSIGNED, LE, 4) &
              bytes(c0 mod 16#100000000, UNSIGNED, LE, 4) &
              bytes(d0 mod 16#100000000, UNSIGNED, LE, 4);
  end func;


(**
 *  Compute a message digest with the RIPEMD-160 message digest algorithm.
 *  @return the RIPEMD-160 message digest (a string of 20 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: ripemd160 (in var string: message) is func
  result
    var string: digest is "";
  local
    const array integer: k1 is [] (16#00000000, 16#5a827999, 16#6ed9eba1, 16#8f1bbcdc, 16#a953fd4e);
    const array integer: k2 is [] (16#50a28be6, 16#5c4dd124, 16#6d703ef3, 16#7a6d76e9, 16#00000000);
    const array integer: r1 is [] (
         1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
         8,  5, 14,  2, 11,  7, 16,  4, 13,  1, 10,  6,  3, 15, 12,  9,
         4, 11, 15,  5, 10, 16,  9,  2,  3,  8,  1,  7, 14, 12,  6, 13,
         2, 10, 12, 11,  1,  9, 13,  5, 14,  4,  8, 16, 15,  6,  7,  3,
         5,  1,  6, 10,  8, 13,  3, 11, 15,  2,  4,  9, 12,  7, 16, 14);
    const array integer: r2 is [] (
         6, 15,  8,  1, 10,  3, 12,  5, 14,  7, 16,  9,  2, 11,  4, 13,
         7, 12,  4,  8,  1, 14,  6, 11, 15, 16,  9, 13,  5, 10,  2,  3,
        16,  6,  2,  4,  8, 15,  7, 10, 12,  9, 13,  3, 11,  1,  5, 14,
         9,  7,  5,  2,  4, 12, 16,  1,  6, 13,  3, 14, 10,  8, 11, 15,
        13, 16, 11,  5,  2,  6,  9,  8,  7,  3, 14, 15,  1,  4, 10, 12);
    const array integer: s1 is [] (
        11, 14, 15, 12,  5,  8,  7,  9, 11, 13, 14, 15,  6,  7,  9,  8,
         7,  6,  8, 13, 11,  9,  7, 15,  7, 12, 15,  9, 11,  7, 13, 12,
        11, 13,  6,  7, 14,  9, 13, 15, 14,  8, 13,  6,  5, 12,  7,  5,
        11, 12, 14, 15, 14, 15,  9,  8,  9, 14,  5,  6,  8,  6,  5, 12,
         9, 15,  5, 11,  6,  8, 13, 12,  5, 12, 13, 14, 11,  8,  5,  6);
    const array integer: s2 is [] (
         8,  9,  9, 11, 13, 15, 15,  5,  7,  7,  8, 11, 14, 14, 12,  6,
         9, 13, 15,  7, 12,  8,  9, 11,  7,  7, 12,  7,  6, 15, 13, 11,
         9,  7, 15, 11,  8,  6,  6, 14, 12, 13,  5, 14, 13, 13,  7,  5,
        15,  5,  8, 11, 14, 14,  6, 14,  6,  9, 12,  9, 12,  5, 15,  8,
         8,  5, 12,  9, 12,  5, 14,  6,  8, 13,  6,  5, 15, 13, 11, 11);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var array integer: x is 16 times 0;
    var integer: h0 is 16#67452301;
    var integer: h1 is 16#efcdab89;
    var integer: h2 is 16#98badcfe;
    var integer: h3 is 16#10325476;
    var integer: h4 is 16#c3d2e1f0;
    var bin32: a1 is bin32(0);
    var bin32: b1 is bin32(0);
    var bin32: c1 is bin32(0);
    var bin32: d1 is bin32(0);
    var bin32: e1 is bin32(0);
    var bin32: a2 is bin32(0);
    var bin32: b2 is bin32(0);
    var bin32: c2 is bin32(0);
    var bin32: d2 is bin32(0);
    var bin32: e2 is bin32(0);
    var integer: t1 is 0;
    var integer: t2 is 0;
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit little-endian integer.
    message &:= bytes(8 * length, UNSIGNED, LE, 8);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit little-endian words.
      for index range 1 to 16 do
        x[index] := bytes2Int(message[wordIndex fixLen 4], UNSIGNED, LE);
        wordIndex +:= 4;
      end for;

      a1 := bin32(h0);
      b1 := bin32(h1);
      c1 := bin32(h2);
      d1 := bin32(h3);
      e1 := bin32(h4);
      a2 := bin32(h0);
      b2 := bin32(h1);
      c2 := bin32(h2);
      d2 := bin32(h3);
      e2 := bin32(h4);

      for index range 1 to 80 do
        case index of
          when {1 .. 16}:
            t1 := ord(b1 >< c1 >< d1);  # + k1[1];
            t2 := ord(b2 >< (c2 | ~d2)) + k2[1];
          when {17 .. 32}:
            t1 := ord((b1 & c1) | (~b1 & d1)) + k1[2];
            t2 := ord((b2 & d2) | (c2 & ~d2)) + k2[2];
          when {33 .. 48}:
            t1 := ord((b1 | ~c1) >< d1) + k1[3];
            t2 := ord((b2 | ~c2) >< d2) + k2[3];
          when {49 .. 64}:
            t1 := ord((b1 & d1) | (c1 & ~d1)) + k1[4];
            t2 := ord((b2 & c2) | (~b2 & d2)) + k2[4];
          when {65 .. 80}:
            t1 := ord(b1 >< (c1 | ~d1)) + k1[5];
            t2 := ord(b2 >< c2 >< d2);  # + k2[5];
        end case;
        t1 +:= ord(a1) + x[r1[index]];
        t1 := ord(rotLeft(bin32(t1 mod 16#100000000), s1[index])) + ord(e1);
        a1 := e1;
        e1 := d1;
        d1 := rotLeft(c1, 10);
        c1 := b1;
        b1 := bin32(t1 mod 16#100000000);
        t2 +:= ord(a2) + x[r2[index]];
        t2 := ord(rotLeft(bin32(t2 mod 16#100000000), s2[index])) + ord(e2);
        a2 := e2;
        e2 := d2;
        d2 := rotLeft(c2, 10);
        c2 := b2;
        b2 := bin32(t2 mod 16#100000000);
      end for;

      t1 := (h1 + ord(c1) + ord(d2)) mod 16#100000000;
      h1 := (h2 + ord(d1) + ord(e2)) mod 16#100000000;
      h2 := (h3 + ord(e1) + ord(a2)) mod 16#100000000;
      h3 := (h4 + ord(a1) + ord(b2)) mod 16#100000000;
      h4 := (h0 + ord(b1) + ord(c2)) mod 16#100000000;
      h0 := t1;
    end while;

    # Produce the final hash value:
    digest := bytes(h0, UNSIGNED, LE, 4) &
              bytes(h1, UNSIGNED, LE, 4) &
              bytes(h2, UNSIGNED, LE, 4) &
              bytes(h3, UNSIGNED, LE, 4) &
              bytes(h4, UNSIGNED, LE, 4);
  end func;


(**
 *  Compute a message digest with the SHA-1 secure hash algorithm.
 *  @return the SHA-1 message digest (a string of 20 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: sha1 (in var string: message) is func
  result
    var string: digest is "";
  local
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var digestData80: w is digestData80.value;
    var integer: h0 is 16#67452301;
    var integer: h1 is 16#efcdab89;
    var integer: h2 is 16#98badcfe;
    var integer: h3 is 16#10325476;
    var integer: h4 is 16#c3d2e1f0;
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: e is bin32(0);
    var bin32: f is bin32(0);
    var bin32: g is bin32(0);
    var integer: temp is 0;
    var integer: k is 0;
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= bytes(8 * length, UNSIGNED, BE, 8);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin32(bytes2Int(message[wordIndex fixLen 4], UNSIGNED, BE));
        wordIndex +:= 4;
      end for;

      # Extend the sixteen 32-bit words into eighty 32-bit words.
      for index range 17 to 80 do
        g := w[index-3] >< w[index-8] >< w[index-14] >< w[index-16];
        w[index] := rotLeft(g, 1);
      end for;

      a := bin32(h0 mod 16#100000000);
      b := bin32(h1 mod 16#100000000);
      c := bin32(h2 mod 16#100000000);
      d := bin32(h3 mod 16#100000000);
      e := bin32(h4 mod 16#100000000);

      for index range 1 to 80 do
        if index <= 20 then
          f := d >< (b & (c >< d));
          k := 16#5a827999;
        elsif index <= 40 then
          f := b >< c >< d;
          k := 16#6ed9eba1;
        elsif index <= 60 then
          f := (b & c) | (d & (b | c));
          k := 16#8f1bbcdc;
        else
          f := b >< c >< d;
          k := 16#ca62c1d6;
        end if;

        temp := ord(rotLeft(a, 5));
        temp +:= ord(f) + ord(e) + k + ord(w[index]);
        e := d;
        d := c;
        c := rotLeft(b, 30);
        b := a;
        a := bin32(temp mod 16#100000000);
      end for;

      # Add this chunk's hash to result so far:
      h0 +:= ord(a);
      h1 +:= ord(b);
      h2 +:= ord(c);
      h3 +:= ord(d);
      h4 +:= ord(e);
    end while;

    # Produce the final hash value:
    digest := bytes(h0 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h1 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h2 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h3 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h4 mod 16#100000000, UNSIGNED, BE, 4);
  end func;


(**
 *  Compute a message digest with the SHA-224 secure hash algorithm.
 *  @return the SHA-224 message digest (a string of 28 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: sha224 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Initialize array of round constants with the first 32 bits of
    # the fractional parts of the cube roots of the first 64 primes 2..311.
    const array integer: k is [] (
        16#428a2f98, 16#71374491, 16#b5c0fbcf, 16#e9b5dba5, 16#3956c25b, 16#59f111f1, 16#923f82a4, 16#ab1c5ed5,
        16#d807aa98, 16#12835b01, 16#243185be, 16#550c7dc3, 16#72be5d74, 16#80deb1fe, 16#9bdc06a7, 16#c19bf174,
        16#e49b69c1, 16#efbe4786, 16#0fc19dc6, 16#240ca1cc, 16#2de92c6f, 16#4a7484aa, 16#5cb0a9dc, 16#76f988da,
        16#983e5152, 16#a831c66d, 16#b00327c8, 16#bf597fc7, 16#c6e00bf3, 16#d5a79147, 16#06ca6351, 16#14292967,
        16#27b70a85, 16#2e1b2138, 16#4d2c6dfc, 16#53380d13, 16#650a7354, 16#766a0abb, 16#81c2c92e, 16#92722c85,
        16#a2bfe8a1, 16#a81a664b, 16#c24b8b70, 16#c76c51a3, 16#d192e819, 16#d6990624, 16#f40e3585, 16#106aa070,
        16#19a4c116, 16#1e376c08, 16#2748774c, 16#34b0bcb5, 16#391c0cb3, 16#4ed8aa4a, 16#5b9cca4f, 16#682e6ff3,
        16#748f82ee, 16#78a5636f, 16#84c87814, 16#8cc70208, 16#90befffa, 16#a4506ceb, 16#bef9a3f7, 16#c67178f2);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var digestData64: w is digestData64.value;
    # Initialize hash values with the second 32 bits of
    # the fractional parts of the square roots of the 9th through 16th primes 23..53.
    var integer: h0 is 16#c1059ed8;
    var integer: h1 is 16#367cd507;
    var integer: h2 is 16#3070dd17;
    var integer: h3 is 16#f70e5939;
    var integer: h4 is 16#ffc00b31;
    var integer: h5 is 16#68581511;
    var integer: h6 is 16#64f98fa7;
    var integer: h7 is 16#befa4fa4;
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: e is bin32(0);
    var bin32: f is bin32(0);
    var bin32: g is bin32(0);
    var bin32: h is bin32(0);
    var bin32: s0 is bin32(0);
    var bin32: s1 is bin32(0);
    var integer: temp1 is 0;
    var integer: temp2 is 0;
    var bin32: ch is bin32(0);
    var bin32: maj is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= bytes(8 * length, UNSIGNED, BE, 8);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin32(bytes2Int(message[wordIndex fixLen 4], UNSIGNED, BE));
        wordIndex +:= 4;
      end for;

      # Extend the first 16 words into the remaining 48 words of message schedule array:
      for index range 17 to 64 do
        w[index] := bin32(ord(w[index-16]) +
                          ord(rotRight(w[index-15], 7) >< rotRight(w[index-15], 18) >< (w[index-15] >> 3)) +
                          ord(w[index-7]) +
                          ord(rotRight(w[index-2], 17) >< rotRight(w[index-2], 19) >< (w[index-2] >> 10))) &
                    bin32(16#ffffffff);
      end for;

      # Initialize working variables to current hash value:
      a := bin32(h0 mod 16#100000000);
      b := bin32(h1 mod 16#100000000);
      c := bin32(h2 mod 16#100000000);
      d := bin32(h3 mod 16#100000000);
      e := bin32(h4 mod 16#100000000);
      f := bin32(h5 mod 16#100000000);
      g := bin32(h6 mod 16#100000000);
      h := bin32(h7 mod 16#100000000);

      # Compression function main loop:
      for index range 1 to 64 do
        s1 := rotRight(e, 6) >< rotRight(e, 11) >< rotRight(e, 25);
        ch := (e & f) >< ((bin32(16#ffffffff) >< e) & g);
        temp1 := ord(h) + ord(s1) + ord(ch) + k[index] + ord(w[index]);
        s0 := rotRight(a, 2) >< rotRight(a, 13) >< rotRight(a, 22);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := ord(s0) + ord(maj);

        h := g;
        g := f;
        f := e;
        e := bin32((ord(d) + temp1) mod 16#100000000);
        d := c;
        c := b;
        b := a;
        a := bin32((temp1 + temp2) mod 16#100000000);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 +:= ord(a);
      h1 +:= ord(b);
      h2 +:= ord(c);
      h3 +:= ord(d);
      h4 +:= ord(e);
      h5 +:= ord(f);
      h6 +:= ord(g);
      h7 +:= ord(h);
    end while;

    # Produce the final hash value:
    digest := bytes(h0 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h1 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h2 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h3 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h4 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h5 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h6 mod 16#100000000, UNSIGNED, BE, 4);
  end func;


(**
 *  Compute a message digest with the SHA-256 secure hash algorithm.
 *  @return the SHA-256 message digest (a string of 32 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: sha256 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Initialize array of round constants with the first 32 bits of
    # the fractional parts of the cube roots of the first 64 primes 2..311.
    const array integer: k is [] (
        16#428a2f98, 16#71374491, 16#b5c0fbcf, 16#e9b5dba5, 16#3956c25b, 16#59f111f1, 16#923f82a4, 16#ab1c5ed5,
        16#d807aa98, 16#12835b01, 16#243185be, 16#550c7dc3, 16#72be5d74, 16#80deb1fe, 16#9bdc06a7, 16#c19bf174,
        16#e49b69c1, 16#efbe4786, 16#0fc19dc6, 16#240ca1cc, 16#2de92c6f, 16#4a7484aa, 16#5cb0a9dc, 16#76f988da,
        16#983e5152, 16#a831c66d, 16#b00327c8, 16#bf597fc7, 16#c6e00bf3, 16#d5a79147, 16#06ca6351, 16#14292967,
        16#27b70a85, 16#2e1b2138, 16#4d2c6dfc, 16#53380d13, 16#650a7354, 16#766a0abb, 16#81c2c92e, 16#92722c85,
        16#a2bfe8a1, 16#a81a664b, 16#c24b8b70, 16#c76c51a3, 16#d192e819, 16#d6990624, 16#f40e3585, 16#106aa070,
        16#19a4c116, 16#1e376c08, 16#2748774c, 16#34b0bcb5, 16#391c0cb3, 16#4ed8aa4a, 16#5b9cca4f, 16#682e6ff3,
        16#748f82ee, 16#78a5636f, 16#84c87814, 16#8cc70208, 16#90befffa, 16#a4506ceb, 16#bef9a3f7, 16#c67178f2);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var digestData64: w is digestData64.value;
    # Initialize hash values with the first 32 bits of
    # the fractional parts of the square roots of the first 8 primes 2..19.
    var integer: h0 is 16#6a09e667;
    var integer: h1 is 16#bb67ae85;
    var integer: h2 is 16#3c6ef372;
    var integer: h3 is 16#a54ff53a;
    var integer: h4 is 16#510e527f;
    var integer: h5 is 16#9b05688c;
    var integer: h6 is 16#1f83d9ab;
    var integer: h7 is 16#5be0cd19;
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: e is bin32(0);
    var bin32: f is bin32(0);
    var bin32: g is bin32(0);
    var bin32: h is bin32(0);
    var bin32: s0 is bin32(0);
    var bin32: s1 is bin32(0);
    var integer: temp1 is 0;
    var integer: temp2 is 0;
    var bin32: ch is bin32(0);
    var bin32: maj is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= bytes(8 * length, UNSIGNED, BE, 8);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin32(bytes2Int(message[wordIndex fixLen 4], UNSIGNED, BE));
        wordIndex +:= 4;
      end for;

      # Extend the first 16 words into the remaining 48 words of message schedule array:
      for index range 17 to 64 do
        w[index] := bin32(ord(w[index-16]) +
                          ord(rotRight(w[index-15], 7) >< rotRight(w[index-15], 18) >< (w[index-15] >> 3)) +
                          ord(w[index-7]) +
                          ord(rotRight(w[index-2], 17) >< rotRight(w[index-2], 19) >< (w[index-2] >> 10))) &
                    bin32(16#ffffffff);
      end for;

      # Initialize working variables to current hash value:
      a := bin32(h0 mod 16#100000000);
      b := bin32(h1 mod 16#100000000);
      c := bin32(h2 mod 16#100000000);
      d := bin32(h3 mod 16#100000000);
      e := bin32(h4 mod 16#100000000);
      f := bin32(h5 mod 16#100000000);
      g := bin32(h6 mod 16#100000000);
      h := bin32(h7 mod 16#100000000);

      # Compression function main loop:
      for index range 1 to 64 do
        s1 := rotRight(e, 6) >< rotRight(e, 11) >< rotRight(e, 25);
        ch := (e & f) >< ((bin32(16#ffffffff) >< e) & g);
        temp1 := ord(h) + ord(s1) + ord(ch) + k[index] + ord(w[index]);
        s0 := rotRight(a, 2) >< rotRight(a, 13) >< rotRight(a, 22);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := ord(s0) + ord(maj);

        h := g;
        g := f;
        f := e;
        e := bin32((ord(d) + temp1) mod 16#100000000);
        d := c;
        c := b;
        b := a;
        a := bin32((temp1 + temp2) mod 16#100000000);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 +:= ord(a);
      h1 +:= ord(b);
      h2 +:= ord(c);
      h3 +:= ord(d);
      h4 +:= ord(e);
      h5 +:= ord(f);
      h6 +:= ord(g);
      h7 +:= ord(h);
    end while;

    # Produce the final hash value:
    digest := bytes(h0 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h1 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h2 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h3 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h4 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h5 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h6 mod 16#100000000, UNSIGNED, BE, 4) &
              bytes(h7 mod 16#100000000, UNSIGNED, BE, 4);
  end func;


(**
 *  Compute a message digest with the SHA-384 secure hash algorithm.
 *  @return the SHA-384 message digest (a string of 48 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: sha384 (in var string: message) is func
  result
    var string: digest is "";
  local
    const bigInteger: powTwo64 is 2_**64;
    # Initialize array of round constants with the first 64 bits of
    # the fractional parts of the cube roots of the first 80 primes 2..409.
    const array bin64: k is [] (
        bin64(16#428a2f98d728ae22_), bin64(16#7137449123ef65cd_), bin64(16#b5c0fbcfec4d3b2f_), bin64(16#e9b5dba58189dbbc_),
        bin64(16#3956c25bf348b538_), bin64(16#59f111f1b605d019_), bin64(16#923f82a4af194f9b_), bin64(16#ab1c5ed5da6d8118_),
        bin64(16#d807aa98a3030242_), bin64(16#12835b0145706fbe_), bin64(16#243185be4ee4b28c_), bin64(16#550c7dc3d5ffb4e2_),
        bin64(16#72be5d74f27b896f_), bin64(16#80deb1fe3b1696b1_), bin64(16#9bdc06a725c71235_), bin64(16#c19bf174cf692694_),
        bin64(16#e49b69c19ef14ad2_), bin64(16#efbe4786384f25e3_), bin64(16#0fc19dc68b8cd5b5_), bin64(16#240ca1cc77ac9c65_),
        bin64(16#2de92c6f592b0275_), bin64(16#4a7484aa6ea6e483_), bin64(16#5cb0a9dcbd41fbd4_), bin64(16#76f988da831153b5_),
        bin64(16#983e5152ee66dfab_), bin64(16#a831c66d2db43210_), bin64(16#b00327c898fb213f_), bin64(16#bf597fc7beef0ee4_),
        bin64(16#c6e00bf33da88fc2_), bin64(16#d5a79147930aa725_), bin64(16#06ca6351e003826f_), bin64(16#142929670a0e6e70_),
        bin64(16#27b70a8546d22ffc_), bin64(16#2e1b21385c26c926_), bin64(16#4d2c6dfc5ac42aed_), bin64(16#53380d139d95b3df_),
        bin64(16#650a73548baf63de_), bin64(16#766a0abb3c77b2a8_), bin64(16#81c2c92e47edaee6_), bin64(16#92722c851482353b_),
        bin64(16#a2bfe8a14cf10364_), bin64(16#a81a664bbc423001_), bin64(16#c24b8b70d0f89791_), bin64(16#c76c51a30654be30_),
        bin64(16#d192e819d6ef5218_), bin64(16#d69906245565a910_), bin64(16#f40e35855771202a_), bin64(16#106aa07032bbd1b8_),
        bin64(16#19a4c116b8d2d0c8_), bin64(16#1e376c085141ab53_), bin64(16#2748774cdf8eeb99_), bin64(16#34b0bcb5e19b48a8_),
        bin64(16#391c0cb3c5c95a63_), bin64(16#4ed8aa4ae3418acb_), bin64(16#5b9cca4f7763e373_), bin64(16#682e6ff3d6b2b8a3_),
        bin64(16#748f82ee5defb2fc_), bin64(16#78a5636f43172f60_), bin64(16#84c87814a1f0ab72_), bin64(16#8cc702081a6439ec_),
        bin64(16#90befffa23631e28_), bin64(16#a4506cebde82bde9_), bin64(16#bef9a3f7b2c67915_), bin64(16#c67178f2e372532b_),
        bin64(16#ca273eceea26619c_), bin64(16#d186b8c721c0c207_), bin64(16#eada7dd6cde0eb1e_), bin64(16#f57d4f7fee6ed178_),
        bin64(16#06f067aa72176fba_), bin64(16#0a637dc5a2c898a6_), bin64(16#113f9804bef90dae_), bin64(16#1b710b35131c471b_),
        bin64(16#28db77f523047d84_), bin64(16#32caab7b40c72493_), bin64(16#3c9ebe0a15c9bebc_), bin64(16#431d67c49c100d4c_),
        bin64(16#4cc5d4becb3e42b6_), bin64(16#597f299cfc657e2a_), bin64(16#5fcb6fab3ad6faec_), bin64(16#6c44198c4a475817_));
    var integer: length is 0;
    var integer: chunkIndex is 0;
    var integer: index is 0;
    var bin64digestData80: w is bin64digestData80.value;
    # Initialize hash values with the first 64 bits of
    # the fractional parts of the square roots of the first 8 primes 2..19.
    var bin64: h0 is bin64(16#cbbb9d5dc1059ed8_);
    var bin64: h1 is bin64(16#629a292a367cd507_);
    var bin64: h2 is bin64(16#9159015a3070dd17_);
    var bin64: h3 is bin64(16#152fecd8f70e5939_);
    var bin64: h4 is bin64(16#67332667ffc00b31_);
    var bin64: h5 is bin64(16#8eb44a8768581511_);
    var bin64: h6 is bin64(16#db0c2e0d64f98fa7_);
    var bin64: h7 is bin64(16#47b5481dbefa4fa4_);
    var bin64: a is bin64(0);
    var bin64: b is bin64(0);
    var bin64: c is bin64(0);
    var bin64: d is bin64(0);
    var bin64: e is bin64(0);
    var bin64: f is bin64(0);
    var bin64: g is bin64(0);
    var bin64: h is bin64(0);
    var bin64: s0 is bin64(0);
    var bin64: s1 is bin64(0);
    var bin64: temp1 is bin64(0);
    var bin64: temp2 is bin64(0);
    var bin64: ch is bin64(0);
    var bin64: maj is bin64(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 1024).
    message &:= "\0;" mult 127 - (length + 8) mod 128;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= bytes(8 * length, UNSIGNED, BE, 8);

    # Process the message in successive 1024-bit chunks:
    for chunkIndex range 1 to length(message) step 128 do
      # Check that the input contains no character beyond '\255;'.
      for index range 0 to 127 do
        if ord(message[chunkIndex + index]) > 255 then
          raise RANGE_ERROR;
        end if;
      end for;

      # Break chunk into sixteen 64-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin64(message[chunkIndex + 8 * pred(index) fixLen 8], BE);
      end for;

      # Extend the first 16 words into the remaining 64 words of message schedule array:
      for index range 17 to 80 do
        w[index] := bin64((big(w[index-16]) +
                           big(rotRight(w[index-15], 1) ><
                               rotRight(w[index-15], 8) ><
                               (w[index-15] >> 7)) +
                           big(w[index-7]) +
                           big(rotRight(w[index-2], 19) ><
                               rotRight(w[index-2], 61) ><
                               (w[index-2] >> 6))) mod powTwo64);
      end for;

      # Initialize working variables to current hash value:
      a := h0;
      b := h1;
      c := h2;
      d := h3;
      e := h4;
      f := h5;
      g := h6;
      h := h7;

      # Compression function main loop:
      for index range 1 to 80 do
        s1 := rotRight(e, 14) >< rotRight(e, 18) >< rotRight(e, 41);
        ch := (e & f) >< (~e & g);
        temp1 := bin64((big(h) + big(s1) + big(ch) + big(k[index]) + big(w[index])) mod powTwo64);
        s0 := rotRight(a, 28) >< rotRight(a, 34) >< rotRight(a, 39);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := bin64((big(s0) + big(maj)) mod powTwo64);

        h := g;
        g := f;
        f := e;
        e := bin64((big(d) + big(temp1)) mod powTwo64);
        d := c;
        c := b;
        b := a;
        a := bin64((big(temp1) + big(temp2)) mod powTwo64);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 := bin64((big(h0) + big(a)) mod powTwo64);
      h1 := bin64((big(h1) + big(b)) mod powTwo64);
      h2 := bin64((big(h2) + big(c)) mod powTwo64);
      h3 := bin64((big(h3) + big(d)) mod powTwo64);
      h4 := bin64((big(h4) + big(e)) mod powTwo64);
      h5 := bin64((big(h5) + big(f)) mod powTwo64);
      h6 := bin64((big(h6) + big(g)) mod powTwo64);
      h7 := bin64((big(h7) + big(h)) mod powTwo64);
    end for;

    # Produce the final hash value:
    digest := bytes(h0, BE, 8) &
              bytes(h1, BE, 8) &
              bytes(h2, BE, 8) &
              bytes(h3, BE, 8) &
              bytes(h4, BE, 8) &
              bytes(h5, BE, 8);
  end func;


(**
 *  Compute a message digest with the SHA-512 secure hash algorithm.
 *  @return the SHA-512 message digest (a string of 64 bytes).
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: sha512 (in var string: message) is func
  result
    var string: digest is "";
  local
    const bigInteger: powTwo64 is 2_**64;
    # Initialize array of round constants with the first 64 bits of
    # the fractional parts of the cube roots of the first 80 primes 2..409.
    const array bin64: k is [] (
        bin64(16#428a2f98d728ae22_), bin64(16#7137449123ef65cd_), bin64(16#b5c0fbcfec4d3b2f_), bin64(16#e9b5dba58189dbbc_),
        bin64(16#3956c25bf348b538_), bin64(16#59f111f1b605d019_), bin64(16#923f82a4af194f9b_), bin64(16#ab1c5ed5da6d8118_),
        bin64(16#d807aa98a3030242_), bin64(16#12835b0145706fbe_), bin64(16#243185be4ee4b28c_), bin64(16#550c7dc3d5ffb4e2_),
        bin64(16#72be5d74f27b896f_), bin64(16#80deb1fe3b1696b1_), bin64(16#9bdc06a725c71235_), bin64(16#c19bf174cf692694_),
        bin64(16#e49b69c19ef14ad2_), bin64(16#efbe4786384f25e3_), bin64(16#0fc19dc68b8cd5b5_), bin64(16#240ca1cc77ac9c65_),
        bin64(16#2de92c6f592b0275_), bin64(16#4a7484aa6ea6e483_), bin64(16#5cb0a9dcbd41fbd4_), bin64(16#76f988da831153b5_),
        bin64(16#983e5152ee66dfab_), bin64(16#a831c66d2db43210_), bin64(16#b00327c898fb213f_), bin64(16#bf597fc7beef0ee4_),
        bin64(16#c6e00bf33da88fc2_), bin64(16#d5a79147930aa725_), bin64(16#06ca6351e003826f_), bin64(16#142929670a0e6e70_),
        bin64(16#27b70a8546d22ffc_), bin64(16#2e1b21385c26c926_), bin64(16#4d2c6dfc5ac42aed_), bin64(16#53380d139d95b3df_),
        bin64(16#650a73548baf63de_), bin64(16#766a0abb3c77b2a8_), bin64(16#81c2c92e47edaee6_), bin64(16#92722c851482353b_),
        bin64(16#a2bfe8a14cf10364_), bin64(16#a81a664bbc423001_), bin64(16#c24b8b70d0f89791_), bin64(16#c76c51a30654be30_),
        bin64(16#d192e819d6ef5218_), bin64(16#d69906245565a910_), bin64(16#f40e35855771202a_), bin64(16#106aa07032bbd1b8_),
        bin64(16#19a4c116b8d2d0c8_), bin64(16#1e376c085141ab53_), bin64(16#2748774cdf8eeb99_), bin64(16#34b0bcb5e19b48a8_),
        bin64(16#391c0cb3c5c95a63_), bin64(16#4ed8aa4ae3418acb_), bin64(16#5b9cca4f7763e373_), bin64(16#682e6ff3d6b2b8a3_),
        bin64(16#748f82ee5defb2fc_), bin64(16#78a5636f43172f60_), bin64(16#84c87814a1f0ab72_), bin64(16#8cc702081a6439ec_),
        bin64(16#90befffa23631e28_), bin64(16#a4506cebde82bde9_), bin64(16#bef9a3f7b2c67915_), bin64(16#c67178f2e372532b_),
        bin64(16#ca273eceea26619c_), bin64(16#d186b8c721c0c207_), bin64(16#eada7dd6cde0eb1e_), bin64(16#f57d4f7fee6ed178_),
        bin64(16#06f067aa72176fba_), bin64(16#0a637dc5a2c898a6_), bin64(16#113f9804bef90dae_), bin64(16#1b710b35131c471b_),
        bin64(16#28db77f523047d84_), bin64(16#32caab7b40c72493_), bin64(16#3c9ebe0a15c9bebc_), bin64(16#431d67c49c100d4c_),
        bin64(16#4cc5d4becb3e42b6_), bin64(16#597f299cfc657e2a_), bin64(16#5fcb6fab3ad6faec_), bin64(16#6c44198c4a475817_));
    var integer: length is 0;
    var integer: chunkIndex is 0;
    var integer: index is 0;
    var bin64digestData80: w is bin64digestData80.value;
    # Initialize hash values with the first 64 bits of
    # the fractional parts of the square roots of the first 8 primes 2..19.
    var bin64: h0 is bin64(16#6a09e667f3bcc908_);
    var bin64: h1 is bin64(16#bb67ae8584caa73b_);
    var bin64: h2 is bin64(16#3c6ef372fe94f82b_);
    var bin64: h3 is bin64(16#a54ff53a5f1d36f1_);
    var bin64: h4 is bin64(16#510e527fade682d1_);
    var bin64: h5 is bin64(16#9b05688c2b3e6c1f_);
    var bin64: h6 is bin64(16#1f83d9abfb41bd6b_);
    var bin64: h7 is bin64(16#5be0cd19137e2179_);
    var bin64: a is bin64(0);
    var bin64: b is bin64(0);
    var bin64: c is bin64(0);
    var bin64: d is bin64(0);
    var bin64: e is bin64(0);
    var bin64: f is bin64(0);
    var bin64: g is bin64(0);
    var bin64: h is bin64(0);
    var bin64: s0 is bin64(0);
    var bin64: s1 is bin64(0);
    var bin64: temp1 is bin64(0);
    var bin64: temp2 is bin64(0);
    var bin64: ch is bin64(0);
    var bin64: maj is bin64(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 1024).
    message &:= "\0;" mult 127 - (length + 8) mod 128;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= bytes(8 * length, UNSIGNED, BE, 8);

    # Process the message in successive 1024-bit chunks:
    for chunkIndex range 1 to length(message) step 128 do
      # Check that the input contains no character beyond '\255;'.
      for index range 0 to 127 do
        if ord(message[chunkIndex + index]) > 255 then
          raise RANGE_ERROR;
        end if;
      end for;

      # Break chunk into sixteen 64-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin64(message[chunkIndex + 8 * pred(index) fixLen 8], BE);
      end for;

      # Extend the first 16 words into the remaining 64 words of message schedule array:
      for index range 17 to 80 do
        w[index] := bin64((big(w[index-16]) +
                           big(rotRight(w[index-15], 1) ><
                               rotRight(w[index-15], 8) ><
                               (w[index-15] >> 7)) +
                           big(w[index-7]) +
                           big(rotRight(w[index-2], 19) ><
                               rotRight(w[index-2], 61) ><
                               (w[index-2] >> 6))) mod powTwo64);
      end for;

      # Initialize working variables to current hash value:
      a := h0;
      b := h1;
      c := h2;
      d := h3;
      e := h4;
      f := h5;
      g := h6;
      h := h7;

      # Compression function main loop:
      for index range 1 to 80 do
        s1 := rotRight(e, 14) >< rotRight(e, 18) >< rotRight(e, 41);
        ch := (e & f) >< (~e & g);
        temp1 := bin64((big(h) + big(s1) + big(ch) + big(k[index]) + big(w[index])) mod powTwo64);
        s0 := rotRight(a, 28) >< rotRight(a, 34) >< rotRight(a, 39);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := bin64((big(s0) + big(maj)) mod powTwo64);

        h := g;
        g := f;
        f := e;
        e := bin64((big(d) + big(temp1)) mod powTwo64);
        d := c;
        c := b;
        b := a;
        a := bin64((big(temp1) + big(temp2)) mod powTwo64);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 := bin64((big(h0) + big(a)) mod powTwo64);
      h1 := bin64((big(h1) + big(b)) mod powTwo64);
      h2 := bin64((big(h2) + big(c)) mod powTwo64);
      h3 := bin64((big(h3) + big(d)) mod powTwo64);
      h4 := bin64((big(h4) + big(e)) mod powTwo64);
      h5 := bin64((big(h5) + big(f)) mod powTwo64);
      h6 := bin64((big(h6) + big(g)) mod powTwo64);
      h7 := bin64((big(h7) + big(h)) mod powTwo64);
    end for;

    # Produce the final hash value:
    digest := bytes(h0, BE, 8) &
              bytes(h1, BE, 8) &
              bytes(h2, BE, 8) &
              bytes(h3, BE, 8) &
              bytes(h4, BE, 8) &
              bytes(h5, BE, 8) &
              bytes(h6, BE, 8) &
              bytes(h7, BE, 8);
  end func;


(**
 *  Enumeration of message digest algorithms.
 *  Defines: NO_DIGEST, MD4, MD5, RIPEMD160, SHA1, SHA224, SHA256, SHA384 and SHA512.
 *)
const type: digestAlgorithm is new enum
    NO_DIGEST, MD4, MD5, RIPEMD160, SHA1, SHA224, SHA256, SHA384, SHA512
  end enum;


(**
 *  Compute a message digest with the given [[msgdigest#digestAlgorithm|digestAlgorithm]].
 *  @param digestAlg The [[msgdigest#digestAlgorithm|digestAlgorithm]] to be used.
 *  @return the message digest of the ''message''.
 *  @exception RANGE_ERROR If ''message'' contains a character beyond '\255;'.
 *)
const func string: msgDigest (in digestAlgorithm: digestAlg, in string: message) is DYNAMIC;

const func string: msgDigest (NO_DIGEST, in string: message) is return "";
const func string: msgDigest (MD4, in string: message)       is return md4(message);
const func string: msgDigest (MD5, in string: message)       is return md5(message);
const func string: msgDigest (RIPEMD160, in string: message) is return ripemd160(message);
const func string: msgDigest (SHA1, in string: message)      is return sha1(message);
const func string: msgDigest (SHA224, in string: message)    is return sha224(message);
const func string: msgDigest (SHA256, in string: message)    is return sha256(message);
const func string: msgDigest (SHA384, in string: message)    is return sha384(message);
const func string: msgDigest (SHA512, in string: message)    is return sha512(message);


(**
 *  Block size used by the given [[msgdigest#digestAlgorithm|digestAlgorithm]].
 *  @return the block size in bytes used by the message digest algorithm.
 *)
const func integer: blockSize (in digestAlgorithm: digestAlg) is DYNAMIC;

const func integer: blockSize (NO_DIGEST) is   0;
const func integer: blockSize (MD4)       is  64;
const func integer: blockSize (MD5)       is  64;
const func integer: blockSize (RIPEMD160) is  64;
const func integer: blockSize (SHA1)      is  64;
const func integer: blockSize (SHA224)    is  64;
const func integer: blockSize (SHA256)    is  64;
const func integer: blockSize (SHA384)    is 128;
const func integer: blockSize (SHA512)    is 128;


(**
 *  Size of a message digest computed with the given [[msgdigest#digestAlgorithm|digestAlgorithm]].
 *  @return the size of a message digest in bytes.
 *)
const func integer: digestSize (in digestAlgorithm: digestAlg) is DYNAMIC;

const func integer: digestSize (NO_DIGEST) is  0;
const func integer: digestSize (MD4)       is 16;
const func integer: digestSize (MD5)       is 16;
const func integer: digestSize (RIPEMD160) is 20;
const func integer: digestSize (SHA1)      is 20;
const func integer: digestSize (SHA224)    is 28;
const func integer: digestSize (SHA256)    is 32;
const func integer: digestSize (SHA384)    is 48;
const func integer: digestSize (SHA512)    is 64;