sodium

Comprehensive cryptographic library for Kit using libsodium

Files

FileDescription
.editorconfigEditor formatting configuration
.gitignoreGit ignore rules for build artifacts and dependencies
.tool-versionsasdf tool versions (Zig, Kit)
LICENSEMIT license file
README.mdThis file
c/kit_sodium.cC FFI wrapper
c/kit_sodium.hC header for FFI wrapper
examples/sodium-hmac.kitExample: sodium hmac
examples/sodium-kdf.kitExample: sodium kdf
examples/sodium-key-exchange.kitExample: sodium key exchange
examples/sodium-signatures.kitExample: sodium signatures
examples/sodium-stream-cipher.kitExample: sodium stream cipher
examples/sodium.kitExample: sodium
kit.tomlPackage manifest with metadata and dependencies
src/sodium.kitKit Sodium - Comprehensive Cryptographic Operations Library
tests/sodium.test.kitTests for sodium

Dependencies

No Kit package dependencies.

Native Dependencies

This package uses libsodium through Kit's C FFI support.

Install libsodium before building or running examples:

# macOS
brew install libsodium

# Ubuntu/Debian
sudo apt install libsodium-dev

# Fedora
sudo dnf install libsodium-devel

Installation

kit add gitlab.com/kit-lang/packages/kit-sodium.git

Usage

import Kit.Sodium as sodium

main = fn =>
  # Initialize libsodium once before using crypto functions
  match sodium.init
    | Err e -> println "Failed to initialize sodium: ${e}"
    | Ok _ ->
      message = "Hello, Kit!"

      # Hashing
      sha256-hash = sodium.sha256 message
      sha512-hash = sodium.sha512 message
      blake2b-hash = sodium.hash message

      println "SHA-256: ${sha256-hash}"
      println "SHA-512: ${sha512-hash}"
      println "BLAKE2b: ${blake2b-hash}"

      # Configurable BLAKE2b
      match sodium.blake2b "payload" "secret-key" 32
        | Some digest -> println "Keyed BLAKE2b: ${digest}"
        | None -> println "BLAKE2b hashing failed"

      # Password hashing with Argon2id
      password = "correct horse battery staple"
      match sodium.hash-password password
        | Some password-hash ->
          is-valid? = sodium.verify-password? password-hash password
          println "Password valid: ${is-valid?}"
        | None -> println "Password hashing failed"

      # HMAC for API/webhook signing
      secret = "shared-secret"
      signature = sodium.hmac-sha256-hex message secret
      expected = sodium.hmac-sha256-hex message secret
      is-authentic? = sodium.secure-compare? signature expected
      println "Message authentic: ${is-authentic?}"

main

Development

Running Examples

Run examples with the interpreter:

kit run examples/sodium.kit
kit run examples/sodium-hmac.kit
kit run examples/sodium-kdf.kit
kit run examples/sodium-key-exchange.kit
kit run examples/sodium-signatures.kit
kit run examples/sodium-stream-cipher.kit

Compile an example to a native binary:

kit build examples/sodium.kit && ./sodium

Running Tests

Run the test suite:

kit test

Run the test suite with coverage:

kit test --coverage

Running kit dev

Run the standard development workflow (format, check, examples, test):

kit dev

This will:

  1. Build or refresh the native FFI library when needed
  2. Format and check source files in src/
  3. Type check examples in examples/
  4. Run tests in tests/ with coverage

Running Parity Checks

Check interpreter and compiled-output parity for all examples:

kit parity --failures-only

This package keeps example output deterministic for parity checks. Examples avoid printing random bytes, generated keys, and signatures where those values naturally change between runs.

Generating Documentation

Generate API documentation from doc comments:

kit doc

