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). C11 static (libitb_c.a) + shared (libitb_c.so)
library that links against libitb.so at compile time
(-litb_c -litb with an embedded RPATH) — no runtime symbol loading.
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 one itb_pipeline
handle (init / load / save / rekey / free, Single Message encrypt /
decrypt, whole-buffer stream pumps, incremental itb_stream sessions
with write / end / read), an itb_opts query-string builder for init
overrides, the profile-record entries (itb_register / itb_lookup
/ itb_profiles / itb_inspect), and the Go runtime knobs.
sudo pacman -S go gcc makeGeneric Linux: a Go toolchain, a C11 compiler (gcc or clang), and GNU
make. macOS: the same via Xcode command-line tools; libitb builds as
libitb.dylib. Windows: MinGW-w64 or clang against libitb.dll.
The convenience driver builds libitb.so, the C library, and every
test binary in one step:
./bindings/c/build.shEquivalent manual invocation:
go build -trimpath -buildmode=c-shared \
-o dist/linux-amd64/libitb.so ./cmd/cshared
cd bindings/c && make allCompile against the public header and link the static archive plus
the underlying libitb.so:
cc -std=c11 -I/path/to/bindings/c/include myapp.c \
/path/to/bindings/c/build/libitb_c.a \
-L/path/to/dist/linux-amd64 -Wl,-rpath,/path/to/dist/linux-amd64 \
-litbThe header is C++-aware (extern "C" block guarded by
__cplusplus), so the same archive serves C and C++ consumers
without a separate wrapper.
#include <itb.h>
itb_pipeline *sender = NULL, *receiver = NULL;
itb_status st = itb_pipeline_init("singlemsg-triple-mac-v1", NULL, &sender);
/* st != ITB_STATUS_OK → consult itb_last_error() */
uint8_t *blob = NULL; size_t blob_len = 0;
st = itb_pipeline_save(sender, &blob, &blob_len);
st = itb_pipeline_load(blob, blob_len, NULL, 0, NULL, 0, &receiver);
itb_bytes_free(blob);
uint8_t *wire = NULL; size_t wire_len = 0;
st = itb_pipeline_encrypt_message(sender, (const uint8_t *)"data", 4,
&wire, &wire_len);
uint8_t *plain = NULL; size_t plain_len = 0;
st = itb_pipeline_decrypt_message(receiver, wire, wire_len,
&plain, &plain_len);
itb_bytes_free(wire);
itb_bytes_free(plain);
itb_pipeline_free(receiver);
itb_pipeline_free(sender);itb_opts overrides the profile default at init (chunk size, outer
cipher, parallax on/off, wrapper on/off, MAC name, palette, worker
cap); every setter goes through itb_opts_set(opts, key, value). The
resolved shape travels inside the blob, so the receiver needs no
options of its own:
itb_opts *opts = itb_opts_new();
itb_opts_set(opts, "chunkSize", "65536");
itb_opts_set(opts, "withWrapper", "false");
itb_opts_set(opts, "maxWorkers", "4");
itb_pipeline_init("singlemsg-triple-mac-v1", opts, &sender);
itb_opts_free(opts);itb_pipeline_rekey rotates the parallax + wrapper masters
mid-session (the eight ITB seeds and MAC key are fixed for the
session lifetime by design) and hands back the fresh blob; the
receiver picks up the new masters by loading it:
uint8_t perm[32] = { /* fresh */ }, wrap[32] = { /* fresh */ };
uint8_t *rotated = NULL; size_t rotated_len = 0;
itb_pipeline_rekey(sender, perm, sizeof perm, wrap, sizeof wrap,
&rotated, &rotated_len);
itb_pipeline_load(rotated, rotated_len, NULL, 0, NULL, 0, &receiver);
itb_bytes_free(rotated);The same rotation is available on the receiver side as a master
override pair on load: itb_pipeline_load(blob, blob_len, perm, sizeof perm, wrap, sizeof wrap, &receiver) reopens the blob with
fresh masters folded in.
itb_pipeline_encrypt_stream_one_shot /
itb_pipeline_decrypt_stream_one_shot put a whole in-memory payload
through the stream chain in a single call. For bounded-memory
streaming, itb_pipeline_encrypt_stream_pump /
itb_pipeline_decrypt_stream_pump move a whole buffer through an
incremental session; the explicit itb_pipeline_encrypt_stream_begin
/ itb_pipeline_decrypt_stream_begin sessions expose
itb_stream_write / itb_stream_end / itb_stream_read for
caller-driven loops.
The blob returned by itb_pipeline_save is a self-describing session
bundle: it carries the resolved profile record, the inner key
material, and the parallax / wrapper masters. itb_pipeline_load
reconstructs a Pipeline from it without naming a profile.
uint8_t *blob = NULL; size_t blob_len = 0;
itb_pipeline_save(sender, &blob, &blob_len); /* current blob bytes */
itb_pipeline_load(blob, blob_len, NULL, 0, NULL, 0, &receiver); /* reopen from bytes */
itb_pipeline_save_f(sender, "session.blob"); /* write to a file (mode 0600) */
itb_pipeline_load_f("session.blob", NULL, 0, NULL, 0, &receiver2); /* reopen from a file */
char *json = NULL;
itb_inspect(blob, blob_len, &json); /* profile record, no Pipeline */
/* json: {"name":"singlemsg-triple-mac-v1","mode":"singlemsg-mac",...} */
itb_string_free(json);
itb_bytes_free(blob);itb_inspect decodes the embedded profile record (a JSON object)
without constructing a Pipeline. itb_pipeline_save_f /
itb_pipeline_load_f perform the file access inside libitb.
Load works for blobs generated with shipped primitives (every entry in
the shipped catalogue). Blobs generated by Go programs that use
hashes.Register or macs.Register to install custom primitives
cannot be loaded through this binding — the receiver must use the Go
library directly and register the same custom primitive under the
same name before opening. Attempting to load such a blob through this
binding surfaces ITB_STATUS_RECIPE_PRIMITIVE_UNKNOWN.
Runtime tuning. The worker cap is per-machine and never travels in the blob; the receiver may pick its own after load:
itb_pipeline_max_workers(receiver, 4); /* clamped by libitb; <= 0 selects auto */itb_register installs a user-defined profile under a new name from
a profile JSON record; itb_lookup reads a registered record back;
itb_profiles lists every registered name as a JSON array. The
record's field rules are enforced by libitb; the binding treats the
JSON as an opaque string.
itb_register("my-nomac-plain",
"{\"mode\":\"singlemsg-nomac\",\"width\":512,\"hash\":\"areion512\","
"\"keybits\":1024,\"wrapper\":false,\"parallax\":false}");
char *json = NULL;
itb_lookup("my-nomac-plain", &json); /* record with "name" filled in */
itb_string_free(json);
itb_profiles(&json); /* ["blob-triple-mac-v1", ...] */
itb_string_free(json);Profile names, opts keys, and every primitive name are validated by
the Go side; a rejected string surfaces as a non-OK itb_status with
the diagnostic available via itb_last_error().
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 -1 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(512LL << 20); /* 512 MiB soft cap */
itb_set_gc_percent(20); /* aggressive GC */./bindings/c/run_tests.shThe harness builds libitb.so + the C library, compiles every
tests/test_*.c to its own standalone executable under
tests/build/, and runs each in turn; per-process isolation gives
every test a fresh libitb global state. The suite covers Single
Message round trips per shipped profile, stream pumps, incremental
sessions with pathological batch sizes, tampered-wire failure
stickiness, mid-flight cancellation, rekey, profile registration,
opts-builder encoding, and error mapping — surface parity checks; the
deep suite lives in Go under the shipped tree. Override the compiler
via CC=clang ./bindings/c/run_tests.sh.
cd bindings/c
make test-asan # test suite under AddressSanitizer
make test-ubsan # test suite under UndefinedBehaviorSanitizer
make test-valgrind # test suite under valgrind --leak-check=fullThe sanitizer targets rebuild the library + tests into separate
build directories (build/asan, build/ubsan) so instrumented and
plain objects never mix. test-valgrind requires valgrind to be able
to read the host ld.so symbols (on some distributions this needs
the glibc debug-symbol package); where host symbols are unavailable,
the same binaries run under valgrind inside a stock ubuntu:24.04
container with valgrind + libc6-dbg installed and the repository
bind-mounted at its host path (the embedded RPATH resolves
libitb.so unchanged). tests/valgrind.supp silences memcheck noise
whose faulting frame lies inside libitb.so — the Go runtime manages
its own stacks and heap in ways memcheck cannot model; errors in the
binding's own C frames are never suppressed.
./bindings/c/run_bench.shMicro-benches: message (EncryptMessage) and stream_pump
(encrypt stream pump) throughput at 1 KiB / 64 KiB / 1 MiB / 16 MiB,
reported as an MB/s table on stdout. The runner exports
ITB_GOMEMLIMIT=512MiB + ITB_GOGC=20 defaults (respecting caller
overrides) and the bench binaries apply the same caps
programmatically.
A small CLI under bindings/c/eitb/ mirrors the shipped Go
tools/eitb scope for shell smoke tests:
cd bindings/c/eitb && make
./eitb version
./eitb profiles
./eitb encrypt singlemsg-triple-mac-v1 in.bin out.bin # blob hex on stderr
./eitb decrypt singlemsg-triple-mac-v1 <blob-hex> out.bin back.bindecrypt reopens the session with itb_pipeline_load from the blob
hex; the profile argument only selects the Single Message or
streaming cipher pair.
The shipped itb3 binary under cmd/itb3/ of the main repository
generates profile files (.json on disk) that this binding reopens
via itb_pipeline_load_f; the same utility also encrypts and
decrypts files directly. See cmd/itb3/README.md for full usage.
- The binding wraps the Triple Pipeline surface only. The Low-Level seed / MAC / blob / wrapper / parallax APIs are not exposed — use the shipped Go core for those.
- Streaming-decrypt caveat: chunked Streaming AEAD verifies per chunk, so plaintext of verified chunks is released before a later chunk can fail authentication.
- The
itb_last_error()text is process-global last-write-wins on the Go side; fetch it immediately after the failing call. The status code is always attributable. itb_pipeline_rekeymust not run concurrently with cipher calls or open stream sessions on the same Pipeline.- Handles are freed exactly once (
itb_pipeline_free/itb_stream_free, both NULL-safe); a stream session must not outlive its Pipeline.