itb-crystal
ITB Crystal Binding
Security notice. ITB is an experimental symmetric cipher construction without prior peer review, independent cryptanalysis, or formal certification. The construction's security properties have not been verified by independent cryptographers or mathematicians.
PRF-grade hash functions are required. No warranty is provided.
No bespoke cryptography. ITB introduces no cryptographic primitive of its own — no custom S-box, permutation, or round function. It is a construction over existing primitives, much as PGP composes standard ciphers rather than defining one. Such constructions are not the object of algorithm-level cryptographic certification: national regimes (NIST CAVP/FIPS in the US, GOST/FSB in Russia, OSCCA's SM-series in China, IC3S in India, SOG-IS/EUCC and national lists in the EU, ASD's ISM in Australia, CRYPTREC in Japan, KCMVP in South Korea) certify primitives and the modules built on them, not compositional schemes. Eligibility for regulated use is therefore inherited from the primitives ITB is configured with, not conferred by ITB itself.
Thin proxy over the libitb shared library's ITB_Triple_* surface (cmd/cshared), written against Crystal's native lib / fun C bindings — no external shard dependencies, no C glue code. The compiled binaries link libitb.so directly, and every hash-name / MAC-name / cipher-name / profile-name is an opaque string passed through to Go for validation — the binding carries no ITB construction logic. The public surface is the ITB::Pipeline class (init / open / rekey / close, Single Message encrypt / decrypt, whole-buffer and incremental stream sessions), the fluent ITB::Opts query-string builder, ITB.register_profile, the registry roster helpers, and the Go runtime knobs.
Prerequisites (Arch Linux)
sudo pacman -S go crystal shards
Generic Linux: any Crystal 1.x toolchain works. shards is only needed for consuming the binding as a shard dependency — the in-repo build, tests, and benches call the crystal compiler directly.
Build
The convenience driver builds libitb.so (only when absent — set ITB_REBUILD_LIBITB=1 to force a Go rebuild) and compiles the eitb CLI binary in one step:
./bindings/crystal/build.sh
Equivalent manual invocation:
go build -trimpath -buildmode=c-shared -o dist/linux-amd64/libitb.so ./cmd/cshared
cd bindings/crystal && crystal build -o bin/eitb eitb/itb_eitb.cr
Hosts without AVX-512+VL: pass --noitbasm to build.sh to opt out of ITB's chain-absorb asm.
Library lookup order
Linking is resolved at compile time through the @[Link(ldflags: ...)] annotation in src/itb/ffi_bridge.cr, which executes src/itb/libitb_flags.sh. Search order:
ITB_LIBITB_PATHenvironment variable (path to the shared library file) — read when the Crystal program is compiled.<repo>/dist/<os>-<arch>/libitb.<ext>resolved by walking up from the binding source directory (in-repo builds).- The OS default loader path (
-litb).
The resolved directory is baked into the produced binary as an RPATH, so executables run without LD_LIBRARY_PATH.
Usage example
require "itb"
# Single Message: sender initializes a session, receiver opens it
# from the exported blob.
sender = ITB::Pipeline.new("singlemsg-triple-mac-v1")
receiver = ITB::Pipeline.new("singlemsg-triple-mac-v1", sender.blob)
wire = sender.encrypt_message("attack at dawn".to_slice)
plain = receiver.decrypt_message(wire) # => "attack at dawn".to_slice
# Streaming AEAD: incremental sessions over a streaming profile.
s = ITB::Pipeline.new("streaming-aead-triple-mac-v1")
r = ITB::Pipeline.new("streaming-aead-triple-mac-v1", s.blob)
enc = s.encrypt_stream
enc.write(chunk1)
enc.write(chunk2)
wire = enc.drain_all # ends the input and drains the remaining wire
enc.free
dec = r.decrypt_stream
dec.write(wire)
dec.end_stream
loop do
chunk, finished = dec.read
process(chunk)
break if finished
end
dec.free
# Opts pass-through (validated by Go; unknown keys are rejected).
opts = ITB::Opts.new
.with_nonce_bits(512)
.with_inner_hash("areion512")
pipe = ITB::Pipeline.new("singlemsg-triple-nomac-v1", opts: opts)
# Master rotation refreshes the exported blob.
pipe.rekey(perm_master, wrap_master)
# Registry roster.
ITB.version # => libitb version string
ITB.hashes # => [ITB::HashInfo(name, width), ...] in canonical order
ITB.profiles # => shipped profile identifiers
ITB::Opts overrides the profile default per call (chunk size, outer cipher, parallax on/off, wrapper on/off, MAC name, palette); every setter returns the same builder for fluent chaining:
opts = ITB::Opts.new.with_chunk_size(65536).with_wrapper(false)
sender = ITB::Pipeline.new("singlemsg-triple-mac-v1", opts: opts)
receiver = ITB::Pipeline.new("singlemsg-triple-mac-v1", sender.blob, opts: opts)
Pipeline#rekey rotates the parallax + wrapper masters mid-session (the eight ITB seeds and MAC key are fixed for the session lifetime by design); the receiver picks up the new masters through a fresh sender.blob handshake:
sender.rekey(Bytes.new(32, 0x11_u8), Bytes.new(32, 0x22_u8))
receiver = ITB::Pipeline.new("singlemsg-triple-mac-v1", sender.blob)
Every fallible call raises ITB::Error, which carries status (the ITB::Status enum), status_code (raw integer), and last_error (the ITB_LastError diagnostic). Garbage collection releases Go-side handles; call Pipeline#free / session #free for deterministic release. A stream session holds a reference to its parent Pipeline, so the parent cannot be collected while the session is reachable.
Memory
Two process-wide knobs constrain Go runtime arena pacing, readable at libitb load time via env vars (ITB_GOMEMLIMIT, ITB_GOGC) and adjustable at any time programmatically. Pass a negative value to query without changing. Long-running or allocation-heavy workloads (benchmarks, bulk encryption) should set both — without a soft cap + aggressive GC the Go scratch heap grows unboundedly under allocation churn:
ITB.set_memory_limit(512_i64 << 20) # 512 MiB soft cap
ITB.set_gc_percent(20) # aggressive GC
Testing
./bindings/crystal/run_tests.sh
Runs crystal spec: version and roster checks (canonical hash order, shipped profiles), Single Message and incremental-stream round trips (including pathological 17-byte feed / 23-byte drain batches), a large-plaintext round trip past 1 MiB, error mapping (unknown profile, unknown opts key, tampered wire, closed pipeline), rekey, custom-profile registration, opts encoding, and the stream-session parent-pin.
Benchmarking
./bindings/crystal/run_bench.sh
Compiles bench/bench.cr with --release and measures Single Message encrypt and incremental Streaming encrypt throughput at 1 MiB / 16 MiB / 64 MiB under the canonical fleet configuration (Areion-SoEM-512, 1024-bit key, 512-bit nonce, parallax and wrapper off, No MAC profiles, 5 s wall-clock per case; see BENCH.md). Shape overrides via ITB_INNER_HASH, ITB_KEY_BITS, ITB_NONCE_BITS, ITB_WITH_PARALLAX, ITB_WITH_WRAPPER, ITB_PROFILE, ITB_BENCH_MIN_SEC.
eitb utility
./bindings/crystal/eitb/eitb version
./bindings/crystal/eitb/eitb hashes
./bindings/crystal/eitb/eitb profiles
./bindings/crystal/eitb/eitb encrypt <profile> <in-file> <out-file>
./bindings/crystal/eitb/eitb decrypt <profile> <blob-hex> <in-file> <out-file>
encrypt prints the session blob to stderr as hex; feed that hex back to decrypt on the receiving side.
Limitations
- Compile-time linking. Library resolution happens when the Crystal program is compiled, not at process start — moving
libitb.soafter compilation requires either the baked RPATH to stay valid orLD_LIBRARY_PATHat run time.ITB_LIBITB_PATHset at compile time overrides the search. - Blocking FFI calls. Every binding call blocks the calling thread until libitb returns; the calls do not integrate with Crystal's fiber scheduler. Long encrypt / decrypt operations should not share a thread with latency-sensitive fibers.
- Streaming-decrypt caveat. Chunked Streaming AEAD verifies per chunk, so plaintext of verified chunks is released before a later chunk can fail authentication. Consumers requiring whole-message authentication before any plaintext release should use Single Message profiles (
singlemsg-triple-mac-v1). - Triple surface only. The binding exposes the Triple Pipeline facade; the Low-Level configuration surface stays Go-native and is not exported here.
- Profile roster is pinned. The C ABI exposes no profile enumeration;
ITB.profilesreturns the shipped identifiers pinned in the binding and does not include profiles registered at runtime.
itb-crystal
- 0
- 0
- 0
- 0
- 0
- about 1 hour ago
- August 30, 2026
MIT License
Wed, 02 Sep 2026 23:27:21 GMT