1
0
Fork
You've already forked emacs-esodium
0
Libsodium, parenthesis includedTM
  • Rust 54.4%
  • Emacs Lisp 45.4%
  • Makefile 0.2%
coopi e9f575b201
Add missing license file.
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.
2026年07月03日 11:15:49 +04:00
docs Initial commit. 2026年07月03日 11:15:49 +04:00
lisp Initial commit. 2026年07月03日 11:15:49 +04:00
src Initial commit. 2026年07月03日 11:15:49 +04:00
test Initial commit. 2026年07月03日 11:15:49 +04:00
tools Initial commit. 2026年07月03日 11:15:49 +04:00
.gitignore Initial commit. 2026年07月03日 11:15:49 +04:00
Cargo.lock Initial commit. 2026年07月03日 11:15:49 +04:00
Cargo.toml Initial commit. 2026年07月03日 11:15:49 +04:00
LICENSE.org Add missing license file. 2026年07月03日 11:15:49 +04:00
Makefile Initial commit. 2026年07月03日 11:15:49 +04:00
README.org Initial commit. 2026年07月03日 11:15:49 +04:00
rustfmt.toml Initial commit. 2026年07月03日 11:15:49 +04:00

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.