--- /dev/null
+---
+postid: 071
+title: Interfacing Common Lisp programs with GPG the (nearly) painless way
+date: April 27, 2018
+author: Lucian Mogoșanu
+tags: tech
+---
+
+A short introduction, for the atechnical: Lisp is one of the definitory
+programming languages for the fields of computer science and
+engineering. Common Lisp is a particular instance of Lisp, designed at
+some point in history as a unifying standard for various Lisp
+dialects. GPG is for the time being the one and only swiss army knife of
+cryptography, a tool that can aid one at identity (read: asymmetric key)
+management, encryption, sealing[^1] and [many others][v]. Unfortunately,
+in the last few years the integrity of GPG itself has been a target for
+[the usual wreckers][koch] and thus [its days are numbered][ffa].
+
+It very often happens in systems that two such seemingly unrelated
+components need to be interfaced; more specifically, in our case we
+would like to call GPG functionality from Common Lisp. "Traditionally"
+-- whatever that might mean -- this is supposedly achieved through the
+[GPGME][gpgme] library, which includes a set of Common Lisp bindings.
+
+I specifically added the "supposedly" above because I for one couldn't
+achieve this feat. While GPGME compiles without errors on my systems,
+the "language binding" glue code, written in Lisp and depending on
+[CFFI][cffi], throws very weird type errors that I for one am not
+willing to debug, especially given the general lack of documentation on
+[Google's interwebs][https]. It may very well be the case that I am
+technically incompetent. But I am rather willing to stick my hand in the
+fire[^2] that GPGME makes dishonest assumptions about the version of
+CFFI; and moreover, that both CFFI and GPGME (especially the latter) are
+balls of [doesn't-fit-in-head][fits-in-head] that aren't worth half the
+attention that I gave them.
+
+This blog post proposes a more elegant alternative for CL-GPG
+interfacing, a. that makes minimal assumptions about the underlying
+environment (e.g. Common Lisp implementation, GPG version), i.e. works
+on any "modern" Common Lisp implementation running inside a Unix
+system[^3]; and b. whose implementation fits in a blog post, and thus in
+the reader's head.
+
+First, the assumptions. We assume that the CL implementation can launch
+other Unix processes and communicate with them through pipes connecting
+the usual [standard I/O streams][unix-stdio] (std{in,out,err}). The
+astute reader may have already figured out that this approach is not
+fundamentally different from the Bash/Perl approach of launching a new
+process, feeding it input, then piping the output back to our script or
+program. That is all there is to it.
+
+For this purpose we will define a primitive `lispy-run-program`
+function, that can rely on whatever Unix process management primitives
+the underlying Common Lisp implementation provides. The example below
+uses [UIOP][uiop]'s `run-program` function.
+
+~~~~ {.commonlisp}
+(defun lispy-run-program (command &optional (input ""))
+ "Run `command' with optional `input'.
+
+Returns two strings, representing what the command wrote on the standard
+output and standard error respectively. The result (error code) of the
+command is ignored."
+ (let ((stdout (make-string-output-stream))
+ (stderr (make-string-output-stream)))
+ (with-input-from-string (stdin input)
+ (uiop:run-program command
+ :input stdin
+ :output stdout
+ :error-output stderr
+ :ignore-error-status t))
+ (values (get-output-stream-string stdout)
+ (get-output-stream-string stderr))))
+~~~~
+
+`lispy-run-program` takes as parameters a string `command` (the command
+to be run) and an optional string `input` (the input to be piped to the
+child process's standard input). It creates three (one input and two
+output) Common Lisp streams: `stdin`, `stdout` and `stderr`, and passes
+them to `run-program`. Then it "gets" the strings from the two output
+streams and returns both of them. Also note that the return code of the
+child process is ignored -- this can be changed by the programmer
+howsowever he or she wishes.
+
+Let's try out a few invocations of `lispy-run-program`:
+
+~~~~ {.commonlisp}
+; Listing /etc/sh*
+CL-USER> (lispy-run-program "ls /etc/sh*")
+"/etc/shadow
+/etc/shells
+"
+""
+
+; Listing a non-existing file
+CL-USER> (lispy-run-program "ls /bla")
+""
+"ls: cannot access '/bla': No such file or directory
+"
+
+; The equivalent of "echo 1 2 3 4 | cut -d' ' -f3"
+CL-USER> (lispy-run-program "cut -d' ' -f3" "1 2 3 4")
+"3
+"
+""
+~~~~
+
+As seen above, this simple function puts a very powerful
+[composition][pipes] tool directly at our fingertips. We will use it to
+interface with the following set of GPG functionalities in Common Lisp:
+encryption, decryption and signature verification. Note that all our
+processing will be performed on plain-text [ASCII-armored][ascii-armor]
+files, though the functions in this post can in principle be adapted to
+use binary inputs/outputs.
+
+Looking at the [GPG man page][man-gpg], and judging by the known
+behaviour of GPG, we notice that we can run it outside of a
+terminal-based environment -- and thus obtain full control over its
+input and output -- by passing the `--no-tty` argument. Thus, encryption
+can for example be written as:
+
+~~~~ {.commonlisp}
+(defun gpg-encrypt (input recipient)
+ "Encrypt `input' to `recipient'."
+ (lispy-run-program
+ (format nil "/path/to/gpg --no-tty -ea -r ~a" recipient)
+ input))
+~~~~
+
+Where `/path/to/gpg` is the path to the GPG executable inside the Unix
+file system. Similarly, decryption:
+
+~~~~ {.commonlisp}
+(defun gpg-decrypt (input)
+ "Decrypt ASCII-armored `input'."
+ (lispy-run-program "/path/to/gpg --no-tty -d" input))
+~~~~
+
+Similarly, clearsigning (not-quite-properly named "seal" here):
+
+~~~~ {.commonlisp}
+(defun gpg-seal (input &optional (uid ""))
+ "Make a cleartext signature of `input'.
+
+If `uid' is provided, the input will be signed using that specific key."
+ (let ((uid-flag (if (string/= "" uid)
+ (format nil "-u ~a" uid)
+ "")))
+ (lispy-run-program
+ (format nil "/path/to/gpg --no-tty --clearsign ~a" uid-flag)
+ input)))
+~~~~
+
+Notice that `gpg-seal` gets an optional `uid` parameter that can be used
+to sign as a specific key (using GPG's `-u` flag).
+
+Finally, seal verification:
+
+~~~~ {.commonlisp}
+(defun gpg-verify-seal (input)
+ "Verify cleartext-signed `input'."
+ (lispy-run-program "/path/to/gpg --no-tty --verify" input))
+~~~~
+
+Now let's test our functions. First, we create a test key:
+
+~~~~
+$ gpg --gen-key
+
+# usual GnuPG key generation follows
+# ...
+
+gpg: key 48BECFE5 marked as ultimately trusted
+public and secret key created and signed.
+
+pub 4096R/48BECFE5 2018-04-23
+ Key fingerprint = 533B 174D 1962 36B1 C066 670F CDD8 A167 48BE CFE5
+uid Tarpiter <tarpiter@thetarpit.org>
+sub 4096R/CF9F3670 2018-04-23
+~~~~
+
+And now back in the Lisp console, we play with the four
+functions. First, encryption and decryption.
+
+~~~~ {.commonlisp}
+CL-USER> (defvar *secret*)
+*SECRET*
+; Encrypt the string "a very sikrit test" to tarpiter's key and store it
+; in `*secret*'.
+CL-USER> (setq *secret* (gpg-encrypt "a very sikrit text" "tarpiter"))
+"-----BEGIN PGP MESSAGE-----
+
+hQIMAzPNTIDPnzZwAQ//X+f2PGhEbouCYSxtaOXfY4XQU6vFjlts63PEM+5qD5b3
+dSPVVJ+wjLZOK97eB0WGAqnGHRmIxpjr1vHVwK/b/uh+KPWTrCLbWZscGoSIUxrG
+GJpsQLmRJx4NEStDhjZ1+PwFod0aHqFJ32chiP4bTQfKt1vLDi0Cs7eEDAZzaBXV
+UrEa2t+IfY6BpOn4SUfMPJZDBnK+b1n7QV0gpRCp+x1qrf4Sun/PD7PuUy9zy66i
++HcVWnju4tEdpA7sNV/nsHHUzaDlVriJkFOTDNOvs4n8ku7Zvcl6GQjPFvrFYRo3
+e/G7+1GxphyWZN6ARd3PNybMAopZ+FlhwYHIvfGJgt8E3p9HgrHR18RQt1WadJF1
+NAYlVcF2LkY8qj4wdGwdhHnbDhuu8w+OsTF+RZS7c5FbtEKYaUgIT1lhiU5iPpkL
+tzwbVXNn6VOCcG3O65yaxAxf6RFO1vSo1hcB0xRDBJeMhhBeU+bJt9mVIQDV7FKt
+2mZQstZXOOFLT3CfIE4A0Nbe4F6/KYg+tImptnWtG2XP2a4RR39D4uAXV5mHpyDI
+FkcmyK7Qw2gfVV5slxmRZdzna3dkCe9MQUZLj0oD5sAXAJlXroBWOG0yyJHKCgnN
+AEhq4HEksMrQLkAVmpMIw4pYSLwxI6rlIJdWqDGUJICYj6qRh/OQPA9p0dclZJ/S
+TQH9UnRvNzfdCaitQrEndwe1l7BjZtHJYAT/3AFVTJU4nC9kSNFqLVYH71itGEr8
+5RhiNKHkdbNzb6HLmmuiZoQVzy+C60ofBy+F2ilR
+=s/mJ
+-----END PGP MESSAGE-----
+"
+; Decrypt the encrypted content of `*secret*'.
+CL-USER> (gpg-decrypt *secret*)
+"a very sikrit text"
+"gpg: encrypted with 4096-bit RSA key, ID CF9F3670, created 2018-04-23
+ \"Tarpiter <tarpiter@thetarpit.org>\"
+"
+~~~~
+
+Then, sealing and verification:
+
+~~~~ {.commonlisp}
+CL-USER> (defvar *clearsigned*)
+*CLEARSIGNED*
+; Clearsign the string "a very authentic text" and store it in
+; `*clearsigned*'.
+CL-USER> (setq *clearsigned* (gpg-seal "a very authentic text" "tarpiter"))
+"-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+a very authentic text
+-----BEGIN PGP SIGNATURE-----
+
+iQIcBAEBCgAGBQJa3jHGAAoJEM3YoWdIvs/leKYQALexMI0Md83qYwnvcjnxmqSw
+H7hGJWGir8UCBtR3nmzVUTj3dybwclwpkeAdbCMPlwfOuLtVlEi3u122t1fphq8g
+TJw+2OMWeX0A5TdhD/t9ZXXy9yS7QjOgEyRhzORO+Lsb4lPf7FOdKKXLpFfihmkv
+7Ew8IXoEUmvgcb0bEs5dX4Y7DKt6M9v0xdpUb9qrRa+Cp6qqwIpc+FCKq58dMGyA
+SL41hswKCXSnhrdVVi97QzJKyj3QJ33OdyNfXjM12AbFEDHQHkYYtKYk5DiH82lS
+lRVAIOwpD4xu7DdbUTRQJcQcBTJlN8RDCjwRx9E++ebzi/Mm5+8nXiiVyp2EQ4nk
+AiUEoTIoWUAouPVzj9fdxijejKx/yQmRegRR9drTJkZ3cX5YYbiiILMll0gNPBYw
+z4YMweLlpf6OyqdtlgwYym8nWEGOYmDA468h4NiEdzwaFEgoi5gzL5abHYnhoNLx
+q+GVHE5imblo4to/iDnv6lhVKcU4IwhDzw9Ku9WtaXa8gOYcdKyru5PQm1EogpCG
+kABFlikrvtPjg7RCJBpR1m/EmQW4t1BeOLyTk7Sc5RVimHc2V7C6cSmATHeDlLIY
+qWYafbdRSo/b+bbHU9c47KSlaYhpElbmY7fj3uv6dUvKMBSo+i+8U3BBeNXF5O5N
+zNIemhVEmp8cnnGY9Jf9
+=B6SZ
+-----END PGP SIGNATURE-----
+"
+; Verify the content and the seal in `*clearsigned*'.
+CL-USER> (gpg-verify-seal *clearsigned*)
+""
+"gpg: Signature made Mon 23 Apr 2018 22:19:34 EEST using RSA key ID 48BECFE5
+gpg: Good signature from \"Tarpiter <tarpiter@thetarpit.org>\"
+"
+; Modify a character in the clearsigned payload.
+CL-USER> (aref *clearsigned* 62)
+#\t
+CL-USER> (setf (aref *clearsigned* 62) #\c)
+#\c
+CL-USER> *clearsigned*
+"-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+a very authencic text
+-----BEGIN PGP SIGNATURE-----
+
+iQIcBAEBCgAGBQJa3jHGAAoJEM3YoWdIvs/leKYQALexMI0Md83qYwnvcjnxmqSw
+H7hGJWGir8UCBtR3nmzVUTj3dybwclwpkeAdbCMPlwfOuLtVlEi3u122t1fphq8g
+TJw+2OMWeX0A5TdhD/t9ZXXy9yS7QjOgEyRhzORO+Lsb4lPf7FOdKKXLpFfihmkv
+7Ew8IXoEUmvgcb0bEs5dX4Y7DKt6M9v0xdpUb9qrRa+Cp6qqwIpc+FCKq58dMGyA
+SL41hswKCXSnhrdVVi97QzJKyj3QJ33OdyNfXjM12AbFEDHQHkYYtKYk5DiH82lS
+lRVAIOwpD4xu7DdbUTRQJcQcBTJlN8RDCjwRx9E++ebzi/Mm5+8nXiiVyp2EQ4nk
+AiUEoTIoWUAouPVzj9fdxijejKx/yQmRegRR9drTJkZ3cX5YYbiiILMll0gNPBYw
+z4YMweLlpf6OyqdtlgwYym8nWEGOYmDA468h4NiEdzwaFEgoi5gzL5abHYnhoNLx
+q+GVHE5imblo4to/iDnv6lhVKcU4IwhDzw9Ku9WtaXa8gOYcdKyru5PQm1EogpCG
+kABFlikrvtPjg7RCJBpR1m/EmQW4t1BeOLyTk7Sc5RVimHc2V7C6cSmATHeDlLIY
+qWYafbdRSo/b+bbHU9c47KSlaYhpElbmY7fj3uv6dUvKMBSo+i+8U3BBeNXF5O5N
+zNIemhVEmp8cnnGY9Jf9
+=B6SZ
+-----END PGP SIGNATURE-----
+"
+; Verify it again.
+CL-USER> (gpg-verify-seal *clearsigned*)
+""
+"gpg: Signature made Mon 23 Apr 2018 22:19:34 EEST using RSA key ID 48BECFE5
+gpg: BAD signature from \"Tarpiter <tarpiter@thetarpit.org>\"
+"
+~~~~
+
+and so on.
+
+Finally, some considerations. First, the functions presented above
+assume that the `recipient` and `uid` arguments -- or for that matter
+any other arguments that might be passed from the program to the command
+line -- are either trusted or pre-processed by the programmer. Consider
+the following common example:
+
+~~~~ {.commonlisp}
+; We seal to tarpiter, but in the process also execute an arbitrary
+; command.
+CL-USER> (gpg-seal "" "tarpiter; ls /etc/passwd")
+"*snip'ed GPG output*
+/etc/passwd
+"
+""
+~~~~
+
+There is no way to avoid this other than by doing very strict
+parsing/sanitization on the inputs, which is outside the scope of this
+article. It's also worth mentioning that some classes of problems,
+e.g. [deedbot][deedbot] authentication[^4], use (constant)
+programmer-defined sensitive inputs, and thus are not susceptible to
+this leaky abstraction problem.
+
+A similar issue is related to passphrase processing, assuming that the
+programmer uses such keys. For this purpose, GPG provides the
+`--passphrase*` class of command-line flags, of which `--passphrase-fd`
+is the sanest, as it can map to an explicit communication pipe between
+the two processes; on the other hand, `--passphrase` should be avoided
+at all costs, as anyone with the ability to execute e.g. `ps` can see
+the command-line of processes in the system.
+
+Additionally, we notice that our functions do not process the output in
+any way, relying on the programmer for e.g. error checking or automation
+of signature verification, the latter being crucial to the correct
+implementation of [V][v].
+
+Finally, the reader has probably noticed that there is a lot of
+functionality missing from these examples. There are many ways to use
+GPG; for example the programmer may need to verify detached signatures
+instead of the clearsigned variety that we've provided. It would be
+pointless and potentially dangerous to attempt devising a general
+interface to GPG using this approach, which is maybe the only aspect
+giving GPGME an advantage. That aside, the examples above can be
+extended without much hassle to work with most of the well-known GPG
+recipes.
+
+Of course, I'm not the first to have thought of this. [v.pl][v.pl]
+interfaces with GPG this way, albeit from Perl rather than Common
+Lisp. Moreover I've found out that [Andrew][esthlos] has written a Lisp
+V that uses SBCL's `run-program` in pretty much the same way as
+described here. I'm just adding my report to the journal in the hope
+that it'll be useful to other people attempting similar things in the
+future.
+
+**Update 1**: Trinque has [brought to my attention][trinque-comment],
+and phf has [followed up][phf-comment], that sending command-line inputs
+via `format` is very bad practice. In particular UIOP provides a
+somewhat saner approach to shell command passing, via `escape-sh-token`
+and similar functions. For example we can have `uiop:run-program` take a
+list of tokens instead of a single string, e.g. `("/bin/ls" "-l"
+"/")`. Our `lispy-run-program` then becomes:
+
+~~~~ {.commonlisp}
+(defun lispy-run-program (command &optional (input ""))
+ "Run `command' with optional `input'.
+
+`command' is a list whose first element is the name of the program to be
+executed and the rest of the elements are each a command-line argument
+to be passed to the program.
+
+Returns two strings, representing what the command wrote on the standard
+output and standard error respectively. The result (error code) of the
+command is ignored."
+ (check-type command list)
+ (let ((stdout (make-string-output-stream))
+ (stderr (make-string-output-stream)))
+ (with-input-from-string (stdin input)
+ (uiop:run-program command
+ :input stdin
+ :output stdout
+ :error-output stderr
+ :ignore-error-status t))
+ (values (get-output-stream-string stdout)
+ (get-output-stream-string stderr))))
+~~~~
+
+and `gpg-encrypt` and `gpg-decrypt` are implemented as:
+
+~~~~ {.commonlisp}
+(defun gpg-encrypt (input recipient)
+ "Encrypt `input' to `recipient'."
+ (lispy-run-program
+ (list "/path/to/gpg" "--no-tty" "-ea" "-r" recipient)
+ input))
+
+(defun gpg-decrypt (input)
+ "Decrypt ASCII-armored `input'."
+ (lispy-run-program
+ (list "/path/to/gpg" "--no-tty" "-d")
+ input))
+~~~~
+
+A reimplementation of the GPG interface using UIOP is provided in
+[gpg-uiop.lisp][gpg-uiop.lisp]. The reader is encouraged to read them
+and try them out, e.g. against the examples above.
+
+**Update 2**: Trinque and phf [also][trinque-comment2]
+[comment][phf-comment2] that UIOP, or rather ASDF3's inclusion of said
+library, is generally ill-regarded due to the former's inclusion as an
+implicit dependency. An implementation of `lispy-run-program` using
+SBCL's own `run-program` is provided in [gpg-sbcl.lisp][gpg-sbcl.lisp]
+for the reader to inspect. Note that the GPG functions using it remain
+the same as gpg-uiop.lisp. Also note that `sb-ext:run-program` is by
+default stricter than UIOP's, in that it requires the full path of the
+program to be executed, `PATH` (and other environment variables)
+requiring explicit specification.
+
+[^1]: A brief yet very useful discussion on the subject of sealing is
+ given by Nick Szabo's "[The Playdough Protocols][sealing]".
+
+[^2]: "A băga mâna în foc că [...]". Romanian expression, connoting, as
+ the astute reader might intuit, the willingness to bet one's ass on
+ the veracity of the statement that follows it.
+
+ Since I'm doing Romanian-English translations, I might as well give
+ these short expressions a shot, so expect to see more of them in the
+ future.
+
+[^3]: Though I don't see why this approach wouldn't work with any
+ "non-modern" Common Lisp implementations running on a Unix machine,
+ as long as the former can access the latter's functionality.
+
+[^4]: Which incidentally is the problem I was working on that caused me
+ to trip the GPGME landmine.
+
+[sealing]: http://archive.is/9i7mx
+[v]: /posts/y04/069-on-intellectual-ownership.html#selection-39.0-43.14
+[koch]: http://trilema.com/2016/werner-koch-lies/
+[ffa]: http://www.loper-os.org/?cat=49
+[gpgme]: http://archive.is/AHhfP
+[cffi]: http://archive.is/Xr6V4
+[https]: /posts/y03/05b-https-war-declaration.html
+[fits-in-head]: http://btcbase.org/log-search?q=fits+in+head
+[unix-stdio]: http://archive.is/FGN6n
+[uiop]: http://quickdocs.org/uiop/
+[pipes]: /posts/y00/00d-composition-operators-pipes.html
+[ascii-armor]: https://archive.is/h0o1u#selection-3411.503-3421.0
+[man-gpg]: http://archive.is/kj2Bv
+[deedbot]: http://deedbot.org/
+[v.pl]: http://thebitcoin.foundation/index.html
+[esthlos]: http://www.esthlos.com/posts/2018/03/17/1.html
+[trinque-comment]: http://btcbase.org/log/2018-04-27#1805898
+[phf-comment]: http://btcbase.org/log/2018-04-27#1805919
+[trinque-comment2]: http://btcbase.org/log/2018-04-27#1805904
+[phf-comment2]: http://btcbase.org/log/2018-04-27#1805912
+[gpg-uiop.lisp]: /uploads/2018/04/gpg-uiop.lisp
+[gpg-sbcl.lisp]: /uploads/2018/04/gpg-sbcl.lisp
--- /dev/null
+---
+postid: 072
+title: trilemabot [i]: introduction and self-voicing patch proposal
+date: June 5, 2018
+author: Lucian Mogoșanu
+tags: tech
+---
+
+First, the rationale and scope definition. In an ideal, purely platonic
+world, trilemabot represents a perfect cut of the points in the
+[#trilema bot spec][trilema-bot-spec] that are common to all #trilema
+bots, past, present and future. In practice, basing his or her bot upon
+the Republican [ircbot][ircbot] standard, an operator could be served by
+some or all of the following pieces of scaffolding: self-voicing, a
+means to implement prefixed commands, querying [deedbot][deedbot] to get
+[WoT][wot] ratings or perform automated payments, and others -- the
+list, I suspect, is far from exhaustive. This is where trilemabot should
+prove itself useful.
+
+Following discussions in [the forum][btcbase-1819034], this post
+presents a first patch proposal for trilemabot, that adds self-voicing
+on top of ircbot. **Please note** that this proposal is at the moment merely **a
+draft**: the final patch will be posted here after review by the
+Lordship, and as a regrind *on top* of ircbot[^1].
+
+Now, without further ado, the README:
+
+~~~~
+`trilemabot` extends `ircbot` with #trilema-specific functionality. See the
+following for more details:
+
+ * http://trilema.com/2016/how-to-participate-in-the-affairs-of-the-most-serene-republic/
+ * http://trilema.com/2016/trilema-bot-spec/
+ * http://thetarpit.org/posts/y04/072-trilemabot-i.html
+
+Currently the following functionalities are implemented:
+
+ * self-voicing with deedbot, using a list of pre-provided OTPs
+~~~~
+
+INSTALL:
+
+~~~~
+ * Install and load cl-irc and its dependencies
+
+ * Install ircbot
+
+ * Use V to press `trilemabot`
+
+mkdir -p ~/src/trilemabot
+cd ~/src/trilemabot
+
+mkdir .wot
+cd .wot && wget http://lucian.mogosanu.ro/spyked.asc && cd ..
+
+v.pl init http://lucian.mogosanu.ro/src/trilemabot/
+v.pl press trilemabot-voicer trilemabot-voicer.vpatch
+
+ * Load `trilemabot` via Quicklisp or directly using ASDF:
+
+(dolist (path '("/path/to/ircbot/" "~/src/trilemabot/trilemabot-voicer/"))
+ (pushnew path asdf:*central-registry* :test #'string=))
+(asdf:load-system :trilemabot)
+~~~~
+
+And last but not least, USAGE:
+
+~~~~ {.commonlisp}
+(asdf:load-system :trilemabot)
+(defvar *bot*)
+(setq *bot*
+ (trlb:make-trilemabot
+ "chat.freenode.net" 6667 "nick" "password" '("#trilema")))
+
+;; connect in separate thread, returning thread
+(ircbot:ircbot-connect-thread *bot*)
+
+;;;; 1. Self-voicing
+
+;; ask for n !!up OTPs from deedbot
+(loop for i from 1 to n do
+ (trlb:trilemabot-send-up *bot*)
+ (sleep 0.5))
+
+;; get messages received from deedbot
+(trlb:trilemabot-inbox *bot*)
+
+;; add decrypted !!up OTPs
+(trlb:trilemabot-add-voice-otps *bot*
+ "decrypted-otp-1" "decrypted-otp2" ... "decrypted-otpn")
+
+;; get the list of !!up OTPs
+(trlb:trilemabot-voice-otp-stash *bot*)
+
+;; save !!up OTPs to disk
+(trlb:trilemabot-save-voice-otp-stash *bot* "voice-otps.sexp")
+
+;; self-voice
+(trlb:trilemabot-voice *bot*)
+
+;; if `voice-otp-stash' is not empty, the bot will automatically
+;; self-voice on reconnect
+(ircbot:ircbot-reconnect *bot*)
+~~~~
+
+[^1]: At the time of writing this is not possible because ircbot and
+ trilemabot are separate modules, and they absolutely require a
+ [manifest][manifest] as the binder that puts them in the same [V][v]
+ tree.
+
+[trilema-bot-spec]: http://trilema.com/2016/trilema-bot-spec/
+[ircbot]: http://trinque.org/2016/08/10/ircbot-genesis/
+[deedbot]: http://deedbot.org/help.html
+[wot]: http://trilema.com/2014/what-the-wot-is-for-how-it-works-and-how-to-use-it/
+[btcbase-1819034]: http://btcbase.org/log/2018-05-25#1819034
+[manifest]: http://trinque.org/2018/06/02/v-manifest-specification/
+[v]: http://cascadianhacker.com/07_v-tronics-101-a-gentle-introduction-to-the-most-serene-republic-of-bitcoins-cryptographically-backed-version-control-system
--- /dev/null
+---
+postid: 073
+title: The story of Prikoke
+date: June 12, 2018
+author: Lucian Mogoșanu
+tags: storytime
+---
+
+*Below lies a translation of Prikoke, an animated fable from Planeta
+Moldova. The original is still available somewhere on
+[the youtubes][youtube], grab it while you still can.*
+
+**Narrator-raven** [standing on the roof of a pigsty, in a wintery
+setting]: This is the sty of Prikoke, the happy piglet. The two-legged
+beasts tend him and feed him with steaming swill a few times a day, and
+Prikoke is extremely pleased with his life -- caw-caw! Lately, the
+two-legged beasts have visited him more often than usual, talking
+somethings about hundreds of kilograms, about lard, puddings and
+sausages. This got Prikoke thinking.
+
+Inside the pigsty, Prikoke.
+
+**Prikoke**: I wonder what these beasts have in store for me? Maybe they
+want to swill me more. Hm... But I can't eat more. Or maybe they want to
+set me free, I believe -- why else would they feed me so much? They want
+me to be strong so I can romp about carefree and happy. These two-legged
+beasts have such a big heart, bless them.
+
+**Narrator-raven**: Caw-caw! This is Mefi[^1], the cat from the
+two-legged beasts' household.
+
+**Mefi**: Meow! Bless the two-legged beasts, you say? Meow... You believe
+they have a big heart? You silly Prikoke!
+
+**Prikoke**: What do you mean, silly, Mefi? I'm the happiest pig alive. I
+have everything I need.
+**Mefi**: Meow! Everything you need? Heheh... Look at you, Prikoke, you're
+as naive as you're fat. Listen to me, Prikoke. I don't mean you any
+harm. I don't want to see you end up like those other ones.
+
+**Prikoke**: What other ones?
+**Mefi**: Prikoke, you have no idea how many like you have lived here in
+this rotten shack. Pinkers, Jointy, Curly, Snouter -- oh! how many like
+you wound up in jellies and soups, in sausages, pasties, scrapples and
+steaks -- all of them ended up in the two-legged beasts' bellies.
+
+**Prikoke**: Huh, what do you mean? That the beasts are evil? You think
+they want to eat me? [Upset.] Get outta here with your lies. The beasts
+are my friends: they scratch me behind my ears, they talk to me, they
+laugh, give me smiles and swill me; their children pet me and indulge me
+with their sweet words. But you, you sly beast, you come here to stir me
+up against them?
+
+**Mefi**: Meow! Prikoke! Listen carefully! Look around you! Don't you
+see you're living in filth up your neck? What do you think? that the
+refuse they give you they tear out from their liver[^2]? They give you
+only scraps; if you only saw what they eat! But I don't want to spin it
+out; we're out of time, tomorrow's a big feast and I came here to
+help. Meow!
+
+**Prikoke**: Oink-oink! Help me? Help me with what?
+**Mefi**: Meow! Listen to me carefully, Prikoke. Make an effort and get
+up; go to that wall at the end of the shack and rush towards it with all
+you have. This shack is rotten by now, Prikoke. And you, with your
+weight, will break through the wall like butter. And don't fear, you
+need to be brave! That's the only way you can escape. But then you'll
+run into the woods, to freedom. There you'll become friends with the
+wild swine. They'll show you what to eat, how to live, and you'll live
+happily ever after. Listen well to what I'm saying, Prikoke.
+
+**Prikoke**: Huh, freedom, you say? But what, I don't have freedom? I
+have everything I need here! Food, a home, a bed...
+**Mefi**: All you want my ass! You know nothing of this world, Prikoke. You
+never had anything, so you think you have everything.
+**Prikoke**: But more than this I don't even need! I'm satisfied with what
+I have!
+
+**Mefi**: Meow! Satisfied? Tomorrow you'll be satisfied! with the skewer in
+your heart!
+**Prikoke**: With what in my heart?
+
+**Mefi**: Oh, I'll shut up... or I'll scare you. Better do as I said,
+Prikoke! Come, fast! Budge that huge ass of yours and rush towards
+freedom! Go, Prikoke, I know you can. Hurry and break this filthy
+shack! Run to your freedom!
+
+While the cat tries to convince him, Prikoke gets up slowly. Meanwhile,
+the pig notices the swill trough and heads that way.
+
+**Mefi**: [Perplexed.] Meow!
+
+**Prikoke**: Oink-oink! [Eating.] Mefi... So, come to me tomorrow and
+we'll talk, yes? 'Cause I have some beet with bread to finish up. Want
+some?
+**Mefi**: [Sighs.] Prikoke... Well, then. Go, then. Whatever, goodbye. By
+now, who knows if we'll ever see each other. In other words, farewell,
+Prikoke, and merry Christmas!
+
+The cat goes away. We're back on the roof of the pigsty, with the
+narrator-raven speaking.
+
+**Narrator-raven**: Caw-caw! Well, this is the story of Prikoke. Now
+I'll leave you to draw the conclusion yourself -- I'll go search for an
+earthworm 'cause I'm hungry.
+
+Narrator-raven flies. The view goes down towards the shack, the shadow
+of a two-legged beast looming over its door. While the door opens and
+the view moves at the border of the yard, sounds of sharpening knives
+can be heard in the background.
+
+That's all.
+
+[^1]: Some obscure transcripts on the web erroneously call him
+ Nefi. It is quite obvious that the cat depicted herein is a
+ mephistophelian agent; thus, Mefi.
+
+[^2]: The original reads, I quote: "Miau! Prikoke! Ascultă-mă cu
+ atenție! Uite în jurul tău! Tu nu vezi că trăiești pân' la gât în
+ mizerie? Ce crezi tu? că lăturile care ți le dau ei le rup de la
+ sufletul lor?" Upon careful consideration and council with people
+ much better than me at this English thing, I have come to the
+ conclusion that that last bit is not really translatable. So I took
+ the liberty to place the two-legged-beastly soul in their
+ liver. This makes perfect sense, doesn't it?
+
+[youtube]: https://www.youtube.com/watch?v=aerj4qXPHJc
--- /dev/null
+---
+postid: 074
+title: An early Lisp scriptlang prototype in Ada
+date: June 24, 2018
+author: Lucian Mogoșanu
+tags: tech
+---
+
+This document describes the result of my attempts at implementing a Lisp
+(more precisely a Scheme) scripting language in Ada. The effort spawned
+from numerous discussions in the logs[^1] and used a few important
+references[^2]. In short, the goal is to have a Lisp interpreter that is
+a. small, easy to read, i.e. fits-in-head; and b. written in a sane
+language, sans pointers and other cockroaches inhabiting today's
+[broken computing machines][software-engineering].
+
+I have attached below:
+
+* a tarball: [adalisp-proto.tar.gz][adalisp-proto.tar.gz]; and
+* a detached signature by yours truly:
+ [adalisp-proto.tar.gz.spyked.sig][adalisp-proto.tar.gz.spyked.sig].
+
+The source code is organized as follows:
+
+* a set of restrictions for the implementation (`restrict.adc`)[^3];
+* a Lisp machine representation in Ada (`lispm.{ads,adb}`);
+* a S-expression parser (`parser.{ads,adb}`);
+* a Scheme evaluator (`evaler.{ads,adb}`); and finally,
+* some glue that puts everything together in a Read-Eval-Print Loop
+ (`test_repl.adb`).
+
+Additionally, the tarball includes a GPRbuild project file and a small
+set of tests. To compile and run the tests, run in the project
+directory:
+
+~~~~
+$ gprbuild
+$ cat test.scm | ./bin/test_repl
+~~~~
+
+In the end, some comments about the implementation:
+
+[i] The implementation is incomplete. A lot of functionality is still
+unimplemented, e.g. `<`, `>`, `gensym`, quasiquotes, `let*`,
+`letrec`. This is just a matter of adding them into the `evaler`.
+
+[ii] The implementation is incomplete also because it has no mechanism
+for recovery from parser and evaler errors, etc.
+
+[iii] Reading from standard input is utterly broken in many ways,
+because Ada's `Get_Char` doesn't know how to read newlines among
+others. These should be replaced with FFA's [I/O routines][ffacalc-io].
+
+[iv] The author's cockroaches led him to implement Scheme booleans
+(`#t`, `#f`) as a subset of integers. These are traditionally
+symbols. On another note, it's not immediately clear that schemisms such
+as `#t`/`#f` are relevant at all, one might as well use `t`/`nil` as
+truth values.
+
+[vi] The code should be refactored in more than one direction. One
+important direction is separating all the mechanisms in a `libadalisp`
+to be used for embedding into other applications. Another direction
+involves improving readability by e.g. using keyword arguments instead
+of the positional ones that are so popular in C programs. But more
+importantly, the user should be able to swallow the program piece by
+piece, as is healthy and has been [demonstrated before][ffa-ch1].
+
+[vii] I suspect the `evaler` is [far from correct][logs-evaluation], but
+this can only be evaluated [sic!] after the completion of point vi
+above.
+
+[viii] Both the parser[^4] and printer can be replaced with `lispm`
+bytecode that gets loaded at the beginning of the world and evaluated as
+needed. The current `parser` module could be possibly used to bootstrap
+the bytecode generation process.
+
+[ix] Currently, `lispm` memory has a fixed static size. As
+[FFACalc][ffacalc-stack] demonstrates, the heap size can be passed as a
+command-line argument and initialized when the program boots. This gives
+the user some flexibility in choosing how much memory their Lisp program
+can use, especially given the fact that there is no garbage collection
+implemented.
+
+[x] The list is far from exhaustive. Some aspects are already being
+discussed in the logs; other threads will inevitably arise.
+
+[^1]: See for example: [1][logs-adalisp], [2][logs-ada-scheme],
+ [3][logs-ada-lisp].
+
+[^2]: Kogge's "The Architecture of Symbolic Computers", Queinnec's "Lisp
+ in Small Pieces", (in some cases) the [tinyscheme][tinyscheme]
+ implementation, the [FFA][ffa] implementation and last but not
+ least, the [Ada reference manual][ada].
+
+[^3]: For more details, consult the [FFA][ffa] series, in particular the
+ [first chapter][ffa-ch1].
+
+[^4]: Discussion in the logs: [1][logs-bytecode], [2][logs-bc-detailed].
+
+[logs-adalisp]: http://btcbase.org/log-search?q=adalisp
+[logs-ada-scheme]: http://btcbase.org/log-search?q=ada+scheme
+[logs-ada-lisp]: http://btcbase.org/log-search?q=ada+lisp
+[tinyscheme]: http://tinyscheme.sourceforge.net/
+[ffa]: http://www.loper-os.org/?cat=49
+[ffa-ch1]: http://www.loper-os.org/?p=1913
+[ada]: http://ada-auth.org/standards/ada12.html
+[software-engineering]: /posts/y02/03c-the-myth-of-software-engineering.html
+[adalisp-proto.tar.gz]: /uploads/2018/06/adalisp-proto.tar.gz
+[adalisp-proto.tar.gz.spyked.sig]: /uploads/2018/06/adalisp-proto.tar.gz.spyked.sig
+[ffacalc-io]: http://btcbase.org/patches/ffa_ch4_ffacalc#L816
+[logs-evaluation]: http://btcbase.org/log/2018-06-21#1828378
+[logs-bytecode]: http://btcbase.org/log/2018-06-22#1828584
+[logs-bc-detailed]: http://btcbase.org/log/2018-06-25#1829352
+[ffacalc-stack]: http://btcbase.org/patches/ffa_ch4_ffacalc#L202
--- /dev/null
+---
+postid: 075
+title: The return of the lost son of the father of adalisp, now in a genesis
+date: July 2, 2018
+author: Lucian Mogoșanu
+tags: tech
+---
+
+[Prelude][logs-discussion]:
+
+> **mircea_popescu**: spyked> I think there's great benefit in the ffa
+> chapter-based approach << that ~started with a genesis~.
+> **mircea_popescu**: so did diana_coman 's eucrypt.
+> **mircea_popescu**: genesis should have included exactly the thing you
+> published.
+>
+> **mircea_popescu**: **asciilifeform**:
+> http://btcbase.org/log/2018-06-25#1829397 << imho a genesis oughta be
+> a proggy, if a minimal one << nah, a genesis can well be nothing more
+> than the manifest, if the manifest is a one line poem.
+> a111: Logged on 2018-06-25 12:20 **spyked**: I think there's great
+> benefit in the ffa chapter-based approach, so I'm not sure yet what
+> the genesis should include. maybe only the lispm piece? or only the
+> spec file from lispm? my sense so far is that after some discussion,
+> at least some of the parts will be rewritten
+> **mircea_popescu**: there is \~no\~ benefit to delaying
+> genesis-ification. all it buys one is the naggum stupidity,
+> http://btcbase.org/log/2018-06-25#1829365
+> a111: Logged on 2018-06-25 06:15 **mircea_popescu**: this habit of
+> "i'll publish" is on the record having killed more smart men than the
+> police.
+> **mircea_popescu**: that's the one part of naggum you absolutely do
+> not want.
+> **mircea_popescu**: we already have a mechanism to deal with
+> irrelevance, there's no need for an ad-hoc, broken, personal
+> implementation whenever something useful may be occuring, to fuck it
+> up.
+>
+> **mircea_popescu**: but to reiterate : there's nothing wrong with a
+> broken, not complete, not working, not pretty etc genesis. most
+> neonates are also not worth the fucking.
+
+The item in question:
+
+* [adalisp_genesis.vpatch][adalisp_genesis.vpatch]
+* [adalisp_genesis.vpatch.spyked.sig][adalisp_genesis.vpatch.spyked.sig]
+
+As per the readme, one can press it directly using e.g. mod6's [V][v]
+[implementation][mod6-v]:
+
+~~~~
+mkdir ~/src/adalisp
+cd ~/src/adalisp
+
+mkdir .wot
+cd .wot && wget http://lucian.mogosanu.ro/spyked.asc && cd ..
+
+v.pl init http://lucian.mogosanu.ro/src/adalisp
+v.pl press adalisp_genesis adalisp_genesis.vpatch
+~~~~
+
+Details are available in the [previous post][adalisp-prototype].
+
+[logs-discussion]: http://btcbase.org/log/2018-06-25#1829429
+[adalisp_genesis.vpatch]: http://lucian.mogosanu.ro/src/adalisp/v/patches/adalisp_genesis.vpatch
+[adalisp_genesis.vpatch.spyked.sig]: http://lucian.mogosanu.ro/src/adalisp/v/seals/adalisp_genesis.vpatch.spyked.sig
+[v]: http://cascadianhacker.com/07_v-tronics-101-a-gentle-introduction-to-the-most-serene-republic-of-bitcoins-cryptographically-backed-version-control-system
+[mod6-v]: http://thebitcoin.foundation/
+[adalisp-prototype]: /posts/y04/074-adalisp-prototype.html
--- /dev/null
+(defun lispy-run-program (command &optional (input ""))
+ "Run `command' with optional `input'.
+
+`command' is a list whose first element is the path to the program to be
+executed and the rest of the elements are each a command-line argument
+to be passed to the program.
+
+Returns two strings, representing what the command wrote on the standard
+output and standard error respectively. The result (error code) of the
+command is ignored."
+ (check-type command list)
+ (assert (car command) (command)
+ "Command must contain at least one element.")
+ (let ((stdout (make-string-output-stream))
+ (stderr (make-string-output-stream)))
+ (with-input-from-string (stdin input)
+ (sb-ext:run-program (car command)
+ (cdr command)
+ :input stdin
+ :output stdout
+ :error stderr))
+ (values (get-output-stream-string stdout)
+ (get-output-stream-string stderr))))
+
+(defun gpg-encrypt (input recipient)
+ "Encrypt `input' to `recipient'."
+ (lispy-run-program
+ (list "/usr/bin/gpg1" "--no-tty" "-ea" "-r" recipient)
+ input))
+
+(defun gpg-decrypt (input)
+ "Decrypt ASCII-armored `input'."
+ (lispy-run-program
+ (list "/usr/bin/gpg1" "--no-tty" "-d")
+ input))
+
+(defun gpg-seal (input &optional (uid ""))
+ "Make a cleartext signature of `input'.
+
+If `uid' is provided, the input will be signed using that specific key."
+ (let ((uid-flag (if (string/= "" uid)
+ (list "-u" uid)
+ nil)))
+ (lispy-run-program
+ (append (list "/usr/bin/gpg1" "--no-tty" "--clearsign")
+ uid-flag)
+ input)))
+
+(defun gpg-verify-seal (input)
+ "Verify cleartext-signed `input'."
+ (lispy-run-program
+ (list "/usr/bin/gpg1" "--no-tty" "--verify")
+ input))
--- /dev/null
+(defun lispy-run-program (command &optional (input ""))
+ "Run `command' with optional `input'.
+
+`command' is a list whose first element is the name of the program to be
+executed and the rest of the elements are each a command-line argument
+to be passed to the program.
+
+Returns two strings, representing what the command wrote on the standard
+output and standard error respectively. The result (error code) of the
+command is ignored."
+ (check-type command list)
+ (let ((stdout (make-string-output-stream))
+ (stderr (make-string-output-stream)))
+ (with-input-from-string (stdin input)
+ (uiop:run-program command
+ :input stdin
+ :output stdout
+ :error-output stderr
+ :ignore-error-status t))
+ (values (get-output-stream-string stdout)
+ (get-output-stream-string stderr))))
+
+(defun gpg-encrypt (input recipient)
+ "Encrypt `input' to `recipient'."
+ (lispy-run-program
+ (list "/usr/bin/gpg1" "--no-tty" "-ea" "-r" recipient)
+ input))
+
+(defun gpg-decrypt (input)
+ "Decrypt ASCII-armored `input'."
+ (lispy-run-program
+ (list "/usr/bin/gpg1" "--no-tty" "-d")
+ input))
+
+(defun gpg-seal (input &optional (uid ""))
+ "Make a cleartext signature of `input'.
+
+If `uid' is provided, the input will be signed using that specific key."
+ (let ((uid-flag (if (string/= "" uid)
+ (list "-u" uid)
+ nil)))
+ (lispy-run-program
+ (append (list "/usr/bin/gpg1" "--no-tty" "--clearsign")
+ uid-flag)
+ input)))
+
+(defun gpg-verify-seal (input)
+ "Verify cleartext-signed `input'."
+ (lispy-run-program
+ (list "/usr/bin/gpg1" "--no-tty" "--verify")
+ input))
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iQIcBAABCgAGBQJbMAJlAAoJEL2unQUaPTuV4L4P/2RMocQqxgktyrMSBmhyOvm1
+lv0dORIfi7ZBG8Dp7cNRe5naKuZts6j303T5GLd+mmvyDEUQCXUCRn3ciSWAY27R
+1hCeONWRSH9uaMt3kOwo1L8z18WvzYIKPVr/8pQ9ywVLOngYf1mTqo4lM0Rfyq/j
+B/fJow0VrMvblf4qLu1ry8YtfAdEND8AhaRypK3uhwZN3YRYyNFj9HP3uxoarm+X
+H6mc9Nwg+xY99Zq12A3vxSKYmaREvRoB7JZLAaR01YBaiSrBwww8Cup2VkS9m5sM
+KXAc75H6sTLl7Z1zl0HQIe62lHkR55HtpAgkM5r8jM17C8epuQuplrP5paje7jW+
+UoDkwb3lhedMsWsNs6xUqSbDyxWUrJyIG4deYeVkd2fV7lrmoGNLnPykrlECdeza
+Jk/4dq7mbCmhVviBEbbfxzEqRYM4JPSx5Q/QLsn90utudz9SfRg/fSHmAva18ruH
+k0e5qEWqEvACKquqmXzaR2i8T2BTdKXt7n2egKWL6FNGjFD2ATsH2B7autVq8VJS
+lbop0AJhXPjr80KN8tW0LNmUDkoiorEZXtKx5B09Y+9nVK4AThRDO2emup+QaWx1
+AUQq8+BgxMlPKnl/YNYU9hckuHIw15iKtToaVOPJcT0PwylOGXLw9himBc6WDuT0
+AAlI4zEXf+LJJPlfEJtZ
+=oI/p
+-----END PGP SIGNATURE-----