Packages

macula

3.8.0
7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula native macula_crypto_nif src lib.rs
Raw

native/macula_crypto_nif/src/lib.rs

//! Macula Cryptographic NIF operations.
//!
//! This module provides high-performance implementations of:
//! - Ed25519 key generation, signing, and verification
//! - BLAKE3 hashing (primary algorithm for content-addressed storage)
//! - SHA-256 hashing
//! - Base64 encoding/decoding (URL-safe)
//! - Constant-time secure comparison
//!
//! These NIFs provide the cryptographic foundation for UCAN tokens,
//! DID operations, and content-addressed storage in the Macula mesh.
use ed25519_dalek::{Signature, SigningKey, VerifyingKey, Signer, Verifier};
use rand::rngs::OsRng;
use rustler::{Atom, Binary, Env, NifResult, OwnedBinary};
use sha2::{Digest, Sha256};
mod atoms {
rustler::atoms! {
ok,
error,
invalid_signature,
invalid_public_key,
invalid_private_key,
invalid_key_length,
}
}
/// Generate a new Ed25519 keypair.
///
/// Returns:
/// - `{ok, {PublicKey, PrivateKey}}` where both are 32-byte binaries
/// Note: PrivateKey is the seed (32 bytes), not the full secret key (64 bytes)
#[rustler::nif]
fn nif_generate_keypair<'a>(env: Env<'a>) -> NifResult<(Atom, (Binary<'a>, Binary<'a>))> {
let mut csprng = OsRng;
let signing_key = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
// Get the 32-byte seed (private key)
let private_key_bytes = signing_key.to_bytes();
let public_key_bytes = verifying_key.to_bytes();
// Create output binaries
let mut pub_out = OwnedBinary::new(32).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary for public key",
)))?;
pub_out.as_mut_slice().copy_from_slice(&public_key_bytes);
let mut priv_out = OwnedBinary::new(32).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary for private key",
)))?;
priv_out.as_mut_slice().copy_from_slice(&private_key_bytes);
Ok((atoms::ok(), (pub_out.release(env), priv_out.release(env))))
}
/// Sign a message with an Ed25519 private key.
///
/// Arguments:
/// - message: The message to sign (binary)
/// - private_key: The 32-byte Ed25519 private key seed (binary)
///
/// Returns:
/// - `{ok, Signature}` where Signature is a 64-byte binary
/// - `{error, invalid_private_key}` if the key is invalid
#[rustler::nif]
fn nif_sign<'a>(env: Env<'a>, message: Binary, private_key: Binary) -> NifResult<(Atom, Binary<'a>)> {
// Validate key length
if private_key.len() != 32 {
let empty = OwnedBinary::new(0).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
return Ok((atoms::invalid_private_key(), empty.release(env)));
}
// Parse private key
let key_bytes: [u8; 32] = match private_key.as_slice().try_into() {
Ok(bytes) => bytes,
Err(_) => {
let empty = OwnedBinary::new(0).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
return Ok((atoms::invalid_private_key(), empty.release(env)));
}
};
let signing_key = SigningKey::from_bytes(&key_bytes);
let signature = signing_key.sign(message.as_slice());
// Create output binary
let mut sig_out = OwnedBinary::new(64).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary for signature",
)))?;
sig_out.as_mut_slice().copy_from_slice(&signature.to_bytes());
Ok((atoms::ok(), sig_out.release(env)))
}
/// Verify an Ed25519 signature.
///
/// Arguments:
/// - message: The message that was signed (binary)
/// - signature: The 64-byte Ed25519 signature (binary)
/// - public_key: The 32-byte Ed25519 public key (binary)
///
/// Returns:
/// - `true` if signature is valid
/// - `false` if signature is invalid
#[rustler::nif]
fn nif_verify(message: Binary, signature: Binary, public_key: Binary) -> bool {
// Validate input lengths
if signature.len() != 64 {
return false;
}
if public_key.len() != 32 {
return false;
}
// Parse public key
let pk_bytes: [u8; 32] = match public_key.as_slice().try_into() {
Ok(bytes) => bytes,
Err(_) => return false,
};
let verifying_key = match VerifyingKey::from_bytes(&pk_bytes) {
Ok(key) => key,
Err(_) => return false,
};
// Parse signature
let sig_bytes: [u8; 64] = match signature.as_slice().try_into() {
Ok(bytes) => bytes,
Err(_) => return false,
};
let sig = Signature::from_bytes(&sig_bytes);
// Verify signature
verifying_key.verify(message.as_slice(), &sig).is_ok()
}
/// Compute SHA-256 hash of data.
///
/// Arguments:
/// - data: The data to hash (binary)
///
/// Returns:
/// - 32-byte SHA-256 hash (binary)
#[rustler::nif]
fn nif_sha256<'a>(env: Env<'a>, data: Binary) -> NifResult<Binary<'a>> {
let mut hasher = Sha256::new();
hasher.update(data.as_slice());
let result = hasher.finalize();
let mut output = OwnedBinary::new(32).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(&result);
Ok(output.release(env))
}
/// Compute BLAKE3 hash of data.
///
/// BLAKE3 is a cryptographic hash function that is:
/// - Much faster than SHA-256 (especially on modern CPUs)
/// - Parallelizable for large inputs
/// - Secure (based on BLAKE2 and ChaCha)
///
/// Arguments:
/// - data: The data to hash (binary)
///
/// Returns:
/// - 32-byte BLAKE3 hash (binary)
#[rustler::nif]
fn nif_blake3<'a>(env: Env<'a>, data: Binary) -> NifResult<Binary<'a>> {
let hash = blake3::hash(data.as_slice());
let mut output = OwnedBinary::new(32).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(hash.as_bytes());
Ok(output.release(env))
}
/// Compute BLAKE3 hash of multiple chunks (streaming).
///
/// This is optimized for content-addressed storage where data
/// is processed in chunks. The hasher maintains internal state
/// across all chunks.
///
/// Arguments:
/// - chunks: List of binaries to hash
///
/// Returns:
/// - 32-byte BLAKE3 hash (binary)
#[rustler::nif]
fn nif_blake3_streaming<'a>(env: Env<'a>, chunks: Vec<Binary>) -> NifResult<Binary<'a>> {
let mut hasher = blake3::Hasher::new();
for chunk in chunks {
hasher.update(chunk.as_slice());
}
let hash = hasher.finalize();
let mut output = OwnedBinary::new(32).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(hash.as_bytes());
Ok(output.release(env))
}
/// Verify that data matches a BLAKE3 hash.
///
/// Arguments:
/// - data: The data to verify (binary)
/// - expected_hash: The expected 32-byte BLAKE3 hash (binary)
///
/// Returns:
/// - `true` if the hash matches
/// - `false` if the hash doesn't match or expected_hash is wrong length
#[rustler::nif]
fn nif_blake3_verify(data: Binary, expected_hash: Binary) -> bool {
if expected_hash.len() != 32 {
return false;
}
let computed = blake3::hash(data.as_slice());
computed.as_bytes() == expected_hash.as_slice()
}
/// Compute BLAKE3 hash and encode as hex string.
///
/// Optimized for debugging and logging - combines hash + hex encode
/// in a single NIF call.
///
/// Arguments:
/// - data: The data to hash (binary)
///
/// Returns:
/// - Hex-encoded BLAKE3 hash (64-character binary string)
#[rustler::nif]
fn nif_blake3_hex<'a>(env: Env<'a>, data: Binary) -> NifResult<Binary<'a>> {
let hash = blake3::hash(data.as_slice());
let hex = hash.to_hex();
let mut output = OwnedBinary::new(64).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(hex.as_bytes());
Ok(output.release(env))
}
/// Compute SHA-256 hash and encode as URL-safe base64 (no padding).
///
/// This is optimized for token CID generation - combines hash + encode
/// in a single NIF call to avoid intermediate allocations.
///
/// Arguments:
/// - data: The data to hash (binary)
///
/// Returns:
/// - URL-safe base64-encoded SHA-256 hash (binary string)
#[rustler::nif]
fn nif_sha256_base64<'a>(env: Env<'a>, data: Binary) -> NifResult<Binary<'a>> {
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
// Hash the data
let mut hasher = Sha256::new();
hasher.update(data.as_slice());
let hash = hasher.finalize();
// Encode as URL-safe base64 (no padding)
let encoded = URL_SAFE_NO_PAD.encode(hash);
// Create output binary
let mut output = OwnedBinary::new(encoded.len()).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(encoded.as_bytes());
Ok(output.release(env))
}
/// Base64 URL-safe encode without padding.
///
/// Arguments:
/// - data: The data to encode (binary)
///
/// Returns:
/// - URL-safe base64-encoded data (binary string)
#[rustler::nif]
fn nif_base64_encode<'a>(env: Env<'a>, data: Binary) -> NifResult<Binary<'a>> {
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
let encoded = URL_SAFE_NO_PAD.encode(data.as_slice());
let mut output = OwnedBinary::new(encoded.len()).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(encoded.as_bytes());
Ok(output.release(env))
}
/// Base64 URL-safe decode.
///
/// Arguments:
/// - data: The base64-encoded data (binary string)
///
/// Returns:
/// - `{ok, Binary}` on success
/// - `{error, invalid_base64}` on failure
#[rustler::nif]
fn nif_base64_decode<'a>(env: Env<'a>, data: Binary) -> NifResult<(Atom, Binary<'a>)> {
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
match URL_SAFE_NO_PAD.decode(data.as_slice()) {
Ok(decoded) => {
let mut output =
OwnedBinary::new(decoded.len()).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
output.as_mut_slice().copy_from_slice(&decoded);
Ok((atoms::ok(), output.release(env)))
}
Err(_) => {
// Return empty binary for error case
let output = OwnedBinary::new(0).ok_or(rustler::Error::Term(Box::new(
"Failed to allocate binary",
)))?;
Ok((atoms::error(), output.release(env)))
}
}
}
/// Constant-time comparison of two binaries.
///
/// This is important for security - prevents timing attacks when comparing
/// signatures, hashes, or tokens.
///
/// Arguments:
/// - a: First binary
/// - b: Second binary
///
/// Returns:
/// - `true` if equal (constant time)
/// - `false` if not equal (constant time)
#[rustler::nif]
fn nif_secure_compare(a: Binary, b: Binary) -> bool {
if a.len() != b.len() {
return false;
}
// Constant-time comparison
let mut result: u8 = 0;
for (x, y) in a.as_slice().iter().zip(b.as_slice().iter()) {
result |= x ^ y;
}
result == 0
}
rustler::init!("macula_crypto_nif");