Note: Kit sources with doc comments (##) will generate HTML documents in docs/*.html.

Cleaning Build Artifacts

Remove generated files, caches, parity reports, coverage files, and native build artifacts:

kit task clean

Note: Defined in kit.toml.

Local Installation

To install this package locally for development:

kit install

This installs the package to ~/.kit/packages/@kit/sodium/, making it available for import as Kit.Sodium in other projects.

Developer Notes

  • src/sodium.kit exposes the high-level Kit API.
  • c/kit_sodium.c and c/kit_sodium.h contain the C wrapper used by the FFI bindings.
  • The package manifest declares kind = "ffi-c" and requires the ffi capability.
  • Use sodium.secure-compare? for comparing hashes, HMACs, and signatures. Regular string comparison can leak timing information.
  • Use sodium.hash-password and sodium.verify-password? for passwords instead of general-purpose hashes.
  • Use sodium.hmac-sha256-hex or sodium.hmac-sha512-hex for webhook/API authentication.
  • Use sodium.blake2b when you need keyed hashing or a configurable output length.
  • Call sodium.init once at program startup before using other crypto functions.

License

This package is released under the MIT License - see LICENSE for details.

libsodium is distributed separately by the libsodium project; see its upstream license for details.

Exported Functions & Types

init

Note: This module uses Kit's standard Result type (Ok a | Err String).

Initializes the crypto library.

This function must be called once at program start before using any other cryptographic functions. It initializes the libsodium library and ensures that all internal state is properly set up.

Returns: Result Int String: Ok on success, Err with message on failure

() -> Result Int String

match init()
  | Ok _ -> print "Sodium initialized"
  | Err msg -> print "Failed: ${msg}"

random-u32

Generates a cryptographically secure random unsigned 32-bit integer.

Uses libsodium's secure random number generator, which is suitable for cryptographic purposes (key generation, nonces, etc.).

Returns: Int: A random integer in the range [0, 2^32)

Security: Uses unpredictable randomness suitable for security-critical applications

() -> Int

rand = random-u32()
print "Random number: ${rand}"

random-int

Generates a cryptographically secure random integer in a specified range.

Returns a uniformly distributed random integer in the range [0, n). Uses rejection sampling to avoid modulo bias, ensuring true uniform distribution.

Parameters:

  • n (Int) - Upper bound (exclusive). Must be positive.

Returns: Int: A random integer in [0, n), or 0 if n <= 0

Security: Uses rejection sampling to prevent modulo bias in range selection

Int -> Int

dice-roll = random-int 6  # Returns 0-5
print "You rolled: ${dice-roll + 1}"

random-bool?

Generates a cryptographically secure random boolean value.

Returns true or false with equal probability (50% each).

Returns: Bool: A random boolean value

() -> Bool

if random-bool?() then
  print "Heads"
else
  print "Tails"

random-bytes

Generates cryptographically secure random bytes as a hex string.

Uses libsodium's secure random number generator to produce the requested number of random bytes, returned as a hexadecimal string. This is used in PASETO for generating the 32-byte nonce for every token.

Parameters:

  • size (PositiveInt) - Number of random bytes to generate

Returns: Option String: hex-encoded random bytes (2 * size characters), or None on failure

Security: Uses unpredictable randomness suitable for nonces, keys, and IVs

PositiveInt -> Option String

# Generate a 32-byte random nonce for PASETO
nonce = random-bytes 32

sha256

Computes the SHA-256 hash of a message.

SHA-256 is a cryptographic hash function that produces a 256-bit (32-byte) hash value. It is part of the SHA-2 family and widely used for integrity verification and digital signatures.

Parameters:

  • message (String) - The message to hash

Returns: String: The hash as a 64-character hexadecimal string

Security: SHA-256 is collision-resistant and suitable for most cryptographic purposes

String -> String

hash = sha256 "Hello, World!"
print "SHA-256: ${hash}"

sha512

Computes the SHA-512 hash of a message.

SHA-512 is a cryptographic hash function that produces a 512-bit (64-byte) hash value. It provides a higher security margin than SHA-256 and is faster on 64-bit processors.

Parameters:

  • message (String) - The message to hash

Returns: String: The hash as a 128-character hexadecimal string

Security: SHA-512 provides higher security margin than SHA-256

String -> String

hash = sha512 "Hello, World!"
print "SHA-512: ${hash}"

hash

Computes the BLAKE2b hash of a message.

BLAKE2b is a cryptographic hash function that is faster than SHA-256 while providing at least the same level of security. It produces a 512-bit (64-byte) hash value and is optimized for 64-bit platforms.

Parameters:

  • message (String) - The message to hash

Returns: String: The hash as a 128-character hexadecimal string

Security: BLAKE2b is faster than SHA-256 with equivalent or better security Recommended as the default hash function for general use

String -> String

hash = hash "Hello, World!"
print "BLAKE2b: ${hash}"

blake2b

Computes a keyed BLAKE2b hash with configurable output length.

BLAKE2b supports an optional key for keyed hashing (MAC) and a configurable output length from 16 to 64 bytes. This is the core primitive used in PASETO for key derivation and pre-authentication encoding.

Parameters:

  • message (String) - The raw input data to hash
  • key (Blake2bKey) - The key for keyed hashing (use "" for unkeyed, max 64 bytes)
  • out-len (Blake2bOutputLen) - Desired output length in bytes (16-64)

Returns: Option String: hex-encoded hash output, or None on failure

Security: With a key, this provides a MAC (Message Authentication Code) Without a key (empty string), this is equivalent to unkeyed BLAKE2b

String -> Blake2bKey -> Blake2bOutputLen -> Option String

# Unkeyed BLAKE2b with 32-byte output
hash = blake2b "hello" "" 32
# Keyed BLAKE2b for PASETO key derivation
derived = blake2b payload secret-key 32

hmac-sha256

Computes HMAC-SHA256 authentication code.

HMAC (Hash-based Message Authentication Code) provides message authentication and integrity verification using a secret key. HMAC-SHA256 uses SHA-256 as the underlying hash function.

Parameters:

  • message (String) - The message to authenticate
  • key (String) - The secret key for authentication

Returns: String: 32 bytes of raw binary HMAC output

Security: The key should be at least 32 bytes for optimal security Use secure-compare? to verify HMAC values in constant time

String -> NonEmptyString -> String

mac = hmac-sha256 "message" "secret-key"
# Store mac for later verification

hmac-sha256-hex

Computes HMAC-SHA256 authentication code as hexadecimal.

Same as hmac-sha256 but returns the result as a hexadecimal string for easier storage and transmission.

Parameters:

  • message (String) - The message to authenticate
  • key (String) - The secret key for authentication

Returns: String: 64-character hexadecimal string representing the HMAC

String -> NonEmptyString -> String

mac = hmac-sha256-hex "message" "secret-key"
print "HMAC: ${mac}"

hmac-sha512

Computes HMAC-SHA512 authentication code.

HMAC (Hash-based Message Authentication Code) provides message authentication and integrity verification using a secret key. HMAC-SHA512 uses SHA-512 as the underlying hash function, providing a higher security margin.

Parameters:

  • message (String) - The message to authenticate
  • key (String) - The secret key for authentication

Returns: String: 64 bytes of raw binary HMAC output

Security: The key should be at least 64 bytes for optimal security Use secure-compare? to verify HMAC values in constant time

String -> NonEmptyString -> String

mac = hmac-sha512 "message" "secret-key"
# Store mac for later verification

hmac-sha512-hex

Computes HMAC-SHA512 authentication code as hexadecimal.

Same as hmac-sha512 but returns the result as a hexadecimal string for easier storage and transmission.

Parameters:

  • message (String) - The message to authenticate
  • key (String) - The secret key for authentication

Returns: String: 128-character hexadecimal string representing the HMAC

String -> NonEmptyString -> String

mac = hmac-sha512-hex "message" "secret-key"
print "HMAC: ${mac}"

hmac-sha256-size

Returns the HMAC-SHA256 output size in bytes.

Returns: Int: Always returns 32 (bytes)

() -> Int

hmac-sha512-size

Returns the HMAC-SHA512 output size in bytes.

Returns: Int: Always returns 64 (bytes)

() -> Int

hash-password

Hashes a password using Argon2id. Returns hash suitable for storage.

String -> Option String

verify-password?

Verifies a password against a stored hash.

NonEmptyString -> String -> Bool

key-size

Returns the encryption key size in bytes.

() -> Int

generate-key

Generates a new random encryption key as hex string.

() -> Option String

encrypt

Encrypts a message with a hex-encoded key. Returns base64-encoded ciphertext.

String -> NonEmptyString -> Option String

decrypt

Decrypts a message with a hex-encoded key. Returns plaintext or None.

String -> NonEmptyString -> Option String

secure-compare?

Performs constant-time string comparison for secure hash/key comparison.

String -> String -> Bool

sign-keypair

Generates an Ed25519 signing keypair.

() -> Option SignKeyPair

sign

Signs a message with a hex-encoded secret key. Returns hex-encoded signature.

String -> NonEmptyString -> Option String

verify-signature?

Verifies a signature against a message and public key.

NonEmptyString -> String -> NonEmptyString -> Bool

sign-public-key-size

Returns the public key size in bytes.

() -> Int

sign-secret-key-size

Returns the secret key size in bytes.

() -> Int

signature-size

Returns the signature size in bytes.

() -> Int

kx-keypair

Generates an X25519 key exchange keypair.

() -> Option KxKeyPair

kx-client-session-keys

Computes client session keys from client keypair and server public key.

KxKeyPair -> NonEmptyString -> Option SessionKeys

kx-server-session-keys

Computes server session keys from server keypair and client public key.

KxKeyPair -> NonEmptyString -> Option SessionKeys

kdf-keygen

Generates a master key for key derivation. Returns hex-encoded key.

() -> Option String

kdf-derive

Derives a subkey from a master key with unique ID and 8-char context.

NonEmptyString -> UInt64 -> NonEmptyString -> Option String

kdf-key-size

Returns the KDF master key size in bytes.

() -> Int

stream-xchacha20-xor

Encrypts or decrypts data using the XChaCha20 stream cipher.

XChaCha20 is a stream cipher that XORs the input with a keystream derived from a key and nonce. Since XOR is symmetric, the same function is used for both encryption and decryption. This is the core cipher used in PASETO v2.local.

Note: This is a raw stream cipher without authentication. For authenticated encryption, use the higher-level encrypt/decrypt functions (XChaCha20-Poly1305). In PASETO, authentication is handled separately via the protocol.

Parameters:

  • hex-message (String) - hex-encoded input data (must have even length)
  • hex-nonce (XChaCha20HexNonce) - hex-encoded 24-byte nonce (exactly 48 hex chars)
  • hex-key (XChaCha20HexKey) - hex-encoded 32-byte key (exactly 64 hex chars)

Returns: Option String: hex-encoded output, or None on failure

Security: Never reuse a nonce with the same key This cipher does NOT provide authentication by itself

EvenLength -> XChaCha20HexNonce -> XChaCha20HexKey -> Option String

# Encrypt plaintext
ciphertext = stream-xchacha20-xor hex-plaintext hex-nonce hex-key
# Decrypt (same operation, XOR is symmetric)
plaintext = stream-xchacha20-xor hex-ciphertext hex-nonce hex-key

stream-xchacha20-nonce-size

Returns the XChaCha20 nonce size in bytes (24).

() -> Int

stream-xchacha20-key-size

Returns the XChaCha20 key size in bytes (32).

() -> Int