;;;; dsa.lisp -- implementation of the Digital Signature Algorithm

(in-package :crypto)


;;; class definitions

(defclass discrete-logarithm-group ()
  ((p :initarg :p :reader group-pval)
   (q :initarg :q :reader group-qval)
   (g :initarg :g :reader group-gval)))

(defclass dsa-key ()
  ((group :initarg :group :reader group)))

(defclass dsa-public-key (dsa-key)
  ((y :initarg :y :reader dsa-key-y :type integer)))

(defclass dsa-private-key (dsa-key)
  ((y :initarg :y :reader dsa-key-y :type integer)
   (x :initarg :x :reader dsa-key-x :type integer)))

(defclass dsa-signature ()
  ((r :initarg :r :reader dsa-signature-r)
   (s :initarg :s :reader dsa-signature-s)))

(defun dsa-key-p (dsa-key)
  (slot-value (group dsa-key) 'group-p))
(defun dsa-key-q (dsa-key)
  (slot-value (group dsa-key) 'group-q))
(defun dsa-key-g (dsa-key)
  (slot-value (group dsa-key) 'group-g))


;;; function definitions

(defun make-dsa-signature (r s)
  (make-instance 'dsa-signature
                 :r (maybe-integerize r) :s (maybe-integerize s)))

(defmethod make-public-key ((type (eql :dsa))
                            &key p q g group y &allow-other-keys)
  (when (and group (or p q g))
    (error "Cannot specify both a group and group components"))
  (let ((group (if group
                   group
                   (make-instance 'discrete-logarithm-group :p p :q q :g g))))
    (make-instance 'dsa-public-key :group group :y y)))

(defmethod make-private-key ((type (eql :dsa))
                             &key p q g group y x &allow-other-keys)
  (cond
    (group
     (when (or p q g)
       ;; FIXME: "real" ironclad error needed here
       (error "Cannot specify both a group and group components")))
    (t
     (unless (and p q g)
       ;; FIXME: "real" ironclad error needed here
       (error "Must specify all members of the DL group for DSA"))))
  (let ((group (if group
                   group
                   (make-instance 'discrete-logarithm-group :p p :q q :g g))))
    (make-instance 'dsa-private-key :group group :y y :x x)))

(defconstant +dsa-message-length+ 20)

;;; Note that hashing is not performed here.
(defmethod sign-message ((key dsa-private-key) msg &key (start 0) end)
  (let ((end (or end (length msg))))
    (unless (= (- end start) +dsa-message-length+)
      ;; FIXME: "real" ironclad error needed here
      (error "Can only sign exactly 20 bytes of message with DSA"))
    (let* ((group (group key))
           (k (random (group-qval group)))
           (r (mod (expt-mod (group-gval group) k (group-pval group))
                   (group-qval group)))
           (msg-integer (octets-to-integer msg :start start :end end))
           (k-inverse (modular-inverse k (group-qval group)))
           (s (mod (* k-inverse
                      (+ (* (dsa-key-x key) r) msg-integer))
                   (group-qval group))))
      (assert (= (mod (* k k-inverse) (group-qval group)) 1))
      (make-dsa-signature (integer-to-octets r) (integer-to-octets s)))))

(defun maybe-integerize (thing)
  (etypecase thing
    (integer thing)
    ((simple-array (unsigned-byte 8) (*)) (octets-to-integer thing))))

(defmethod verify-signature ((key dsa-public-key) msg (signature dsa-signature)
                             &key (start 0) end)
  (let ((end (or end (length msg))))
    (unless (= (- end start) +dsa-message-length+)
      ;; FIXME: "real" ironclad error needed here
      (error "Can only verify exactly 20 bytes of message with DSA"))
    (let* ((group (group key))
           (msg-integer (octets-to-integer msg))
           (r-integer (maybe-integerize (dsa-signature-r signature)))
           (s-integer (maybe-integerize (dsa-signature-s signature)))
           (w (mod (modular-inverse s-integer (group-qval group))
                   (group-qval group)))
           (u1 (mod (* msg-integer w) (group-qval group)))
           (u2 (mod (* r-integer w) (group-qval group)))
           (v (mod (mod (* (expt-mod (group-gval group) u1 (group-pval group))
                           (expt-mod (dsa-key-y key) u2 (group-pval group)))
                        (group-pval group))
                   (group-qval group))))
      (= v r-integer))))
