;;; -*- Mode:LISP; Package:USER; Readtable:ZL; Base:10 -*-

#8r si:
(DEFUN PRINT-BIGNUM (BIGNUM STREAM &AUX (base (current-print-base)))
  (declare (unspecial base))
  (cond ((< bignum 1e100)
	 (when *print-radix*
	   (print-print-radix-prefix base stream))
	 (WHEN (MINUSP BIGNUM)
	   (SEND STREAM :TYO (PTTBL-MINUS-SIGN *READTABLE*)))
	 (IF (FIXNUMP BASE)
	     (PRINT-RAW-BIGNUM BIGNUM BASE STREAM)
	   (FUNCALL (GET BASE 'PRINC-FUNCTION) (- BIGNUM) STREAM))
	 (WHEN (AND (OR *PRINT-RADIX* (NOT *NOPOINT))
		    (EQ BASE 10.))
	   (SEND STREAM :TYO (PTTBL-DECIMAL-POINT *READTABLE*)))
	 BIGNUM)
	(t
	 (printing-random-object (bignum stream)
	   (format stream "Bignum with about ~d digits"
		   (round (haulong bignum) (log base 2)))))))


(defun find-d (u v)
  ;; (gcd e m) == 1
  ;; therefore, there exists x0 and y0 so that e*x0+m*y0=1
  ;; d = x0 mod m
  ;;from Knuth vol2 p302 Algorithm X
  (let ((u1 1) (u2 0) (u3 u)
	(v1 0) (v2 1) (v3 v)
	)
    (do ()
	((zerop v3)
	 (values u1 u2 u3))
      (let ((q (floor u3 v3)))
	(psetq u1 v1
	       u2 v2
	       u3 v3
	       v1 (- u1 (* v1 q))
	       v2 (- u2 (* v2 q))
	       v3 (- u3 (* v3 q)))))))

;	(setq t1 (- u1 (* v1 q)))
;	(setq t2 (- u2 (* v2 q)))
;	(setq t3 (- u3 (* v3 q)))
;	(setq u1 v1)
;	(setq u2 v2)
;	(setq u3 v3)
;	(setq v1 t1)
;	(setq v2 t2)
;	(setq v3 t3)))))

;;numbers right around (isqrt m) seem to make the encryption function its
;;own inverse!!
(defun find-e (m)
  (do ((e (round (* 11/10 (isqrt m))) (1+ e)))
      ((= (gcd e m) 1) e)))
    
(defun find-weight (p q)
  (do ((w q (+ w q)))
      ((= (mod w p) 1) w)))

(defvar *p* 5)
(defvar *q* 11.)

(defvar *n*)
(defvar *m*)

(defvar *e*)
(defvar *d*)

(defvar *x1* nil)
(defvar *x2* nil)

(defun print-numbers ()
  (format t "~&P=~d Q=~d N=~d M=~d E=~d D=~d"
	  *p* *q* *n* *m* *e* *d*))

(defun setup-numbers ()
  (setq *n* (* *p* *q*))
  (setq *m* (* (- *p* 1) (- *q* 1)))
  (setq *e* (find-e *m*))
  (setq *d* (mod (find-d *e* *m*) *m*))
  (setq *x1* (find-weight *p* *q*))
  (setq *x2* (find-weight *q* *p*))
  )


(defun slow-rsa-encrypt (s)
  (setup-numbers)
  (mod (^ s *e*) *n*))

(defun slow-rsa-decrypt (s)
  (setup-numbers)
  (mod (^ s *d*) *n*))

(defun exp1 (s a n)
  ;;(s ^ a) mod n
  (do ((prod 1)
       (r 0 (1+ r))
       (squared s (mod (* squared squared) n))
       (end (haulong a)))
      ((= r end)
       prod)
    (if (ldb-test (byte 1 r) a)
	(setq prod (mod (* prod squared) n)))))

(defun test1 ()
  (print-numbers)
  (dotimes (i 20)
    (format t "~&~d ~d ~d ~d ~d"
	    i (slow-rsa-encrypt i) (exp2 i *e* *n*)
	    (slow-rsa-encrypt (slow-rsa-encrypt i))
	    (slow-rsa-decrypt (slow-rsa-encrypt i))

    )))

(defun test2 ()
  (print-numbers)
  (dotimes (i 20)
    (let* ((crypted (exp2 i *e* *n*))
	   (crypted2 (exp2 crypted *e* *n*))
	   (clear (exp2 crypted *d* *n*)))
      (format t "~&~d ~d ~d ~d"
	      i crypted crypted2 clear))))


