Documentation channel: current development main at compiler checkpoint 1d7e15e. The latest tagged release is v0.0.3; pages identify APIs that are not yet released.

Cryptography & binary encoding

Version: This page documents current-main / unreleased at compiler checkpoint 1d7e15e. These APIs are post-release work and are not part of the stable v0.0.3 API.

Strut provides a deliberately small, binary-first cryptographic surface. Include <crypto> for secure random bytes, SHA-256, HMAC-SHA-256, and constant-time comparison. Include <encoding> for dependency-free Base64 and Base64url conversion.

Bytes are the boundary

bytes is an owned binary value. bytes() creates an empty value, bytes(size) creates a zero-filled value, and a contextually typed literal accepts byte values from 0 through 255. Assignment deep-copies, comparison compares contents, and values support mutable integer indexing, length(), empty(), and the half-open copying operation slice(begin, end).

Indexes and lengths use int_64 and bounds are checked. bytes.from_string(text) and value.to_string() copy string code units losslessly; they do not validate or imply UTF-8. There is no implicit conversion between string and bytes.

Exact API

secure_random_bytes(int_64 count) -> bytes : CryptoError
sha256(bytes data) -> bytes : CryptoError
hmac_sha256(bytes key, bytes data) -> bytes : CryptoError
constant_time_equal(bytes left, bytes right) -> bool

base64_encode(bytes data) -> string
base64_decode(string text) -> bytes : EncodingError
base64url_encode(bytes data) -> string
base64url_decode(string text) -> bytes : EncodingError
FunctionResultChecked error
secure_random_bytes(int_64 count)bytesCryptoError
sha256(bytes data)raw 32-byte digestCryptoError
hmac_sha256(bytes key, bytes data)raw 32-byte authenticatorCryptoError
constant_time_equal(bytes left, bytes right)boolnone
base64_encode(bytes data)stringnone
base64_decode(string text)bytesEncodingError
base64url_encode(bytes data)stringnone
base64url_decode(string text)bytesEncodingError

Digests and authenticators are returned as raw bytes, not hexadecimal or Base64 text. Encode them explicitly when a textual representation is required.

Generate, authenticate, and encode

include <crypto>;
include <encoding>;
function main() -> int : (CryptoError, EncodingError) {
    bytes message := bytes.from_string("hello");
    bytes key := secure_random_bytes(32);
    bytes digest := sha256(message);
    bytes authenticator := hmac_sha256(key, message);
    string token := base64url_encode(authenticator);
    bytes decoded := base64url_decode(token);
    if (digest.length() != 32 || !constant_time_equal(authenticator, decoded)) {
        return 1;
    }
    return 0;
}

The example is compile/run-capable. It emits no output; the HMAC key is freshly generated on each run and the exit status verifies digest length and round-trip equality.

Checked errors

CryptoError reports an invalid random-byte count, random-byte allocation failure, or native OpenSSL operation failure. EncodingError reports malformed or noncanonical encoded text. The encoding functions cannot fail for a valid bytes input, and constant_time_equal has no checked error.

Secure randomness

secure_random_bytes uses OpenSSL RAND_bytes and has no non-cryptographic fallback. A count of zero returns empty bytes. A negative, unrepresentable, allocation-failing, or native-generator request raises CryptoError.

Canonical Base64

base64_encode emits canonical RFC 4648 Base64 with the standard +// alphabet and required = padding. base64url_encode uses -/_ and emits no padding.

Decoding is strict rather than forgiving. The decoders reject whitespace, characters from the other alphabet, malformed lengths, misplaced or noncanonical padding, and non-zero unused trailing bits. Standard Base64 must have canonical padding; Base64url must be unpadded. Invalid input raises EncodingError instead of being partially decoded.

Comparison and limits

constant_time_equal uses OpenSSL CRYPTO_memcmp for equal-length values. Length is treated as public: unequal lengths return false immediately. This avoids content-dependent early exit for equal-length secret values, but does not make surrounding application logic constant-time.

SHA-256 is a digest, not password hashing. HMAC authenticates only when keys are generated, stored, rotated, and scoped correctly. The public API does not provide SHA-1, password hashing, key management, certificate management, or general encryption. SHA-1 used internally for the RFC 6455 WebSocket handshake is not exposed as a callable public API.

Native dependency boundary

<encoding> is implemented by the generated runtime and adds no native library. <crypto> requires OpenSSL 3.0 or newer and links libcrypto, but not libssl. OpenSSL failures are translated to checked CryptoError values without exposing its native error queue. TLS is a separate runtime component.