;;;  sha1-old.el -- SHA-1 message digest algorithm

;; Copyright (C) 1998 Ray Jones

;; Author: Ray Jones, rjones@pobox.com
;; Keywords: SHA, SHA-1, message digest
;; Created: 1998-04-27

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;;
;; This program 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 General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, you can either send email to this
;; program's maintainer or write to: The Free Software Foundation,
;; Inc.; 675 Massachusetts Avenue; Cambridge, MA 02139, USA.

;;; Commentary:

;; this is a slower, more clear, version of sha1.el.  it's based on
;; md5-old.el, since much of the code is similar.  however, SHA-1 is
;; different than MD5 in that MD5 treats the input bits as MSB-first
;; bitwise but LSB-first bytewise, but SHA-1 treats the data as
;; MSB-first both bit- and byte-wise.

;;; Code:
(require 'cl)

(defun sha1 (string)
  "return the sha1 hash of a string, as a 128 bit string"
  (let* ((length (length string))
	 ;; sha1 requires the message be padded to a length of 512*k +
	 ;; 64 (bits).  confusion source: we're working with bytes.
	 ;;
	 ;; padding is always done.
	 ;; 512 bits = 64 bytes, 64 bits = 8 bytes
	 (next-512 (+ 64 (logand (+ length 8) (lognot 63))))
	 (pad-bytes (- next-512 length 8))
	 (pad-string (make-string pad-bytes 0))
	 (len-string (make-string 8 0)))
    ;; message is constructed as:
    ;; original-message | pad | length-in-bits
    ;; pad is 10000... (bitwise)
    ;; length-in-bits is length before padding, and is 64 bits long

    ;; fill in the single bit of the pad
    (aset pad-string 0 (ash 1 7))

    ;; there's a slim chance of overflow when multiplying the length
    ;; by 8 to get the length in bits.  to avoid this, do some
    ;; slightly hairier math when writing the length into len-string.

    (dotimes (idx 4)
      (aset len-string (+ 4 idx) (logand #xff
				   (ash length (+ -21 (* 8 idx))))))

    (sha1-vector
     (sha1-string-to-32bit-vec
      (concat string pad-string len-string)))))

(defun sha1-string-to-32bit-vec (string)
  ;; emacs doesn't actually have 32 bits, in most implementations.
  ;; 32 bit numbers are represented as a pair of 16 bit numbers

  ;; 4 chars per 32 bit number, MSB-first
  (let* ((veclen (/ (length string) 4))
	 (vec (make-vector veclen nil))
	 (stridx 0))
    (dotimes (vecidx veclen)
      ;; SHA-1 integers are (hi . lo) 16 bit words
      (aset vec vecidx (cons (+ (ash (aref string (+ stridx 0)) 8)
				(aref string (+ stridx 1)))
			     (+ (ash (aref string (+ stridx 2)) 8)
				(aref string (+ stridx 3)))))
      (incf stridx 4))

    vec))

;; f for rounds 0-19
(defsubst sha1-f1-2 (x y z)
  (logior (logand x y)
	  (logand (lognot x)
		  z)))

;; f for rounds 20-39 and 60-79
(defsubst sha1-f2&4-2 (x y z)
  (logxor x y z))

; f for rounds 40-59
(defsubst sha1-f3-2 (x y z)
  (logior (logand x y)
	  (logand y z)
	  (logand x z)))


(defsubst sha1-f1 (x y z)
  (cons (sha1-f1-2 (car x) (car y) (car z))
	(sha1-f1-2 (cdr x) (cdr y) (cdr z))))

(defsubst sha1-f2&4 (x y z)
  (cons (sha1-f2&4-2 (car x) (car y) (car z))
	(sha1-f2&4-2 (cdr x) (cdr y) (cdr z))))

(defsubst sha1-f3 (x y z)
  (cons (sha1-f3-2 (car x) (car y) (car z))
	(sha1-f3-2 (cdr x) (cdr y) (cdr z))))

(defsubst sha1<<< (val shift)
  "circular shift sha1 32 bit int VAL by 1 bit"
  (let ((a (car val))
	(b (cdr val)))
    ;; only three cases ever occur
    (cond ((= shift 1) (cons (logand #xffff (logior (ash a 1) (ash b -15)))
			     (logand #xffff (logior (ash b 1) (ash a -15)))))
	  ((= shift 5) (cons (logand #xffff (logior (ash a 5) (ash b -11)))
			     (logand #xffff (logior (ash b 5) (ash a -11)))))
	  ;; shift = 30, which is a swap and a shift by 14
	  (t (cons (logand #xffff (logior (ash b 14) (ash a -2)))
		   (logand #xffff (logior (ash a 14) (ash b -2))))))))

(defsubst sha1+ (&rest args)
  ;; since we only use 16 bits, there's enough room to just add
  ;; without carry checks for each add.
  (let* ((lo (apply #'+ (mapcar #'cdr args)))
	 (hi (+ (ash lo -16) (apply #'+ (mapcar #'car args)))))
    (cons (logand #xffff hi)
	  (logand #xffff lo))))

(defsubst sha1-logxor4 (a b c d)
  (cons (logxor (car a) (car b) (car c) (car d))
	(logxor (cdr a) (cdr b) (cdr c) (cdr d))))

(defmacro sha1-rewrite (fun k)
  "helper function for sha1-vector, below.  ugly coding practice,
having a macro-rewriter elsewhere, but the indentation was getting a
bit out of control.
NB: many variables must be defined at the calling point!"
  `(let ((temp (sha1+ (sha1<<< a 5)
		      ,(list fun 'b 'c 'd)
		      e
		      (aref w w-idx)
		      ,k)))
     (setq e d
	   d c
	   c (sha1<<< b 30)
	   b a
	   a temp)
     (incf w-idx)))


(defun sha1-vector (vec)
  ;; initialize the chaining variables
  (let ((a (cons #x6745 #x2301))
	(b (cons #xefcd #xab89))
	(c (cons #x98ba #xdcfe))
	(d (cons #x1032 #x5476))
	(e (cons #xc3d2 #xe1f0))
	(w (make-vector 80 0))
	(v-offset 0))

    (dotimes (count (/ (length vec) 16))
      ;; initialize w
      (dotimes (idx 16)
	(aset w idx (aref vec (+ v-offset idx))))
      
      ;; fill in the rest of w
      (do ((idx 16 (1+ idx)))
	  ((= idx 80))
	(aset w idx (sha1<<< (sha1-logxor4 (aref w (- idx 3))
					   (aref w (- idx 8))
					   (aref w (- idx 14))
					   (aref w (- idx 16)))
			    1)))

      (let ((AA a) (BB b) (CC c) (DD d) (EE e)
	    (w-idx 0))

	(dotimes (count 20)
;;	  (insert (format "%s %s %s %s %s\n"
;;			  a b c d e))
	  (sha1-rewrite sha1-f1 '(#x5a82 . #x7999)))

	(dotimes (count 20)
	  (sha1-rewrite sha1-f2&4 '(#x6ed9 . #xeba1)))

	(dotimes (count 20)
	  (sha1-rewrite sha1-f3 '(#x8f1b . #xbcdc)))

	(dotimes (count 20)
	  (sha1-rewrite sha1-f2&4 '(#xca62 . #xc1d6)))

	(setq a (sha1+ AA a)
	      b (sha1+ BB b)
	      c (sha1+ CC c)
	      d (sha1+ DD d)
	      e (sha1+ EE e)))

      (incf v-offset 16))

    ;; write out the concatenation of the state
    (mapconcat #'(lambda (x) (format "%04x" x))
	       (list (car a) (cdr a)
		     (car b) (cdr b)
		     (car c) (cdr c)
		     (car d) (cdr d)
		     (car e) (cdr e))
	       "")))

(provide 'sha1)
