C++17 RAII wrapper over the libitb shared library (cmd/cshared).
Header-only: consumers include <itb.hpp> (or the finer-grained headers
under <itb/...>) and link against the C binding's static archive plus
libitb.so. Every fallible itb_* status surfaces as a typed exception
derived from itb::ItbError; every opaque handle is owned by a move-only
RAII type that releases on destruction.
Path placeholder. <itb> denotes the path to the local ITB
repository checkout (or this binding's mirror clone) — for example,
/home/you/go/src/itb or ~/projects/itb-cpp. Substitute the literal
token in the recipes below; shell does not expand it.
sudo pacman -S go go-tools gcc clang make cmake pkgconf check catch2gcc is the reference compiler; clang is exercised by the same
Makefile (CXX=clang++ make). pkgconf resolves pkg-config --cflags check for the C-binding test runner; catch2 is the C++17 framework
powering tests/test_*.cpp. Consumer applications linking
libitb_c.a plus libitb.so need none of the test-side packages — the
runtime surface uses only the C++17 standard library.
Run bindings/cpp/build.sh from anywhere to chain every prerequisite
build in one step:
./bindings/cpp/build.shThree underlying steps: build libitb.so from repo root, build
libitb_c.a via the C binding's Makefile, run check_header.sh to
verify include/itb.h has not drifted from
bindings/c/include/itb.h. The C++ wrappers themselves are
header-only. Equivalent manual invocation:
go build -trimpath -buildmode=c-shared \
-o dist/linux-amd64/libitb.so ./cmd/cshared
cd bindings/c && make
cd ../cpp && ./check_header.sh(macOS produces libitb.dylib under dist/darwin-<arch>/,
Windows produces libitb.dll under dist/windows-<arch>/.)
Both compilers accept the source unchanged at -std=c++17:
CXX=g++ make tests # reference compiler
CXX=clang++ make tests # LLVM-backed compilerThe Makefile ships a strict warning baseline (-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wold-style-cast -Wnon-virtual-dtor -Woverloaded-virtual) on top of -O2 -fPIC. Headers,
tests, and bench harness build clean under both compilers at this flag
set.
Compile against the public headers and link the static archive plus
libitb.so:
c++ -std=c++17 -O2 \
-I/path/to/bindings/cpp/include \
-I/path/to/bindings/c/include \
myapp.cpp \
-L/path/to/bindings/c/build -litb_c \
-L/path/to/dist/linux-amd64 -Wl,-rpath,/path/to/dist/linux-amd64 \
-litbFor CMake-driven projects, the binding ships a minimal INTERFACE
target carrying the C++ + C binding include directories transitively.
The consumer adds the static archive plus libitb.so against its own
executable target:
add_subdirectory(third_party/itb/bindings/cpp)
target_link_libraries(my_app PRIVATE itb)
target_link_libraries(my_app PRIVATE
${CMAKE_SOURCE_DIR}/third_party/itb/bindings/c/build/libitb_c.a
-L${CMAKE_SOURCE_DIR}/third_party/itb/dist/linux-amd64 -litb)Every itb::* wrapper resolves at link time against libitb_c.a plus
libitb.so. Runtime resolution of the shared library at process start
follows the standard dynamic-linker order:
LD_LIBRARY_PATHresolved at process startup.- The
rpathbaked at link time (-Wl,-rpath,../../dist/linux-amd64). The Makefile and the CMake snippet above embed this so installed binaries findlibitbwithoutLD_LIBRARY_PATH. - System loader path (
ld.so.cache,DYLD_LIBRARY_PATH,PATH).
Two process-wide knobs constrain Go runtime arena pacing. Both readable at libitb load time via env vars:
ITB_GOMEMLIMIT=512MiB— soft memory limit in bytes; supportsB/KiB/MiB/GiB/TiBsuffixes.ITB_GOGC=20— GC trigger percentage; default100, lower triggers GC more aggressively.
Programmatic setters override env-set values at any time. Pass -1 to either setter to query the current value without changing it.
itb::set_memory_limit(512LL << 20);
itb::set_gc_percent(20);cd bindings/cpp/
make tests # compile every tests/test_*.cpp into tests/build/test_*
make test # tests + run via run_tests.sh
./run_tests.shThe 41 test files cover Single + Triple round-trip across each PRF
primitive, authenticated paths, mixed primitives, persistence and
native blob round-trip, streaming chunked I/O, nonce-size variants,
lockSeed lifecycle, closed-state preflight, empty-payload rejection,
the typed exception hierarchy, and last_mismatch_field(). Each test
compiles to a standalone executable under tests/build/ linked
against build/libitb_c.a + libitb.so + Catch2 v3. Per-process
isolation gives every test a fresh libitb global state.
Filter via ITB_TEST_FILTER, forwarded to Catch2's filter syntax:
ITB_TEST_FILTER='[blake3]' ./run_tests.shThroughput numbers live in bench/BENCH.md; see
bench/README.md for invocation, environment
variables, and per-case output format. The harness covers four ops
across the nine PRF-grade primitives plus one mixed variant for both
Single and Triple at 1024-bit ITB key width and 16 MiB payload.
Four-pass sweep:
cd bindings/cpp/
make bench
./run_bench.sh # full 4-pass canonical sweepStreaming AEAD authenticates a chunked stream end-to-end while preserving the deniability of the per-chunk MAC-Inside-Encrypt container. Each chunk's MAC binds the encrypted payload to a 32-byte CSPRNG stream anchor (written as a once-per-stream wire prefix), the cumulative pixel offset of preceding chunks, and a final-flag bit — defending against chunk reorder, replay within or across streams sharing the PRF / MAC key, silent mid-stream drop, and truncate-tail. The wire format adds 32 bytes of stream prefix plus one byte of encrypted trailing flag per chunk; no externally visible MAC tag.
Easy Mode:
itb::Encryptor::stream_encrypt_auth accepts a StreamSource
(std::function<std::size_t(std::uint8_t*, std::size_t)>) and a
StreamSink (std::function<void(const std::uint8_t*, std::size_t)>).
Closures capturing std::ifstream / std::ofstream by reference adapt
file streams to the binding's callback shape. The MAC key is allocated
CSPRNG-fresh inside the encryptor at constructor time.
#include <fstream>
#include <itb.hpp>
#include <itb/wrapper.hpp>
#include <vector>
constexpr std::size_t kChunkSize = std::size_t(16) * 1024 * 1024;
auto make_reader = [](std::ifstream& in) {
return [&in](std::uint8_t* buf, std::size_t cap) -> std::size_t {
in.read(reinterpret_cast<char*>(buf), cap);
return static_cast<std::size_t>(in.gcount());
};
};
auto make_writer = [](std::ofstream& out) {
return [&out](const std::uint8_t* buf, std::size_t n) {
out.write(reinterpret_cast<const char*>(buf), n);
};
};
itb::Encryptor enc{"areion512", 1024, "hmac-blake3", 1};
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
// Sender — collect the inner ITB stream in memory, then wrap the
// transcript end-to-end through one keystream session before flushing
// to the wire.
{
std::vector<std::uint8_t> inner;
std::ifstream fin("/tmp/64mb.src", std::ios::binary);
enc.stream_encrypt_auth(
make_reader(fin),
[&inner](const std::uint8_t* p, std::size_t n) {
inner.insert(inner.end(), p, p + n);
},
kChunkSize);
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
itb::wrapper::WrapStreamWriter ww{
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size()};
ww.update_in_place(inner.data(), inner.size());
std::ofstream fout("/tmp/64mb.enc", std::ios::binary);
fout.write(reinterpret_cast<const char*>(ww.nonce().data()),
static_cast<std::streamsize>(ww.nonce().size()));
fout.write(reinterpret_cast<const char*>(inner.data()),
static_cast<std::streamsize>(inner.size()));
}
// Receiver — strip the leading nonce, unwrap the body, feed the
// recovered inner-stream bytes to decrypt_auth.
{
const std::size_t nlen =
itb::wrapper::nonce_size(itb::wrapper::Cipher::Aes128Ctr);
std::ifstream fin("/tmp/64mb.enc", std::ios::binary);
std::vector<std::uint8_t> wire_nonce(nlen, 0);
fin.read(reinterpret_cast<char*>(wire_nonce.data()),
static_cast<std::streamsize>(nlen));
itb::wrapper::UnwrapStreamReader ur{
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
wire_nonce.data(), wire_nonce.size()};
std::vector<std::uint8_t> inner_recovered;
{
std::vector<std::uint8_t> buf(1u << 16);
while (fin) {
fin.read(reinterpret_cast<char*>(buf.data()),
static_cast<std::streamsize>(buf.size()));
const auto got = static_cast<std::size_t>(fin.gcount());
if (got == 0) break;
ur.update_in_place(buf.data(), got);
inner_recovered.insert(inner_recovered.end(),
buf.begin(), buf.begin() + got);
}
}
std::size_t pos = 0;
auto inner_reader = [&](std::uint8_t* dst, std::size_t cap) -> std::size_t {
const std::size_t take = std::min(cap, inner_recovered.size() - pos);
std::copy_n(inner_recovered.begin() + static_cast<std::ptrdiff_t>(pos),
take, dst);
pos += take;
return take;
};
std::ofstream fout("/tmp/64mb.dst", std::ios::binary);
enc.stream_decrypt_auth(inner_reader, make_writer(fout), kChunkSize);
}Build + run:
g++ -std=c++17 -O2 -Wall -o main main.cpp \
-I <itb>/bindings/c/include \
-I <itb>/bindings/cpp/include \
<itb>/bindings/c/build/libitb_c.a \
-L <itb>/dist/linux-amd64 -litb \
-Wl,-rpath,<itb>/dist/linux-amd64 \
-lpthread -ldl
./mainOutput (verified):
Easy Mode src sha256: 7adc82f9bebf205db2a6c8033d7c1fe43d3bf8b3ecb0fbfd6c4c2dff71672425
Easy Mode dst sha256: 7adc82f9bebf205db2a6c8033d7c1fe43d3bf8b3ecb0fbfd6c4c2dff71672425
[OK] Easy Mode: 64 MiB roundtrip via stream-auth verified
Low-Level Mode:
Free functions itb::encrypt_stream_auth / itb::decrypt_stream_auth
take three itb::Seed instances plus an itb::Mac (32-byte key from
/dev/urandom) and stream through the same chunked-AEAD construction.
The same StreamSource / StreamSink closure shape applies as in Easy
Mode.
itb::Seed noise{"areion512", 1024};
itb::Seed data {"areion512", 1024};
itb::Seed start{"areion512", 1024};
auto mac_key = csprng_mac_key(); // 32 bytes from /dev/urandom
itb::Mac mac{"hmac-blake3", mac_key};
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
{
std::vector<std::uint8_t> inner;
std::ifstream fin("/tmp/64mb.src", std::ios::binary);
itb::encrypt_stream_auth(
noise, data, start, mac,
make_reader(fin),
[&inner](const std::uint8_t* p, std::size_t n) {
inner.insert(inner.end(), p, p + n);
},
kChunkSize);
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
itb::wrapper::WrapStreamWriter ww{
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size()};
ww.update_in_place(inner.data(), inner.size());
std::ofstream fout("/tmp/64mb.enc", std::ios::binary);
fout.write(reinterpret_cast<const char*>(ww.nonce().data()),
static_cast<std::streamsize>(ww.nonce().size()));
fout.write(reinterpret_cast<const char*>(inner.data()),
static_cast<std::streamsize>(inner.size()));
}Build + run:
g++ -std=c++17 -O2 -Wall -o main main.cpp \
-I <itb>/bindings/c/include \
-I <itb>/bindings/cpp/include \
<itb>/bindings/c/build/libitb_c.a \
-L <itb>/dist/linux-amd64 -litb \
-Wl,-rpath,<itb>/dist/linux-amd64 \
-lpthread -ldl
./mainOutput (verified):
Low-Level src sha256: 7adc82f9bebf205db2a6c8033d7c1fe43d3bf8b3ecb0fbfd6c4c2dff71672425
Low-Level dst sha256: 7adc82f9bebf205db2a6c8033d7c1fe43d3bf8b3ecb0fbfd6c4c2dff71672425
[OK] Low-Level Mode: 64 MiB roundtrip via stream-auth verified
Linking pulls in both the static C-binding archive
(build/libitb_c.a, used by the C++ wrapper internally) AND the shared
Go-built library (-litb). The C++ headers under bindings/cpp/include
re-use the C-binding's itb.h for the raw ABI declarations.
itb::Encryptor (mirroring the github.com/everanium/itb/easy Go
sub-package) replaces the lower-level Seed / encrypt / decrypt
ceremony with one constructor call: it allocates its own three
(Single) or seven (Triple) seeds plus MAC closure, snapshots the global
configuration into a per-instance Config, and exposes setters that
mutate only its own state. Two encryptors with different settings run
side-by-side without cross-contamination.
The MAC primitive is bound at construction time via the third argument
(hmac-blake3 — recommended default, hmac-sha256, kmac256); an
empty mac substitutes "hmac-blake3". A fresh 32-byte CSPRNG MAC
key is allocated alongside the per-seed PRF keys, and
enc.export_state() carries all of them in one
std::vector<std::uint8_t> blob.
// Sender
#include <itb.hpp>
#include <iostream>
#include <string>
#include <vector>
// mode = 1 = Single Ouroboros (3 seeds); mode = 3 = Triple (7 seeds).
itb::Encryptor enc{"areion512", 2048, "hmac-blake3", 1};
enc.set_nonce_bits(512); // default: 128
enc.set_barrier_fill(4); // CSPRNG fill margin; default: 1, valid: 1, 2, 4, 8, 16, 32
enc.set_bit_soup(1); // bit-level split ("bit-soup"; default: 0 = byte-level)
enc.set_lock_soup(1); // Insane Interlocked Mode: per-chunk PRF-keyed
// bit-permutation overlay on top of bit-soup
// (auto-couples with bit_soup for Single)
// enc.set_lock_seed(1); // optional dedicated lockSeed — separates
// bit-permutation PRF keying from the
// noiseSeed-driven noise channel; adds one
// extra seed slot (3 -> 4 / 7 -> 8). Must be
// called BEFORE the first encrypt — mid-session
// throws STATUS_EASY_LOCKSEED_AFTER_ENCRYPT.
// Persistence blob — seeds + PRF keys + MAC key (and lockSeed).
std::vector<std::uint8_t> blob = enc.export_state();
std::cout << "state blob: " << blob.size() << " bytes\n";
std::string plaintext = "any text or binary data - including 0x00 bytes";
// Authenticated encrypt — 32-byte tag embedded inside the RGBWYOPA
// container, preserving oracle-free deniability.
std::vector<std::uint8_t> encrypted = enc.encrypt_auth(plaintext);
std::cout << "encrypted: " << encrypted.size() << " bytes\n";
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
auto nonce = itb::wrapper::wrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
encrypted.data(), encrypted.size());
// Compose the on-wire blob: `nonce || mutated-ciphertext`.
std::vector<std::uint8_t> wire(nonce.size() + encrypted.size());
std::copy(nonce.begin(), nonce.end(), wire.begin());
std::copy(encrypted.begin(), encrypted.end(),
wire.begin() + static_cast<std::ptrdiff_t>(nonce.size()));
// Send `wire` + `blob` + `outerKey` (out-of-band). The destructor zeroes
// key material at scope exit; enc.close() surfaces release-time errors
// instead of swallowing them.
// Receiver
// Receive on-wire blob + state blob + outerKey (out-of-band).
// std::vector<std::uint8_t> wire = ...;
// std::vector<std::uint8_t> blob = ...;
// std::vector<std::uint8_t> outerKey = ...;
// Strip nonce + XOR-decrypt the body in place. body.first / body.second
// is the (pointer, length) view over the recovered ITB ciphertext.
auto body = itb::wrapper::unwrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
wire.data(), wire.size());
std::vector<std::uint8_t> encrypted(body.first, body.first + body.second);
itb::set_max_workers(8); // 8 cores (default: 0 = all)
// Optional: peek at blob metadata before constructing a matching
// encryptor (useful when multiplexing blobs of different shapes).
itb::PeekedConfig cfg = itb::peek_config(blob);
std::cout << "peek: primitive=" << cfg.primitive
<< " key_bits=" << cfg.key_bits
<< " mode=" << cfg.mode
<< " mac=" << cfg.mac_name << "\n";
itb::Encryptor dec{cfg.primitive, cfg.key_bits, cfg.mac_name, cfg.mode};
// import_state restores nonce_bits / barrier_fill / bit_soup /
// lock_soup and lockSeed from the blob. The set_* calls below are
// pre-import override knobs; barrier_fill is asymmetric — a
// receiver-set value > 1 wins over the blob's value.
dec.set_nonce_bits(512);
dec.set_barrier_fill(4);
dec.set_bit_soup(1);
dec.set_lock_soup(1);
// dec.set_lock_seed(1); // optional — import below restores it from the blob.
dec.import_state(blob);
// Authenticated decrypt — single-bit tamper triggers MAC failure
// (no oracle leak). Mismatch surfaces as ItbError with code() ==
// status::kMacFailure, not a corrupted plaintext.
try {
std::vector<std::uint8_t> recovered = dec.decrypt_auth(encrypted);
std::string recovered_str(recovered.begin(), recovered.end());
std::cout << "decrypted: " << recovered_str << "\n";
} catch (const itb::ItbError& e) {
if (e.code() == itb::status::kMacFailure) {
std::cout << "MAC verification failed -- tampered or wrong key\n";
} else {
throw;
}
}Each Encryptor is single-thread by construction. Cipher methods,
per-instance setters (set_nonce_bits / set_barrier_fill /
set_bit_soup / set_lock_soup / set_lock_seed / set_chunk_size),
and persistence (export_state / import_state) all mutate
per-instance state without locking — concurrent use against the same
encryptor requires external synchronisation. Distinct Encryptor
values, each owned by one thread, run independently against the libitb
worker pool.
Each cipher method returns a freshly-allocated
std::vector<std::uint8_t> owned by the caller. The encryptor's
internal cache (the libitb FFI write target) is invisible and lives
across calls; what reaches the caller is always a fresh copy. The
cached bytes are zeroed on grow / close() / destruction, so residual
ciphertext / plaintext does not linger beyond the next cipher call.
itb::Encryptor::Mixed / Mixed3 accept per-slot primitive names —
the noise / data / start (and optional lockSeed) slots may carry
different PRFs within the same native hash width. The state blob
carries per-slot primitives + PRF keys; the receiver constructs a
matching encryptor with the same arguments and import_states.
// Sender
#include <itb.hpp>
#include <iostream>
// Per-slot primitive selection (Single, 3 + 1 slots). Every name
// must share the same native hash width — mixing widths throws
// ItbError(STATUS_SEED_WIDTH_MIX). Triple mirror: Encryptor::Mixed3
// takes seven per-slot names (noise + 3 data + 3 start) + prim_l.
auto enc = itb::Encryptor::Mixed(
"blake3", // prim_n: noiseSeed: BLAKE3
"blake2s", // prim_d: dataSeed: BLAKE2s
"areion256", // prim_s: startSeed: Areion-SoEM-256
"blake2b256", // prim_l: dedicated lockSeed (empty for no lockSeed slot)
1024, // key_bits
"hmac-blake3"); // mac
enc.set_nonce_bits(512);
enc.set_barrier_fill(4);
// BitSoup + LockSoup auto-couple from prim_l above; explicit calls
// are unnecessary but harmless.
// Per-slot introspection — enc.primitive() returns "mixed",
// enc.primitive_at(slot) the per-slot name, enc.is_mixed() the
// predicate. Slot order: 0=noise, 1=data, 2=start, 3=lock (Single);
// Triple grows the middle range to 7 slots + lock.
std::cout << "mixed=" << enc.is_mixed()
<< " primitive=" << enc.primitive() << "\n";
for (int i = 0; i < 4; ++i) {
std::cout << " slot " << i << ": " << enc.primitive_at(i) << "\n";
}
auto blob = enc.export_state();
std::string plaintext = "mixed-primitive Easy Mode payload";
auto encrypted = enc.encrypt_auth(plaintext);
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
auto nonce = itb::wrapper::wrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
encrypted.data(), encrypted.size());
std::vector<std::uint8_t> wire(nonce.size() + encrypted.size());
std::copy(nonce.begin(), nonce.end(), wire.begin());
std::copy(encrypted.begin(), encrypted.end(),
wire.begin() + static_cast<std::ptrdiff_t>(nonce.size()));
// Receiver
// Receive on-wire blob + state blob + outerKey (out-of-band).
// std::vector<std::uint8_t> wire = ...;
// std::vector<std::uint8_t> blob = ...;
// std::vector<std::uint8_t> outerKey = ...;
auto body = itb::wrapper::unwrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
wire.data(), wire.size());
std::vector<std::uint8_t> encrypted_r(body.first, body.first + body.second);
// Matching mixed encryptor — every per-slot name plus key_bits and
// mac must agree with the sender. import_state validates each
// per-slot primitive; mismatches throw ItbEasyMismatchError with the
// offending JSON field name on .field().
auto dec = itb::Encryptor::Mixed(
"blake3", "blake2s", "areion256", "blake2b256",
1024, "hmac-blake3");
dec.import_state(blob);
auto decrypted = dec.decrypt_auth(encrypted_r);
std::string decrypted_str(decrypted.begin(), decrypted.end());
std::cout << "decrypted: " << decrypted_str << "\n";Triple Ouroboros (3× security: P × 2^(3×key_bits)) takes seven seeds
(one shared noiseSeed plus three dataSeed and three startSeed),
all wrapped behind one Encryptor constructor with mode = 3.
#include <itb.hpp>
#include <itb/wrapper.hpp>
#include <string>
// mode=3 selects Triple; other arguments behave as in the Single case.
itb::Encryptor enc{"areion512", 2048, "hmac-blake3", 3};
std::string plaintext = "Triple Ouroboros payload";
auto encrypted = enc.encrypt_auth(plaintext);
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
auto nonce = itb::wrapper::wrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
encrypted.data(), encrypted.size());
std::vector<std::uint8_t> wire(nonce.size() + encrypted.size());
std::copy(nonce.begin(), nonce.end(), wire.begin());
std::copy(encrypted.begin(), encrypted.end(),
wire.begin() + static_cast<std::ptrdiff_t>(nonce.size()));
// Receiver: strip nonce + XOR-decrypt body in place.
auto body = itb::wrapper::unwrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
wire.data(), wire.size());
std::vector<std::uint8_t> encrypted_r(body.first, body.first + body.second);
auto decrypted = enc.decrypt_auth(encrypted_r);
// decrypted holds the recovered plaintext bytes.The seven-seed split is internal; on-wire ciphertext shape matches
Single Ouroboros, only the internal payload split / interleave differs.
Mixed-primitive Triple is reachable via Encryptor::Mixed3.
The lower-level path uses explicit itb::Seed handles for the
noise / data / start trio plus an optional dedicated Seed wired in
via Seed::attach_lock_seed. Useful when the caller needs full
control over per-slot keying (e.g. PRF material stored in an HSM).
// Sender
#include <itb.hpp>
#include <array>
#include <iostream>
#include <string>
// Optional: global configuration (process-wide, atomic).
itb::set_max_workers(8);
itb::set_nonce_bits(512); // default: 128
itb::set_barrier_fill(4); // default: 1, valid: 1, 2, 4, 8, 16, 32
itb::set_bit_soup(1); // bit-level split ("bit-soup"; default: 0)
itb::set_lock_soup(1); // per-chunk PRF-keyed bit-permutation overlay
// (auto-couples with bit_soup for Single)
// Three independent CSPRNG-keyed Areion-SoEM-512 seeds. Each Seed
// pre-keys its primitive once at construction.
itb::Seed ns{"areion512", 2048}; // noise
itb::Seed ds{"areion512", 2048}; // data
itb::Seed ss{"areion512", 2048}; // start
// Optional dedicated lockSeed — separates the bit-permutation PRF's
// keying material from the noiseSeed-driven noise-injection channel.
// The bit-permutation overlay must be engaged (set_bit_soup(1) or
// set_lock_soup(1) — both on above) before the first encrypt.
itb::Seed ls{"areion512", 2048}; // lock
ns.attach_lock_seed(ls);
// HMAC-BLAKE3 — 32-byte CSPRNG key, 32-byte tag. Real code pulls
// the key bytes from a CSPRNG (e.g. /dev/urandom); the zero key
// here is for example purposes only.
std::vector<std::uint8_t> mac_key(32, 0);
itb::Mac mac{"hmac-blake3", mac_key};
std::string plaintext = "any text or binary data - including 0x00 bytes";
// Authenticated encrypt — 32-byte tag embedded inside the RGBWYOPA
// container, preserving oracle-free deniability.
auto encrypted = itb::encrypt_auth(ns, ds, ss, mac, plaintext);
std::cout << "encrypted: " << encrypted.size() << " bytes\n";
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
auto nonce = itb::wrapper::wrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
encrypted.data(), encrypted.size());
std::vector<std::uint8_t> wire(nonce.size() + encrypted.size());
std::copy(nonce.begin(), nonce.end(), wire.begin());
std::copy(encrypted.begin(), encrypted.end(),
wire.begin() + static_cast<std::ptrdiff_t>(nonce.size()));
// Cross-process persistence: itb::Blob512 packs every seed's keys +
// components, lockSeed, and MAC into one JSON blob.
itb::Blob512 blob;
blob.set_key(itb::blob::Slot::Noise, ns.hash_key());
blob.set_components(itb::blob::Slot::Noise, ns.components());
blob.set_key(itb::blob::Slot::Data, ds.hash_key());
blob.set_components(itb::blob::Slot::Data, ds.components());
blob.set_key(itb::blob::Slot::Start, ss.hash_key());
blob.set_components(itb::blob::Slot::Start, ss.components());
blob.set_key(itb::blob::Slot::Lock, ls.hash_key());
blob.set_components(itb::blob::Slot::Lock, ls.components());
blob.set_mac_key(mac_key);
blob.set_mac_name("hmac-blake3");
auto blob_bytes =
blob.export_blob(itb::blob::LockSeed | itb::blob::Mac);
std::cout << "persistence blob: " << blob_bytes.size() << " bytes\n";
// Send `encrypted` + `blob_bytes`; wrapper destructors zero + release
// every handle at scope exit.
// Receiver
itb::set_max_workers(8); // deployment knob — not in Blob512
// Receive on-wire blob + blob_bytes + outerKey (out-of-band).
// std::vector<std::uint8_t> wire = ...;
// std::vector<std::uint8_t> blob_bytes = ...;
// std::vector<std::uint8_t> outerKey = ...;
// Strip nonce + XOR-decrypt body in place; encrypted_r is a view over
// the recovered ITB ciphertext bytes inside `wire`.
auto body = itb::wrapper::unwrap_in_place(
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
wire.data(), wire.size());
std::vector<std::uint8_t> encrypted(body.first, body.first + body.second);
// import_blob restores per-slot keys + components and applies the
// captured globals via the process-wide setters.
itb::Blob512 restored;
restored.import_blob(blob_bytes);
using itb::blob::Slot;
auto rebuild = [&](Slot s) {
return itb::Seed::from_components(
"areion512", restored.get_components(s), restored.get_key(s));
};
auto ns2 = rebuild(Slot::Noise);
auto ds2 = rebuild(Slot::Data);
auto ss2 = rebuild(Slot::Start);
auto ls2 = rebuild(Slot::Lock);
ns2.attach_lock_seed(ls2);
itb::Mac mac2{restored.get_mac_name(), restored.get_mac_key()};
// Authenticated decrypt — single-bit tamper triggers MAC failure
// (no oracle leak).
auto decrypted = itb::decrypt_auth(ns2, ds2, ss2, mac2, encrypted);
std::string decrypted_str(decrypted.begin(), decrypted.end());
std::cout << "decrypted: " << decrypted_str << "\n";The push-pattern wrappers (itb::StreamEncryptor /
itb::StreamDecryptor plus seven-seed StreamEncryptorTriple /
StreamDecryptorTriple) and the free-function bridges
(itb::encrypt_stream / itb::decrypt_stream plus Triple variants)
wrap the Single Message Encrypt / Decrypt API behind a chunked I/O surface.
ITB ciphertexts cap at ~64 MB plaintext per chunk; the binding slices
larger inputs, encrypts each chunk through the regular FFI path, and
concatenates the results. Memory peak is bounded by chunk_size
(default kDefaultChunkSize = 16 MiB); chunk_size = 0 throws
ItbError(STATUS_BAD_INPUT).
Push wrappers take a StreamSink
(std::function<void(const std::uint8_t*, std::size_t)>) receiving
each emitted chunk; the caller drives via write(buf) / feed(buf)
and finally close(). The free-function bridges add a StreamSource
(std::function<std::size_t(std::uint8_t*, std::size_t)>) — read up
to cap bytes, return the number actually read (zero on EOF) — and
loop until EOF internally.
#include <itb.hpp>
#include <itb/wrapper.hpp>
#include <vector>
itb::Seed n{"blake3", 1024};
itb::Seed d{"blake3", 1024};
itb::Seed s{"blake3", 1024};
// Outer cipher key - preferred surface for HKDF / ML-KEM / key-rotation policy in user-side application. ITB Inner seeds + PRF key keep as CSPRNG derived.
auto outerKey = itb::wrapper::generate_key(itb::wrapper::Cipher::Aes128Ctr);
// Push-pattern: sink receives each ITB chunk. close() flushes the
// trailing partial chunk; destructor best-effort-flushes on scope exit.
std::vector<std::uint8_t> sink;
{
itb::StreamEncryptor enc{n, d, s,
[&sink](const std::uint8_t* p, std::size_t len) {
sink.insert(sink.end(), p, p + len);
},
1u << 16};
enc.write(std::string_view{"chunk one"});
enc.write(std::string_view{"chunk two"});
enc.close();
}
// Format-deniability ITB masking via outer-cipher wrapper (AES-128-CTR) ~0% overhead (Recommended in every case).
std::vector<std::uint8_t> wire;
{
itb::wrapper::WrapStreamWriter ww{
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size()};
wire.reserve(ww.nonce().size() + sink.size());
wire.insert(wire.end(), ww.nonce().begin(), ww.nonce().end());
auto wrapped = ww.update(sink.data(), sink.size());
wire.insert(wire.end(), wrapped.begin(), wrapped.end());
}
// Receiver: strip nonce, unwrap body, feed to decrypt.
const std::size_t nlen =
itb::wrapper::nonce_size(itb::wrapper::Cipher::Aes128Ctr);
itb::wrapper::UnwrapStreamReader ur{
itb::wrapper::Cipher::Aes128Ctr,
outerKey.data(), outerKey.size(),
wire.data(), nlen};
auto ciphertext = ur.update(wire.data() + nlen, wire.size() - nlen);
// Feed-pattern: feed ciphertext bytes at any granularity (partial
// chunks buffered); sink receives each decrypted plaintext. close()
// throws when leftover bytes do not form a complete chunk.
std::vector<std::uint8_t> psink;
{
itb::StreamDecryptor dec{n, d, s,
[&psink](const std::uint8_t* p, std::size_t len) {
psink.insert(psink.end(), p, p + len);
}};
dec.feed(ciphertext);
dec.close();
}
// psink == bytes-of("chunk onechunk two")Switching itb::set_nonce_bits mid-stream produces a chunk header
layout the paired decryptor (which snapshots itb_header_size at
construction) cannot parse — the nonce size must be stable for the
lifetime of one stream pair.
The stream wrappers cache raw pointers to the supplied Seed (and
optional Mac) values. Every Seed and Mac handed to a stream
wrapper MUST remain alive for the entire session — letting any go out
of scope before close() / destructor returns is undefined behaviour
(use-after-free in the FFI call). Practical pattern: declare the seeds
in the same scope as the stream and before it, so reverse-order
destruction outlives the stream.
itb::Blob128 / Blob256 / Blob512 are width-typed containers
packing low-level encryptor material (per-seed hash key + components,
optional lockSeed, optional MAC key + name) plus captured process-wide
configuration into one self-describing JSON blob. Used on the
lower-level path where each seed slot may carry a different primitive;
Encryptor::export_state is a narrower one-primitive-per-encryptor
view of the same wire format.
Slot identifiers live in itb::blob::Slot (Noise, Data, Start,
Lock, Data1–Data3, Start1–Start3); option flags in the
bitwise itb::blob::Opt (None, LockSeed, Mac). Combine flags
with bitwise OR and pass to export_blob / export_triple.
export_blob packs Single, export_triple packs Triple; importers
reject the wrong shape with ItbBlobModeMismatchError. Globals
(NonceBits / BarrierFill / BitSoup / LockSoup) are captured at export
and applied process-wide on import via itb::set_*.
Binding-side PRF-only order — Areion-SoEM-256, Areion-SoEM-512,
BLAKE2b-256, BLAKE2b-512, BLAKE2s, BLAKE3, AES-CMAC,
SipHash-2-4, ChaCha20. FFI names: areion256, areion512,
blake2b256, blake2b512, blake2s, blake3, aescmac, siphash24,
chacha20. Below-spec lab primitives (CRC128, FNV-1a, MD5) are absent
by construction — the libitb registry does not expose them.
Single Ouroboros — three seeds (noiseSeed, dataSeed, startSeed)
via itb::encrypt / decrypt / encrypt_auth / decrypt_auth.
Triple Ouroboros (3× security: P × 2^(3×key_bits)) — seven seeds (one
shared noiseSeed, three dataSeed, three startSeed) via the
*_triple counterparts. Streaming: StreamEncryptorTriple /
StreamDecryptorTriple / encrypt_stream_triple /
decrypt_stream_triple.
All seeds in one call must share the same native hash width; mixing
widths throws ItbError(STATUS_SEED_WIDTH_MIX).
Names match the libitb MAC registry; ordering matches that registry's declaration order.
| MAC | Key bytes | Tag bytes | Underlying primitive |
|---|---|---|---|
kmac256 |
32 | 32 | KMAC256 (Keccak-derived) |
hmac-sha256 |
32 | 32 | HMAC over SHA-256 |
hmac-blake3 |
32 | 32 | HMAC over BLAKE3 |
kmac256 and hmac-sha256 accept keys 16 bytes and longer; the binding fleet's tests and examples use 32 bytes uniformly across primitives for cross-binding consistency. hmac-blake3 requires exactly 32 bytes by construction.
Process-wide setters (itb::set_nonce_bits, set_barrier_fill,
set_bit_soup, set_lock_soup, set_max_workers) are atomic
(atomic.Int32.Store) and safe from any thread in isolation. The
caveat is logical, not atomic — changing any knob while an encrypt /
decrypt call is in flight corrupts the running operation, since the
cipher snapshots its configuration at entry. Treat the globals as
set-once-at-startup; runtime updates need external sequencing against
active cipher calls.
A single itb::Encryptor is not safe for concurrent use — cipher
methods, per-instance setters, and persistence all mutate per-instance
state without locking. Distinct Encryptor handles, each owned by one
thread, run independently against the libitb worker pool.
The low-level free functions (itb::encrypt / itb::decrypt /
itb::encrypt_auth / itb::decrypt_auth plus Triple counterparts)
take read-only Seed references and allocate output per call — they
are thread-safe under concurrent invocation on the same seeds. Two
exceptions: Seed::attach_lock_seed mutates the noise seed and must
not race against an in-flight cipher call on it, and the process-wide
setters above stay process-global.
itb::last_error() is captured into thread-local storage on every
failing call. The status code on every raised ItbError is unaffected
by thread interleaving.
Signal-handler reentrance. No binding entry point is
async-signal-safe — they allocate via malloc, mutate per-thread
last-error TLS, and dispatch into libitb's Go worker pool, all
incompatible with signal-safety(7). Post work from a signal handler
to a regular thread (e.g. eventfd / pipe-write) and re-enter from
there.
Two complementary surfaces ship:
Encryptor::export_state/import_state. High-level: one call serialises every seed slot, PRF key, MAC key + name, and per-instance configuration into astd::vector<std::uint8_t>blob. The receiver constructs a matching encryptor (same primitive / key_bits / mode / mac) and callsimport_state(blob).itb::peek_config(blob)parses metadata (primitive/key_bits/mode/mac_name) without full validation — useful when multiplexing blobs of different shapes.Blob128/Blob256/Blob512. Low-level: width-typed Native Blob containers — see the section above for the per-slot API.
Both capture process-wide configuration (NonceBits / BarrierFill / BitSoup / LockSoup) at export. Easy Mode restore applies per-instance; Native Blob restore applies via the process-wide setters.
Every setter takes effect for all subsequent encrypt / decrypt calls
in the process. Out-of-range values throw
ItbError(STATUS_BAD_INPUT) rather than crashing.
| Function | Accepted values | Default |
|---|---|---|
itb::set_max_workers(n) |
non-negative int | 0 (auto) |
itb::set_nonce_bits(n) |
128, 256, 512 | 128 |
itb::set_barrier_fill(n) |
1, 2, 4, 8, 16, 32 | 1 |
itb::set_bit_soup(mode) |
0 (off), non-zero (on) | 0 |
itb::set_lock_soup(mode) |
0 (off), non-zero (on) | 0 |
Read-only accessors: itb::max_key_bits(), itb::channels(),
itb::header_size(), itb::version(). Each setter has a paired
itb::get_* getter.
For custom file formats around ITB chunks:
itb::parse_chunk_len(header, len) inspects the fixed-size chunk
header and returns the chunk's on-the-wire length;
itb::header_size() returns the active header byte count (20 / 36 /
68 for nonce sizes 128 / 256 / 512 bits).
itb::list_hashes() returns std::vector<HashEntry> (name /
width); itb::list_macs() returns std::vector<MacEntry> (name
/ key_size / tag_size / min_key_bytes). Shipping MACs:
kmac256, hmac-sha256, hmac-blake3.
See Threading model above for the set-once-at-startup discipline these setters require.
Every non-OK libitb status surfaces as a typed exception derived from
itb::ItbError, carrying the structural status code (code()) and
the textual diagnostic (message()):
#include <itb.hpp>
#include <iostream>
std::vector<std::uint8_t> keybuf(32, 0);
try {
itb::Mac bad{"nonsense", keybuf};
} catch (const itb::ItbError& e) {
// e.code() == itb::status::kBadMac
std::cerr << "code=" << e.code()
<< " name=" << e.name()
<< " msg=" << e.message() << "\n";
}The typed-exception hierarchy:
ItbError— base class;int code(),std::string_view message(),std::string_view name(), formattedwhat().ItbEasyMismatchError—STATUS_EASY_MISMATCH; addsstd::string_view field()with the offending JSON field name from a failedEncryptor::import_state.ItbBlobModeMismatchError—STATUS_BLOB_MODE_MISMATCH; Native Blob importer receives a Single blob into a Triple receiver (or vice versa).ItbBlobMalformedError—STATUS_BLOB_MALFORMED; JSON parse, magic / shape failure, or a too-new version surfaced viapeek_config(which conflates version-too-new with malformed).ItbBlobVersionTooNewError—STATUS_BLOB_VERSION_TOO_NEW;import_blobdifferentiates version-too-new fromMalformed.
Two free functions expose the per-thread libitb diagnostic surface independently of the exception path:
itb::last_error()— textual diagnostic from the most recent non-OK call on the calling thread (empty string when none).itb::last_mismatch_field()—std::optional<std::string>with the offending JSON field name from the most recentSTATUS_EASY_MISMATCH. The same value reachesItbEasyMismatchError::field(); this free function is for callers needing it outside try / catch.
Status-code constants in itb::status::k* map bit-identically to the
C binding's ITB_* enum; itb::status::name(code) returns a stable
textual name. Empty plaintext / ciphertext is rejected with
STATUS_ENCRYPT_FAILED ("itb: empty data") — pass at least one byte.
The 26 ITB_* constants are mirrored bit-identically as
itb::status::k* constexpr ints in <itb/errors.hpp>.
| Code | Constant | C++ mirror | Path | Typed exception |
|---|---|---|---|---|
| 0 | ITB_OK |
itb::status::kOk |
success | (no throw) |
| 1 | ITB_BAD_HASH |
itb::status::kBadHash |
cold | ItbError |
| 2 | ITB_BAD_KEY_BITS |
itb::status::kBadKeyBits |
cold | ItbError |
| 3 | ITB_BAD_HANDLE |
itb::status::kBadHandle |
cold | ItbError |
| 4 | ITB_BAD_INPUT |
itb::status::kBadInput |
cold | ItbError |
| 5 | ITB_BUFFER_TOO_SMALL |
itb::status::kBufferTooSmall |
cold | ItbError |
| 6 | ITB_ENCRYPT_FAILED |
itb::status::kEncryptFailed |
cold | ItbError |
| 7 | ITB_DECRYPT_FAILED |
itb::status::kDecryptFailed |
cold | ItbError |
| 8 | ITB_SEED_WIDTH_MIX |
itb::status::kSeedWidthMix |
cold | ItbError |
| 9 | ITB_BAD_MAC |
itb::status::kBadMac |
cold | ItbError |
| 10 | ITB_MAC_FAILURE |
itb::status::kMacFailure |
warm | ItbError |
| 11 | ITB_EASY_CLOSED |
itb::status::kEasyClosed |
cold | ItbError |
| 12 | ITB_EASY_MALFORMED |
itb::status::kEasyMalformed |
cold | ItbError |
| 13 | ITB_EASY_VERSION_TOO_NEW |
itb::status::kEasyVersionTooNew |
cold | ItbError |
| 14 | ITB_EASY_UNKNOWN_PRIMITIVE |
itb::status::kEasyUnknownPrimitive |
cold | ItbError |
| 15 | ITB_EASY_UNKNOWN_MAC |
itb::status::kEasyUnknownMac |
cold | ItbError |
| 16 | ITB_EASY_BAD_KEY_BITS |
itb::status::kEasyBadKeyBits |
cold | ItbError |
| 17 | ITB_EASY_MISMATCH |
itb::status::kEasyMismatch |
warm | ItbEasyMismatchError |
| 18 | ITB_EASY_LOCKSEED_AFTER_ENCRYPT |
itb::status::kEasyLockSeedAfterEncrypt |
cold | ItbError |
| 19 | ITB_BLOB_MODE_MISMATCH |
itb::status::kBlobModeMismatch |
warm | ItbBlobModeMismatchError |
| 20 | ITB_BLOB_MALFORMED |
itb::status::kBlobMalformed |
warm | ItbBlobMalformedError |
| 21 | ITB_BLOB_VERSION_TOO_NEW |
itb::status::kBlobVersionTooNew |
warm | ItbBlobVersionTooNewError |
| 22 | ITB_BLOB_TOO_MANY_OPTS |
itb::status::kBlobTooManyOpts |
cold | ItbError |
| 23 | ITB_STREAM_TRUNCATED |
itb::status::kStreamTruncated |
warm | ItbStreamTruncatedError |
| 24 | ITB_STREAM_AFTER_FINAL |
itb::status::kStreamAfterFinal |
warm | ItbStreamAfterFinalError |
| 99 | ITB_INTERNAL |
itb::status::kInternal |
cold | ItbError |
Warm-path codes — MAC failure on tampered ciphertext, Easy Mode blob
mismatch, Native Blob import-side discriminators — are typically
caught as their typed subclass. Cold-path codes (programmer errors,
malformed input, internal sentinels) are usually caught generically as
ItbError.
- C++17 minimum. Headers use
std::string_view,std::optional, structured bindings,if constexpr, and inline variables. GCC ≥ 9 and Clang ≥ 9 meet the baseline. - Header-only. Consumer-visible declarations live in
include/itb/*.hpp(plus the syncedinclude/itb.hC-ABI header); no archive or shared library is produced for the C++ surface itself. - Underlying static archive + libitb.so required. Links
transitively against
bindings/c/build/libitb_c.aanddist/<os>-<arch>/libitb.<ext>— both must be built first. - No external runtime deps beyond libc++ + libitb.so. The test runner additionally requires Catch2 v3.
- Frozen C ABI. The
ITB_*exports ininclude/itb.h(synced from the C binding) are the contract; the binding does not extend or reshape them. - No
dlopen. Symbols are bound at link time. Consumers wanting runtime FFI loading can wrap this binding in their owndlopenshim.
| Header | Public surface |
|---|---|
<itb.hpp> |
Meta-header — pulls in every wrapper below |
<itb/errors.hpp> |
ItbError base + four typed subclasses; itb::status::*; last_error() / last_mismatch_field() |
<itb/library.hpp> |
Process-wide setters / getters, list_hashes / list_macs, version(), header_size(), parse_chunk_len() |
<itb/seed.hpp> |
itb::Seed — RAII over itb_seed_t; CSPRNG and from_components constructors; width() / hash_key() / components() / attach_lock_seed() |
<itb/mac.hpp> |
itb::Mac — RAII over itb_mac_t; (name, key) constructor |
<itb/cipher.hpp> |
Free-function low-level entry points — encrypt / decrypt / encrypt_auth / decrypt_auth plus Triple counterparts |
<itb/encryptor.hpp> |
itb::Encryptor — Easy Mode RAII; single-primitive constructor + Mixed / Mixed3; cipher methods, per-instance setters, persistence; peek_config |
<itb/streams.hpp> |
StreamEncryptor / StreamDecryptor push + Triple variants; free-function bridges encrypt_stream / decrypt_stream; kDefaultChunkSize |
<itb/blob.hpp> |
Blob128 / Blob256 / Blob512 Native Blob wrappers; blob::Slot / blob::Opt |
All public types live in the top-level itb:: namespace; helpers
between headers live in itb::detail::. The itb::status::* constants
and the itb::blob::Slot / itb::blob::Opt enums are namespaced for
collision-free use. Hash names via itb::list_hashes(); MAC names
(kmac256, hmac-sha256, hmac-blake3) via itb::list_macs().
Two additional process-wide setters from <itb/library.hpp> configure
the Go runtime inside libitb. Both functions return the previous value
and accept a negative argument as a "query only, do not change"
sentinel.
| Function | Purpose |
|---|---|
std::int64_t itb::set_memory_limit(std::int64_t limit) |
Sets the Go runtime heap soft limit in bytes. Overrides the ITB_GOMEMLIMIT env var sourced at library load. |
int itb::set_gc_percent(int pct) |
Sets the Go GC trigger percentage (default 100). Overrides the ITB_GOGC env var sourced at library load. |