summaryrefslogtreecommitdiff
path: root/src/eris.lisp
diff options
context:
space:
mode:
authorPiotr Szarmanski2022-09-21 22:50:14 +0200
committerPiotr Szarmanski2022-09-21 22:50:14 +0200
commit56d3ca4cf14ac1b2bac9c866daa98cdb803915fa (patch)
treef97d64c7ba5903491db5ae48612507a4ae216859 /src/eris.lisp
Initial commit.
Diffstat (limited to 'src/eris.lisp')
-rw-r--r--src/eris.lisp244
1 files changed, 244 insertions, 0 deletions
diff --git a/src/eris.lisp b/src/eris.lisp
new file mode 100644
index 0000000..c3bbe5f
--- /dev/null
+++ b/src/eris.lisp
@@ -0,0 +1,244 @@
+;; This file is part of eris-cl.
+;; Copyright (C) 2022 Piotr SzarmaƄski
+
+;; eris-cl 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 3 of the License, or (at your option) any
+;; later versqion.
+
+;; eris-cl 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
+;; eris-cl. If not, see <https://www.gnu.org/licenses/>.
+
+(in-package :eris)
+
+(defun pad (input block-size)
+ (declare (type (simple-array (unsigned-byte 8)) input)
+ (type integer block-size))
+ (let* ((pad-size (- block-size (mod (length input) block-size)))
+ (padded-input (adjust-array input (+ pad-size (length input)) :initial-element 0)))
+ (replace padded-input input)
+ (setf (aref padded-input (length input)) #x80)
+ padded-input))
+
+(deftype block-size ()
+ `(member 1024 32768))
+
+(defconstant 32kib 32768)
+(defconstant 1kib 1024)
+
+(eval-when
+ (:execute :load-toplevel :compile-toplevel)
+ (defclass reference-pair ()
+ ((reference :initarg :reference :accessor reference :type (simple-array (unsigned-byte 8) 32))
+ (key :initarg :key :accessor key :type (simple-array (unsigned-byte 8) 32)))))
+
+(define-constant null-secret (make-array 32 :element-type '(unsigned-byte 8) :initial-element 0) :test #'equalp)
+
+(defun reference-pair-to-octets (pair buf &optional (start 0))
+ (replace buf (reference pair) :start1 start)
+ (replace buf (key pair) :start1 (+ 32 start)))
+
+(defun octets-to-reference-pair (octets)
+ (let ((key (make-array 32 :element-type '(unsigned-byte 8)))
+ (reference (make-array 32 :element-type '(unsigned-byte 8))))
+ (replace reference octets)
+ (replace key octets :start2 32)
+ (make-instance 'reference-pair :key key :reference reference)))
+
+
+(defun compute-reference (block)
+ (declare (type (simple-array (unsigned-byte 8)) block))
+ (let ((reference (make-array 32 :element-type '(unsigned-byte 8))))
+ (ironclad:digest-sequence :blake2/256 block :digest reference)
+ reference))
+
+(declaim (inline make-nonce))
+
+(defun make-nonce (level)
+ (let ((nonce (make-array 12 :element-type '(unsigned-byte 8) :initial-element 0)))
+ (setf (aref nonce 0) level)
+ nonce))
+
+(defun encrypt-block (input secret reference)
+ (declare (type (simple-array (unsigned-byte 8)) input secret reference))
+ (let ((mac (ironclad:make-mac :blake2-mac secret :digest-length 32))
+ (key (make-array 32 :element-type '(unsigned-byte 8))))
+ (ironclad:update-mac mac input)
+ (ironclad:produce-mac mac :digest key)
+ ;; NOT the IETF chacha. Need to fix this by patching ironclad. Maybe.
+ (ironclad:encrypt-in-place (ironclad:make-cipher :chacha :mode :stream :key key
+ :initialization-vector null-secret)
+ input)
+ (ironclad:digest-sequence :blake2/256 input :digest reference)
+ (make-instance 'reference-pair :key key :reference reference)))
+
+(defun encrypt-internal-block (input reference nonce)
+ (declare (type (simple-array (unsigned-byte 8)) input secret reference))
+ (let ((key (make-array 32 :element-type '(unsigned-byte 8))))
+ (ironclad:digest-sequence :blake2/256 input :digest key)
+ ;; NOT the IETF chacha. Need to fix this by patching ironclad. Maybe.
+ (ironclad:encrypt-in-place (ironclad:make-cipher :chacha :mode :stream :key key
+ :initialization-vector nonce)
+ input)
+ (ironclad:digest-sequence :blake2/256 input :digest reference)
+ (make-instance 'reference-pair :key key :reference reference)))
+
+(defun decrypt-block (input key &optional (nonce null-secret))
+ (ironclad:decrypt-in-place
+ (ironclad:make-cipher :chacha :mode :stream :key key :initialization-vector nonce)
+ input)
+ input)
+
+
+(defclass read-capability ()
+ ((block-size :initarg :block-size
+ :accessor block-size
+ :type block-size
+ :documentation "A value of either 1024 or 1kb blocks.")
+ (level :initarg :level :accessor level :type (unsigned-byte 8))
+ (root-reference-pair :initarg :reference-pair :accessor reference-pair)))
+
+
+
+(defun read-capability-to-octets (read-capability)
+ (declare (type read-capability read-capability))
+ (let ((cap (make-array 66 :element-type '(unsigned-byte 8))))
+ (case (block-size read-capability) ;; This depends on the version of the standard
+ (1024 (setf (aref cap 0) #x0a))
+ (32768 (setf (aref cap 0) #x0f)))
+ (setf (aref cap 1) (level read-capability))
+ (reference-pair-to-octets (reference-pair read-capability) cap 2)))
+
+(defun octets-to-read-capability (octets)
+ (declare (type (simple-array (unsigned-byte 8) (66)) octets))
+ (let ((capability (make-instance 'read-capability)))
+ (setf (block-size capability)
+ (case (aref octets 0)
+ (#x0a 1kib)
+ (#x0f 32kib)
+ (t (error 'version-mismatch))))
+ (setf (level capability)
+ (aref octets 1))
+ (setf (reference-pair capability)
+ (octets-to-reference-pair (subseq-shared octets 2)))
+ capability))
+
+(defun read-capability-to-urn (capability)
+ (concatenate 'string
+ "urn:eris:"
+ (bytes-to-base32-unpadded (read-capability-to-octets capability))))
+
+(defun urn-to-read-capability (urn)
+ (octets-to-read-capability (base32-to-bytes-unpadded (subseq urn (1+ (position #\: urn :from-end t))))))
+
+(defun reference-to-block-urn (reference)
+ (declare (type (simple-array (unsigned-byte 8) (32)) reference))
+ (concatenate 'string "urn:blake2b:" (bytes-to-base32-unpadded reference)))
+
+(defun block-urn-to-reference (urn)
+ (declare (type string urn))
+ (base32-to-bytes-unpadded (subseq urn (1+ (position #\: urn :from-end t)))))
+
+(defvar *output-hashmap* nil)
+
+(defmacro output-block (ref-vector &rest expr)
+ `(let ((reference (compute-reference block)))
+ (if hash-output
+ (unless (gethash reference *output-hashmap*)
+ (let ((rk (encrypt-block block secret reference)))
+ (vector-push-extend rk ,ref-vector)
+ (funcall output-function block (reference rk))
+ (setf (gethash reference *output-hashmap*) t)))
+ (let ((rk (encrypt-block block secret reference)))
+ (vector-push-extend rk ,ref-vector)
+ (funcall output-function block (reference rk))))
+ ,@expr))
+
+(defmacro output-internal-block (ref-vector nonce &rest expr)
+ `(let ((reference (compute-reference block)))
+ (if hash-output
+ (unless (gethash reference *output-hashmap*)
+ (let ((rk (encrypt-internal-block block reference ,nonce)))
+ (vector-push-extend rk ,ref-vector)
+ (funcall output-function block (reference rk))
+ (setf (gethash reference *output-hashmap*) t)))
+ (let ((rk (encrypt-internal-block block reference ,nonce)))
+ (vector-push-extend rk ,ref-vector)
+ (funcall output-function block (reference rk))))
+ ,@expr))
+
+(defgeneric eris-encode (input block-size output-function &key secret hash-output)
+ (:documentation
+ "Encode an input into block-size (32kib or 1kib) blocks, that are output using
+the function output-function. This function wil be called with two arguments: an
+encoded block and a 32-byte reference octet vector.
+
+An optional 32-byte secret can be passed for additional encryption using the
+:secret keyword."))
+
+(defmethod eris-encode ((input simple-array) block-size output-function &key (secret null-secret) hash-output)
+ (declare (type block-size block-size)
+ (type function output-function)
+ (type (simple-array (unsigned-byte 8) (32)) secret))
+
+ (setf input (pad input block-size))
+
+ (let ((reference-vector (make-array 16 :adjustable t :fill-pointer 0))
+ (*output-hashmap* (if hash-output (make-hash-table :test #'equalp) nil)))
+ (loop for block = (make-array block-size :element-type '(unsigned-byte 8) :initial-element 0)
+ for i = 0 then (incf i)
+ until (= (length input) (* i block-size))
+ do (progn
+ (replace block input :start2 (* i block-size))
+ (output-block reference-vector nil)))
+ (eris-create-tree reference-vector block-size output-function :hash-output hash-output)))
+
+(defmethod eris-encode ((input stream) block-size output-function &key (secret null-secret) hash-output)
+ (declare (type block-size block-size)
+ (type function output-function)
+ (type (simple-array (unsigned-byte 8) (32)) secret))
+ (let ((reference-vector (make-array 16 :adjustable t :fill-pointer 0))
+ (*output-hashmap* (if hash-output (make-hash-table :test #'equalp) nil)))
+ (loop for block = (make-array block-size :element-type '(unsigned-byte 8) :initial-element 0)
+ for bytes-read = (read-sequence block input)
+ for i = 0 then (incf i)
+ if (< bytes-read block-size)
+ do (setf (aref block bytes-read) #x80)
+ do (output-block reference-vector nil)
+ until (< bytes-read block-size))
+ (eris-create-tree reference-vector block-size output-function :hash-output hash-output)))
+
+(defun eris-create-tree (reference-vector block-size output-function &key hash-output)
+ (declare (type block-size block-size)
+ (type function output-function)
+ (type (simple-array (unsigned-byte 8) (32)) secret))
+ (loop with block-keys = (/ block-size 64)
+ with level = 0
+ with reference-vector-l = (make-array 16 :adjustable t :fill-pointer 0)
+ for nonce = (make-nonce (1+ level))
+ when (eql (length reference-vector) 1)
+ do (return (make-instance 'read-capability
+ :reference-pair (aref reference-vector 0)
+ :level level
+ :block-size block-size))
+ do (progn
+ (incf level)
+ ;; loop across the key-reference vector and build the tree
+ (loop with block = (make-array block-size :element-type '(unsigned-byte 8) :initial-element 0)
+ for rk across reference-vector
+ with i = 0
+ when (eql i block-keys)
+ do (output-internal-block reference-vector-l nonce
+ (setf block (make-array block-size :element-type '(unsigned-byte 8) :initial-element 0)
+ i 0))
+ do (progn (reference-pair-to-octets rk block (* 64 i))
+ (incf i))
+ finally (unless (zerop i)
+ ;; If i is zero, then the amount of blocks is just right. Otherwise add a final unfinished block.
+ (output-internal-block reference-vector-l nonce)))
+ (setf reference-vector reference-vector-l)
+ (setf reference-vector-l (make-array 16 :adjustable t :fill-pointer 0)))))