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 libitb3 shared library's ITB_Triple_* surface
(cmd/cshared). C11 static (libitb3_c.a) + shared (libitb3_c.so)
library that links against libitb3.so at compile time
(-litb3_c -litb3 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 / init_keys / load / save / rekey / free, Single Message encrypt /
decrypt, one-shot and 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; libitb3 builds as
libitb3.dylib. Windows: MinGW-w64 or clang against libitb3.dll.
The convenience driver builds libitb3.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/libitb3.so ./cmd/cshared
cd bindings/c && make allCompile against the public header and link the static archive plus
the underlying libitb3.so:
cc -std=c11 -I/path/to/bindings/c/include myapp.c \
/path/to/bindings/c/build/libitb3_c.a \
-L/path/to/dist/linux-amd64 -Wl,-rpath,/path/to/dist/linux-amd64 \
-litb3The header is C++-aware (extern "C" block guarded by
__cplusplus), so the same archive serves C and C++ consumers
without a separate wrapper.
#include <itb3.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();
/* Mode 1 per-region (default), Mode 2 per-container (compact VPN tunnel mode) */
itb_opts_set(opts, "mode", "2");
/* Interlocked Barrier chunk width: "48" (default) or "168" (requires 512-bit hash) */
itb_opts_set(opts, "interlock", "168");
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_init_keys constructs a Pipeline deterministically from eight caller-supplied master keys (one per seed slot 0..7) via per-slot KDF, so two peers holding the same eight master keys derive identical Pipeline instances without exchanging a session blob. The eight master keys must come from independent entropy or key-agreement sources — never derive all eight keys from a single secret or a single KDF parent key.
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.
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().
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 libitb3.
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. itb_pipeline_max_workers(receiver, n) sets the
worker cap for every subsequent cipher call (n <= 0 selects auto,
n > 256 is clamped to 256); the receiver may pick its own worker cap
after load — the cap is per-machine and never written to the blob.
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 libitb3; 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);Two process-wide knobs constrain Go runtime arena pacing, readable at
libitb3 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(4LL << 30); /* 4 GiB soft cap */
itb_set_gc_percent(100); /* balanced GC */./bindings/c/run_tests.shThe harness builds libitb3.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 libitb3 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
libitb3.so unchanged). tests/valgrind.supp silences memcheck noise
whose faulting frame lies inside libitb3.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), stream_pump (encrypt
stream pump), and stream_one_shot (EncryptStream) throughput at
1 MiB / 16 MiB / 64 MiB, reported as an MB/s table on stdout. The
runner exports ITB_GOMEMLIMIT=4GiB + ITB_GOGC=100 defaults
(respecting caller overrides) and the bench binaries apply the same
caps programmatically. See
bindings/BENCH.md
for the fleet-wide configuration authority and comparison tables.
The Go core ships an openssl-style CLI utility
itb3 that generates session blobs on disk
(itb3 genblob <mode> <hash> -o blob.json); this binding reopens
such blobs via itb_pipeline_load_f. itb3 also encrypts /
decrypts payloads directly on disk (-i / -o) or through stdin /
stdout, rotates outer masters, and inspects stored blobs. See
cmd/itb3/README.md for the full
subcommand reference.
A long-run stress harness under bindings/c/loop/ holds one
Pipeline handle for minutes, cycles encrypt → decrypt → compare
round-trips through it, rotates the outer masters and reopens the
handle from its session blob on a schedule, and reports whether the
process survived with every byte intact. It is the binding-side
counterpart of the Go harness under tools/loop: same flags, same
round structure, same summary in both renderings.
cd bindings/c && make loop
./run_loop.sh --duration 2m --shape both./loop/loop -h lists every flag. Concurrency mode: shared-handle —
POSIX threads call into one Pipeline handle concurrently, which
libitb3 permits once the handle is constructed, so --goroutines is
the thread count verbatim.
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 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.
Apache-2.0 — see LICENSE.