#!/usr/bin/env ruby
# SHA-224 implementation in Ruby
# Part of the SHA-2 family of cryptographic hash functions
#
# This implementation is for educational purposes and follows FIPS 180-4

require 'digest'

# Pure Ruby implementation of SHA-224
class SHA224
  # SHA-224 uses the same round constants as SHA-256
  K = [
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
  ]

  def initialize
    # SHA-224 initial hash values (different from SHA-256)
    @h = [
      0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
      0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
    ]
    
    # Reset message state
    @buffer = []
    @count = 0
  end

  # Update the hash computation with the specified message
  def update(message)
    bytes = message.is_a?(String) ? message.bytes : message
    
    # Update the count of bytes
    @count += bytes.length
    
    # Add to buffer
    @buffer.concat(bytes)
    
    # Process complete blocks
    while @buffer.length >= 64
      process_block(@buffer.slice!(0, 64))
    end
    
    self
  end

  # Finalizes the hash computation and returns the hash result
  def digest
    # Create a clone of the hash state
    h = @h.dup
    buffer = @buffer.dup
    count = @count
    
    # Apply padding
    bit_length = count * 8
    
    # Append the bit '1' to the message
    buffer << 0x80
    
    # Append padding bits (0) until the message length is congruent to 56 (mod 64)
    while buffer.length % 64 != 56
      buffer << 0
    end
    
    # Append length as a 64-bit big-endian integer
    (7).downto(0) do |i|
      buffer << ((bit_length >> (i * 8)) & 0xff)
    end
    
    # Process final blocks
    buffer.each_slice(64) do |block|
      process_block(block, h)
    end
    
    # Format the result as a hex string (truncated to 224 bits = 28 bytes = 7 words)
    h[0, 7].map { |word| word.to_s(16).rjust(8, '0') }.join
  end

  # Process a single 512-bit block
  private def process_block(block, h = @h)
    # Message schedule
    w = Array.new(64)
    
    # Prepare the message schedule
    16.times do |t|
      w[t] = (block[t * 4] << 24) |
             (block[t * 4 + 1] << 16) |
             (block[t * 4 + 2] << 8) |
             block[t * 4 + 3]
    end
    
    # Extend the message schedule
    16.upto(63) do |t|
      s0 = right_rotate(w[t - 15], 7) ^
           right_rotate(w[t - 15], 18) ^
           (w[t - 15] >> 3)
           
      s1 = right_rotate(w[t - 2], 17) ^
           right_rotate(w[t - 2], 19) ^
           (w[t - 2] >> 10)
           
      w[t] = (w[t - 16] + s0 + w[t - 7] + s1) & 0xffffffff
    end
    
    # Initialize working variables
    a, b, c, d, e, f, g, h_var = h
    
    # Main loop
    64.times do |t|
      s1 = right_rotate(e, 6) ^
           right_rotate(e, 11) ^
           right_rotate(e, 25)
           
      ch = (e & f) ^ ((~e) & g)
      temp1 = (h_var + s1 + ch + K[t] + w[t]) & 0xffffffff
      
      s0 = right_rotate(a, 2) ^
           right_rotate(a, 13) ^
           right_rotate(a, 22)
           
      maj = (a & b) ^ (a & c) ^ (b & c)
      temp2 = (s0 + maj) & 0xffffffff
      
      h_var = g
      g = f
      f = e
      e = (d + temp1) & 0xffffffff
      d = c
      c = b
      b = a
      a = (temp1 + temp2) & 0xffffffff
    end
    
    # Update hash values
    h[0] = (h[0] + a) & 0xffffffff
    h[1] = (h[1] + b) & 0xffffffff
    h[2] = (h[2] + c) & 0xffffffff
    h[3] = (h[3] + d) & 0xffffffff
    h[4] = (h[4] + e) & 0xffffffff
    h[5] = (h[5] + f) & 0xffffffff
    h[6] = (h[6] + g) & 0xffffffff
    h[7] = (h[7] + h_var) & 0xffffffff
  end

  # Right rotate a 32-bit number by n bits
  private def right_rotate(x, n)
    ((x >> n) | (x << (32 - n))) & 0xffffffff
  end

  # Static methods for convenience
  class << self
    # Compute SHA-224 hash of a message
    def hash(message)
      new.update(message).digest
    end
    
    # Verify a message against an expected SHA-224 hash
    def verify(message, expected_hash)
      hash(message).downcase == expected_hash.downcase
    end
  end
end

# Example usage
if __FILE__ == $0
  # Using the custom implementation
  message = "Hello, SHA-224!"
  hash = SHA224.hash(message)
  puts "Custom SHA-224: #{hash}"
  
  # Verify it with the expected value
  puts "Verification: #{SHA224.verify(message, hash)}"
  
  # Using Ruby's built-in Digest module (if available)
  begin
    require 'digest/sha2'
    builtin_hash = Digest::SHA224.hexdigest(message)
    puts "Ruby built-in SHA-224: #{builtin_hash}"
    puts "Matches custom implementation: #{hash == builtin_hash}"
  rescue LoadError
    puts "Ruby built-in SHA-224 not available in this Ruby version"
  end
end