Lean 4 bindings for libsodium, a modern, portable, easy-to-use crypto library.
⚠️ Warning: this is work in progress, it is still incomplete and itmaywill contain errors
Parts of this repository were created with assistance from AI-powered coding tools, specifically Claude by Anthropic. Not all generated code may have been reviewed. Generated code may have been adapted by the author. Design choices, architectural decisions, and final validation were performed independently by the author.
- Random Number Generation - Cryptographically secure random bytes
- Hashing - BLAKE2b, SHA-256, SHA-512
- Streaming Hash - Incremental BLAKE2b for large data
- Short-Input Hash - SipHash-2-4 for hash tables
- Secret-key Encryption - XSalsa20-Poly1305 (secretbox)
- AEAD Encryption - XChaCha20-Poly1305-IETF with associated data
- Secret Stream - Chunked authenticated encryption for files/streams
- Public-key Encryption - X25519-XSalsa20-Poly1305 (box)
- Sealed Boxes - Anonymous public-key encryption
- Digital Signatures - Ed25519
- Message Authentication - HMAC-SHA512-256
- Password Hashing - Argon2id
- Key Derivation - Derive multiple keys from a master key
- Key Exchange - X25519 + BLAKE2b for secure channels
- Lean 4
- libsodium development library
- zlog logging library (for examples)
Ubuntu/Debian:
sudo apt install libsodium-devmacOS:
brew install libsodiumFrom source:
wget https://download.libsodium.org/libsodium/releases/LATEST.tar.gz
tar xzf LATEST.tar.gz
cd libsodium-stable
./configure
make && make check
sudo make installUbuntu/Debian:
sudo apt install libzlog-devFrom source:
git clone https://github.com/HardySimpson/zlog.git
cd zlog
make && sudo make installAdd to your lakefile.lean:
require libsodiumLean from git
"https://github.com/yourusername/libsodium-lean.git" @ "main"Always initialize libsodium before using any functions:
import LibsodiumLean
def main : IO Unit := do
let ok ← Sodium.init
unless ok do
IO.println "Failed to initialize libsodium"
return
-- Use libsodium functions...-- Random 32-bit integer
let n ← Sodium.Random.uint32
-- Random integer in [0, 100)
let r ← Sodium.Random.uniform 100
-- Random bytes
let bytes ← Sodium.Random.bytes 32let data := "Hello, World!".toUTF8
-- BLAKE2b (default, recommended)
let hash ← Sodium.Hash.blake2b data
-- SHA-256
let hash256 ← Sodium.Hash.sha256 data
-- SHA-512
let hash512 ← Sodium.Hash.sha512 dataFor encrypting data when both parties share a secret key:
-- Generate key and nonce
let key ← Sodium.SecretBox.Key.generate
let nonce ← Sodium.SecretBox.Nonce.generate
-- Encrypt
let plaintext := "Secret message".toUTF8
let ciphertext ← Sodium.SecretBox.encrypt plaintext nonce key
-- Decrypt
let decrypted ← Sodium.SecretBox.decrypt ciphertext.get! nonce keyFor encrypting data between two parties:
-- Generate key pairs
let alice ← Sodium.Box.KeyPair.generate
let bob ← Sodium.Box.KeyPair.generate
-- Alice encrypts to Bob
let nonce ← Sodium.Box.Nonce.generate
let ciphertext ← Sodium.Box.encrypt message nonce bob.get!.publicKey alice.get!.secretKey
-- Bob decrypts from Alice
let plaintext ← Sodium.Box.decrypt ciphertext.get! nonce alice.get!.publicKey bob.get!.secretKeyWhen the sender doesn't need to authenticate:
let recipient ← Sodium.Box.KeyPair.generate
-- Anonymous encrypt
let sealed ← Sodium.Box.Sealed.encrypt message recipient.get!.publicKey
-- Recipient decrypts
let opened ← Sodium.Box.Sealed.decrypt sealed.get! recipient.get!.publicKey recipient.get!.secretKey-- Generate signing key pair
let keyPair ← Sodium.Sign.KeyPair.generate
-- Sign a message
let signature ← Sodium.Sign.sign message keyPair.get!.secretKey
-- Verify signature
let valid ← Sodium.Sign.verify signature.get! message keyPair.get!.publicKey-- Generate key
let key ← Sodium.Auth.Key.generate
-- Authenticate message
let tag ← Sodium.Auth.authenticate message key
-- Verify
let valid ← Sodium.Auth.verify tag.get! message key-- Hash password for storage
let hash ← Sodium.Password.hashStringForStorage "my password" .interactive
-- Verify password
let valid ← Sodium.Password.verifyStringStorage hash.get! "my password"
-- Derive key from password
let salt ← Sodium.Password.Salt.generate
let key ← Sodium.Password.deriveKeyFromString "my password" salt 32 .interactiveDerive multiple keys from a master key:
let masterKey ← Sodium.KDF.MasterKey.generate
let context := Sodium.KDF.Context.make "myapp"
-- Derive subkeys
let encKey ← Sodium.KDF.deriveSubkey masterKey 0 context 32
let authKey ← Sodium.KDF.deriveSubkey masterKey 1 context 32Establish secure session keys between client and server:
-- Generate key pairs for client and server
let clientKp ← Sodium.KeyExchange.KeyPair.generate
let serverKp ← Sodium.KeyExchange.KeyPair.generate
-- Client derives session keys
let clientKeys ← Sodium.KeyExchange.clientSessionKeys clientKp.get! serverKp.get!.publicKey
-- Server derives session keys
let serverKeys ← Sodium.KeyExchange.serverSessionKeys serverKp.get! clientKp.get!.publicKey
-- Client's tx key == Server's rx key (and vice versa)
-- Use clientKeys.get!.tx for sending, clientKeys.get!.rx for receivingAuthenticated encryption with associated data (authenticated but not encrypted):
let key ← Sodium.AEAD.Key.generate
let nonce ← Sodium.AEAD.Nonce.generate
let message := "Secret message".toUTF8
let associatedData := "metadata".toUTF8 -- Authenticated but not encrypted
-- Encrypt
let ciphertext ← Sodium.AEAD.encrypt message associatedData nonce key
-- Decrypt (fails if AD doesn't match)
let decrypted ← Sodium.AEAD.decrypt ciphertext.get! associatedData nonce key
-- Auto-nonce variant (nonce prepended to ciphertext)
let encrypted ← Sodium.AEAD.encryptWithNonce message associatedData key
let decrypted ← Sodium.AEAD.decryptWithNonce encrypted.get! associatedData keyChunked authenticated encryption for files and streams:
let key ← Sodium.SecretStream.Key.generate
-- Encryption (push)
match ← Sodium.SecretStream.PushState.init key with
| some (pushState, header) =>
-- Encrypt chunks
let chunk1 ← pushState.push "Hello ".toUTF8 .message
let chunk2 ← pushState.push "World".toUTF8 .final -- Mark final chunk
| none => -- handle error
-- Decryption (pull)
match ← Sodium.SecretStream.PullState.init header key with
| some pullState =>
-- Decrypt chunks
match ← pullState.pull ciphertext1 with
| some (plaintext, tag) =>
-- tag is .message, .push, .rekey, or .final
| none => -- decryption failed
| none => -- handle errorHash data incrementally (for large files):
-- Initialize streaming hash
match ← Sodium.StreamingHash.State.init with
| some state =>
-- Update with chunks
let _ ← state.updateString "Hello, "
let _ ← state.updateString "World!"
-- Finalize
let hash ← state.final
| none => -- handle error
-- Or hash multiple chunks at once
let hash ← Sodium.StreamingHash.hashChunks #[chunk1, chunk2, chunk3]Keyed hash for hash tables (prevents HashDoS):
let key ← Sodium.ShortHash.Key.generate
-- Hash to 8 bytes
let hash ← Sodium.ShortHash.hashString "key" key
-- Get as UInt64 for use in hash tables
let hashValue ← Sodium.ShortHash.hashStringToUInt64 "key" key
-- hashValue.get! can be used as a hash table keyFor convenient chaining of operations, monadic interfaces are available:
-- SecretBox monad
Sodium.SecretBox.SecretBoxM.runWith (do
let encrypted ← SecretBoxM.encryptAuto message
let decrypted ← SecretBoxM.decryptAuto encrypted.get!
return decrypted
) key
-- Sign monad
Sodium.Sign.SignerM.runWith (do
let sig1 ← SignerM.sign msg1
let sig2 ← SignerM.sign msg2
return (sig1, sig2)
) secretKey-- Convert to/from hex
let hex ← Sodium.Utils.toHex bytes
let bytes ← Sodium.Utils.fromHex hex.get!
-- Constant-time comparison (for secrets)
let equal ← Sodium.Utils.secureCompare a b
-- Zero memory
let zeroed ← Sodium.Utils.memzero sensitiveDatalake buildThe examples use zlog for structured logging. Run from the project root directory:
lake exe examplesExample output:
INFO [sodium] libsodium version: 1.0.18
INFO [sodium] === Random Number Generation ===
INFO [sodium] === Hashing ===
INFO [sodium] === Secret-key Encryption ===
INFO [sodium] === Public-key Encryption ===
INFO [sodium] === Sealed Box (Anonymous Encryption) ===
INFO [sodium] === Digital Signatures ===
INFO [sodium] === Message Authentication ===
INFO [sodium] === Password Hashing ===
INFO [sodium] === Key Derivation ===
INFO [sodium] === Secret Stream ===
INFO [sodium] === Key Exchange ===
INFO [sodium] === Streaming Hash ===
INFO [sodium] === Short Hash (SipHash) ===
INFO [sodium] === AEAD Encryption ===
INFO [sodium] All examples completed successfully!
- Always initialize libsodium with
Sodium.initbefore using any functions - Use appropriate security levels for password hashing based on your use case
- Never reuse nonces with the same key
- Use constant-time comparison (
secureCompare) when comparing secrets - Zero sensitive data when done using
memzero
Copyright © 2026 Proofinity s.r.l. Licensed under the Apache License, Version 2.0: see LICENSE and NOTICE.