(defun fast-times (a b)
  (mod (+ (mod (* (mod (* (mod a *p*) (mod b *p*)) *p*) *x1*) *n*)
	  (mod (* (mod (* (mod a *q*) (mod b *q*)) *q*) *x2*) *n*))
       *n*))

(defun exp2 (s a n)
  ;;(s ^ a) mod n
  (do ((prod 1)
       (r 0 (1+ r))
       (squared s (fast-times squared squared))
       (end (haulong a)))
      ((= r end)
       prod)
    (if (ldb-test (byte 1 r) a)
	(setq prod (fast-times prod squared)))))

(defun rabin-composite-p (n a)
  (when (not (<= 1 a (- n 1)))
    (ferror nil "bad a"))
  (let ((m (- n 1))
	(k 0))
    (do ()
	((oddp m))
      (setq m (ash m -1))
      (incf k))
    (let ((x0 (exp1 a m n)))
      (cond ((= x0 1)
	     nil)
	    (t
	     (do ((i 1 (1+ i))
		  (xi (mod (* x0 x0) n) (mod (* xi xi) n))
		  (xi-1 x0 xi))
		 ((> i k)
		  t)
	       (when (= xi 1)
		 (cond ((= xi-1 -1)
			(return nil))
		       (t
			(return t))))))))))

(defun find-small-primes ()
  (let* ((sieve-length 3000)
	 (sieve (make-array sieve-length :type :art-1b))
	 prime-list
	 )
    (do ((start 2 (+ start 1)))
	((>= start sieve-length)
	 (reverse prime-list))
      (when (zerop (aref sieve start))
	(push start prime-list)
	(do ((index (+ start start) (+ index start)))
	    ((>= index sieve-length))
	  (aset 1 sieve index))))))

(defvar *small-primes* (find-small-primes))

(defun divisible-by-small-prime-p (n)
  (dolist (p *small-primes*)
    (cond ((>= p n)
	   (return nil))
	  ((zerop (remainder n p))
	   (return t)))))

(defvar *rsa-random-state* (make-random-state))

(defun choose-a (n)
  (do ((a (random n *rsa-random-state*)
	  (random n *rsa-random-state*)))
      ((>= a 1) a)))

(defun rabin-iterations-needed (probability)
  (values (ceiling (- 3 (log (float probability) 4)))))

(defun probably-prime-p (n probability)
  (cond ((divisible-by-small-prime-p n)
	 nil)
	(t
	 (dotimes (i (rabin-iterations-needed probability))
	   (when (rabin-composite-p n (choose-a n))
	     (return nil)))
	 t)))

(defconst *prime-probability-tolerance* 1e-5)

(defun find-next-probable-prime (lower-bound)
  (do ((n (if (evenp lower-bound)
	      (+ lower-bound 1)
	    (+ lower-bound 2))
	  (+ n 2)))
      ((probably-prime-p n *prime-probability-tolerance*)
       n)))

(defvar *c12*)
(defvar *c21*)

(defun find-cs ()
  (setq *c12* (find-d *p* *q*))
  (setq *c21* (find-d *q* *p*)))

(defun fast-times (a b)
  (let ((u1 (mod (* (mod a *p*) (mod b *p*)) *p*))
	(u2 (mod (* (mod a *q*) (mod b *q*)) *q*)))
    (+ u1
       (* *p*
	  (mod (* *c12* (- u2 u1)) *q*)))))

(defun decimal-digits-to-bits (digits)
  (round (* digits (log 10. 2))))

(defconst *number-of-bits-in-keys* (decimal-digits-to-bits 10.))

(defun string-to-key-base (string)
  (let ((number 0))
    (let ((expansion-factor (max (round (/ *number-of-bits-in-keys*
					   (* 7 (string-length string))))
				 1)))
      (dotimes (i (string-length string))
	(dotimes (j 7)
	  (let ((bit-pos (* expansion-factor (+ (* i 7) j))))
	    (when (> bit-pos *number-of-bits-in-keys*)
	      (return nil))
	    (setq number (dpb (ldb (byte 1 j) (aref string i))
			      (byte 1 bit-pos)
			      number))))))
    number))

(defun string-to-private-key (string)
  (let ((lower-bound (string-to-key-base string)))
    (find-next-probable-prime lower-bound)))

(defun generate-random-prime ()
  (let ((biggest (ash 1 *number-of-bits-in-keys*)))
    (find-next-probable-prime (+ (ash biggest -10)
				 (random (- biggest (ash biggest -10)) *rsa-random-state*)))))
			       

