|
coopi
e9f575b201
The project declares AGPL-3.0-or-later licensing in source and package metadata, but the full license text was not included in the repository. * LICENSE.org: New file. |
||
|---|---|---|
| docs | Initial commit. | |
| lisp | Initial commit. | |
| src | Initial commit. | |
| test | Initial commit. | |
| tools | Initial commit. | |
| .gitignore | Initial commit. | |
| Cargo.lock | Initial commit. | |
| Cargo.toml | Initial commit. | |
| LICENSE.org | Add missing license file. | |
| Makefile | Initial commit. | |
| README.org | Initial commit. | |
| rustfmt.toml | Initial commit. | |
emacs-esodium
esodium
esodium is a dynamic module that exposes the libsodium API to Emacs Lisp.
Function names follow the upstream API. For example, crypto_secretbox_easy
becomes esodium-crypto-secretbox-easy, randombytes_buf becomes
esodium-randombytes-buf, and so on. Existing libsodium documentation and
examples translate directly to Emacs Lisp.
Secret keys, keypairs, precomputed keys, native allocations, and streaming states are represented as opaque esodium objects. Messages, nonces, public keys, ciphertexts, MACs, signatures, hashes, salts, serialized values, and other byte sequences are represented as unibyte strings.
Requirements
esodium supports GNU Emacs 27.1 or later with dynamic module support.
Building the native module requires Rust, Cargo, a C toolchain, and libsodium.
The API coverage tool additionally uses Emacs Tree-sitter together with the Rust
grammar, but this is only needed when regenerating the coverage data.
Building
From a checkout:
make build
During development, the Lisp loader searches Cargo's build directory for the
module. If the shared library is installed elsewhere, set esodium-module-file
before loading the package:
(setq esodium-module-file
"/path/to/libesodium_core.so")
(require 'esodium)
Useful development targets:
make check # Run Rust and Emacs Lisp tests
make compile # Byte-compile the Lisp files
make coverage # Regenerate docs/libsodium-api.eld
make ci # Clean, test, compile, and regenerate coverage
Examples
These examples show how esodium is used in practice.
When viewed in Emacs, the code blocks can be executed with C-c C-c after
building the native module with make build. The results are included inline for
illustration.
An encrypted, signed note
This example encodes a UTF-8 message, encrypts it with Secretbox, signs the ciphertext, verifies the signature, and then decrypts the message. The example uses deterministic key material so that the output remains stable. Applications should normally generate fresh keys.
(defun esodium-readme--filled (length byte)
"Return a unibyte string of LENGTH copies of BYTE."
(let ((value (make-string length byte)))
(set-text-properties 0 (length value) nil value)
(string-as-unibyte value)))
(let* ((message (encode-coding-string "Deploy the new build at 14:30Z." 'utf-8))
(box-key (esodium-crypto-secretbox-key-from-bytes
(esodium-readme--filled esodium-crypto-secretbox-keybytes 1)))
(nonce (esodium-readme--filled esodium-crypto-secretbox-noncebytes 2))
(signing-keypair
(esodium-crypto-sign-seed-keypair
(esodium-readme--filled esodium-crypto-sign-seedbytes 3)))
(public-key (esodium-crypto-sign-public-key signing-keypair))
(ciphertext (esodium-crypto-secretbox-easy box-key nonce message))
(signature (esodium-crypto-sign-detached signing-keypair ciphertext))
(verified (esodium-crypto-sign-verify-detached
public-key signature ciphertext))
(opened (when verified
(esodium-crypto-secretbox-open-easy
box-key nonce ciphertext))))
(pp (list :ciphertext-bytes (length ciphertext)
:ciphertext-prefix (substring (esodium-sodium-bin2hex ciphertext) 0 32)
:signature-bytes (length signature)
:signature-ok verified
:opened (decode-coding-string opened 'utf-8))))
(:ciphertext-bytes 47 :ciphertext-prefix "755e30031bfbaf8d0c55c1cda07255c6" :signature-bytes 64 :signature-ok t :opened "Deploy the new build at 14:30Z.")
Derive a little key hierarchy
The KDF interface returns derived subkeys as unibyte strings while the master key remains an opaque object. This allows the root key to stay in native memory while derived keys can be used where raw bytes are required.
(let* ((master (esodium-crypto-kdf-key-from-bytes
(esodium-readme--filled esodium-crypto-kdf-keybytes 4)))
(context (encode-coding-string "MAILBOX1" 'utf-8))
(message-key (esodium-crypto-kdf-derive-from-key master 32 1 context))
(index-key (esodium-crypto-kdf-derive-from-key master 32 2 context)))
(pp (list :message-key-prefix
(substring (esodium-sodium-bin2hex message-key) 0 32)
:index-key-prefix
(substring (esodium-sodium-bin2hex index-key) 0 32)
:same-key (equal message-key index-key))))
(:message-key-prefix "045fd4d898c208377120bbb2dcf76b87" :index-key-prefix "36f8b8923622e07101c82bcf388390c0" :same-key nil)
Fingerprint a transcript with a streaming XOF
Streaming interfaces operate on opaque state objects. This example absorbs two chunks into a SHAKE256 state, produces a 24-byte fingerprint, and compares it with the output of the corresponding one-shot function.
(let* ((transcript-a (encode-coding-string "GET /inbox\n" 'utf-8))
(transcript-b (encode-coding-string "200 OK\n" 'utf-8))
(state (esodium-crypto-xof-shake256-init)))
(esodium-crypto-xof-shake256-update state transcript-a)
(esodium-crypto-xof-shake256-update state transcript-b)
(let* ((streamed (esodium-crypto-xof-shake256-squeeze state 24))
(oneshot (esodium-crypto-xof-shake256
(concat transcript-a transcript-b) 24)))
(pp (list :fingerprint (esodium-sodium-bin2hex streamed)
:matches-one-shot (equal streamed oneshot)))))
(:fingerprint "866a390eeca599c3be4a3e5ab17f5cc6e1df00cbf1e38198" :matches-one-shot t)
Sign a release manifest
This example hashes a manifest, signs the digest, and verifies the signature using the public key extracted from the opaque keypair.
(let* ((seed (esodium-sodium-hex2bin
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"))
(keypair (esodium-crypto-sign-seed-keypair seed))
(public-key (esodium-crypto-sign-public-key keypair))
(manifest (encode-coding-string
"esodium 0.1.0\nartifact: libesodium_core.so\n" 'utf-8))
(digest (esodium-crypto-hash-sha256 manifest))
(signature (esodium-crypto-sign-detached keypair digest)))
(pp (list :manifest-sha256 (esodium-sodium-bin2hex digest)
:public-key-prefix (substring (esodium-sodium-bin2hex public-key) 0 32)
:signature-valid
(esodium-crypto-sign-verify-detached public-key signature digest))))
(:manifest-sha256 "a3adc52279f8b0b59b0de3895c3782bf5f81145ce2999474139843b008068da6" :public-key-prefix "d75a980182b10ab7d54bfed3c964073a" :signature-valid t)
API coverage data
The coverage tool compares the public Emacs Lisp API against the symbols
exported by libsodium-sys-stable. Function names are matched directly: an
upstream symbol such as crypto_foo_bar corresponds to esodium-crypto-foo-bar.
To regenerate the coverage data:
make coverage
The generated output is written to a lisp data file.
Missing entries are expected only for interfaces that are intentionally not exposed, such as the legacy NaCl-style padded APIs, which libsodium discourages for new applications.