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
| Function | Result | Checked error |
|---|---|---|
secure_random_bytes(int_64 count) | bytes | CryptoError |
sha256(bytes data) | raw 32-byte digest | CryptoError |
hmac_sha256(bytes key, bytes data) | raw 32-byte authenticator | CryptoError |
constant_time_equal(bytes left, bytes right) | bool | none |
base64_encode(bytes data) | string | none |
base64_decode(string text) | bytes | EncodingError |
base64url_encode(bytes data) | string | none |
base64url_decode(string text) | bytes | EncodingError |
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.
