MetamorphicCrypto (metamorphic_crypto v0.8.0)

Copy Markdown View Source

NaCl-compatible encryption for Elixir with post-quantum support.

MetamorphicCrypto provides NaCl-compatible cryptographic primitives powered by Rust NIFs with precompiled binaries — no Rust toolchain, no C compiler, no system packages required.

Quick Start

# Generate keys
key = MetamorphicCrypto.generate_key()
{public_key, private_key} = MetamorphicCrypto.generate_keypair()

# Symmetric encryption (XSalsa20-Poly1305)
{:ok, ciphertext} = MetamorphicCrypto.encrypt("hello", key)
{:ok, "hello"} = MetamorphicCrypto.decrypt(ciphertext, key)

# Public-key encryption (X25519 sealed box)
{:ok, sealed} = MetamorphicCrypto.seal("secret", public_key)
{:ok, "secret"} = MetamorphicCrypto.unseal(sealed, public_key, private_key)

Modules

For full control, use the specialized modules directly:

Wire Format

All functions accept and return base64-encoded strings. Ciphertext produced by this library is byte-compatible with libsodium/NaCl and the metamorphic-crypto WASM module used in browser clients.

Summary

Functions

Decrypt a ciphertext back to a UTF-8 string.

Re-derive the base64 public key from a base64 hybrid signing secret key.

Encrypt a UTF-8 string with a symmetric key.

Generate a random 32-byte symmetric key (base64-encoded).

Generate an X25519 keypair.

Generate a hybrid ML-DSA + Ed25519 signing keypair (Cat-3 / ML-DSA-65 default).

Encrypt a UTF-8 string to a recipient's public key (anonymous sealed box).

SHA3-512 of base64-encoded data (base64 digest out).

Domain-separated SHA3-512 — recommended for key fingerprints, safety numbers, and key-transparency-log entries.

Sign a message binary under a UTF-8 context with a base64 hybrid secret_key.

Decrypt a sealed box using the recipient's keypair.

Verify a composite signature over message / context against public_key.

Functions

decrypt(ciphertext, key)

@spec decrypt(ciphertext :: String.t(), key :: String.t()) ::
  {:ok, String.t()} | {:error, String.t()}

Decrypt a ciphertext back to a UTF-8 string.

Example

{:ok, "hello, world!"} = MetamorphicCrypto.decrypt(ciphertext, key)

derive_public_key(secret_key_b64)

@spec derive_public_key(secret_key_b64 :: String.t()) ::
  {:ok, String.t()} | {:error, String.t()}

Re-derive the base64 public key from a base64 hybrid signing secret key.

Deterministic — reproduces the keypair's public_key exactly, which is what makes recovery-based key regeneration byte-identical. See MetamorphicCrypto.Sign.

encrypt(plaintext, key)

@spec encrypt(plaintext :: String.t(), key :: String.t()) ::
  {:ok, String.t()} | {:error, String.t()}

Encrypt a UTF-8 string with a symmetric key.

Uses XSalsa20-Poly1305 (NaCl secretbox). Returns base64-encoded ciphertext.

Example

key = MetamorphicCrypto.generate_key()
{:ok, ciphertext} = MetamorphicCrypto.encrypt("hello, world!", key)

generate_key()

@spec generate_key() :: String.t()

Generate a random 32-byte symmetric key (base64-encoded).

Example

key = MetamorphicCrypto.generate_key()

generate_keypair()

@spec generate_keypair() :: {String.t(), String.t()}

Generate an X25519 keypair.

Returns {public_key, private_key} as base64-encoded strings.

Example

{public_key, private_key} = MetamorphicCrypto.generate_keypair()

generate_signing_keypair(level \\ :cat3)

@spec generate_signing_keypair(MetamorphicCrypto.Sign.level()) ::
  MetamorphicCrypto.Sign.keypair()

Generate a hybrid ML-DSA + Ed25519 signing keypair (Cat-3 / ML-DSA-65 default).

Returns %{public_key: base64, secret_key: base64}. See MetamorphicCrypto.Sign for security levels, the wire format, and the recovery hook.

Example

kp = MetamorphicCrypto.generate_signing_keypair()
kp = MetamorphicCrypto.generate_signing_keypair(:cat5)

seal(plaintext, public_key)

@spec seal(plaintext :: String.t(), public_key :: String.t()) ::
  {:ok, String.t()} | {:error, String.t()}

Encrypt a UTF-8 string to a recipient's public key (anonymous sealed box).

The sender remains anonymous — only the recipient can decrypt.

Example

{public_key, _private_key} = MetamorphicCrypto.generate_keypair()
{:ok, sealed} = MetamorphicCrypto.seal("secret message", public_key)

sha3_512(data_b64)

@spec sha3_512(data_b64 :: String.t()) :: {:ok, String.t()} | {:error, String.t()}

SHA3-512 of base64-encoded data (base64 digest out).

General-purpose digest for public data. For key fingerprints, safety numbers, and key-transparency-log entries, prefer sha3_512_with_context/2. See MetamorphicCrypto.Hash for the full menu and the "public data only" security note.

Example

{:ok, digest} = MetamorphicCrypto.sha3_512(Base.encode64("abc"))

sha3_512_with_context(context, data_b64)

@spec sha3_512_with_context(context :: String.t(), data_b64 :: String.t()) ::
  {:ok, String.t()} | {:error, String.t()}

Domain-separated SHA3-512 — recommended for key fingerprints, safety numbers, and key-transparency-log entries.

context is a versioned UTF-8 label (e.g. "mosslet/key-fingerprint/v1"); data_b64 is the base64-encoded payload. See MetamorphicCrypto.Hash.

Example

{:ok, fp} =
  MetamorphicCrypto.sha3_512_with_context(
    "mosslet/key-fingerprint/v1",
    Base.encode64("public key bytes")
  )

sign(message, context, secret_key_b64)

@spec sign(message :: binary(), context :: String.t(), secret_key_b64 :: String.t()) ::
  {:ok, String.t()} | {:error, String.t()}

Sign a message binary under a UTF-8 context with a base64 hybrid secret_key.

Returns {:ok, signature_b64}. ML-DSA signing is randomized, so signatures are non-reproducible (but verify). See MetamorphicCrypto.Sign.

Example

{:ok, sig} =
  MetamorphicCrypto.sign("log entry", "metamorphic/sign/v1", kp.secret_key)

unseal(ciphertext, public_key, private_key)

@spec unseal(
  ciphertext :: String.t(),
  public_key :: String.t(),
  private_key :: String.t()
) ::
  {:ok, String.t()} | {:error, String.t()}

Decrypt a sealed box using the recipient's keypair.

Example

{public_key, private_key} = MetamorphicCrypto.generate_keypair()
{:ok, sealed} = MetamorphicCrypto.seal("secret", public_key)
{:ok, "secret"} = MetamorphicCrypto.unseal(sealed, public_key, private_key)

verify(message, context, signature_b64, public_key_b64)

@spec verify(
  message :: binary(),
  context :: String.t(),
  signature_b64 :: String.t(),
  public_key_b64 :: String.t()
) :: boolean()

Verify a composite signature over message / context against public_key.

Returns true only if both the Ed25519 and ML-DSA components verify (strict AND); false otherwise (including malformed input). See MetamorphicCrypto.Sign.

Example

true = MetamorphicCrypto.verify("log entry", "metamorphic/sign/v1", sig, kp.public_key)