C / C++ SDK
The C/C++ SDK wraps the Rust SDK via a C ABI and ships as libratify_c.a (static) and
libratify_c.so / libratify_c.dylib (shared). The header (include/ratify.h) is
auto-generated by cbindgen and includes extern "C" guards so it works in both C and C++.
Byte-for-byte interoperable with Go, TypeScript, Python, and Rust. Apache-2.0.
Stability: every primitive on this page is stable in 1.0.0-alpha.13.
When to use this SDK
Section titled “When to use this SDK”| You’re writing | Use |
|---|---|
| A Go service or CLI | Go SDK |
| A Node.js or browser app | TypeScript SDK |
| A Python script or ML pipeline | Python SDK |
| A Rust service or high-performance binary | Rust SDK — use directly, no FFI overhead |
| C or C++ code | This SDK |
| Firmware, RTOS, hardware driver | This SDK (static library, libratify_c.a) |
| A language that FFIs to C (Swift, Zig, Julia, Lua, etc.) | This SDK |
Install
Section titled “Install”Option 1 — Pre-built library (no Rust required)
Section titled “Option 1 — Pre-built library (no Rust required)”Download the pre-built library for your target from the
GitHub Releases page.
Each release ships .tar.gz archives for common targets:
| Archive | Target |
|---|---|
ratify-c-*-x86_64-linux.tar.gz | x86-64 Linux (server, desktop) |
ratify-c-*-aarch64-linux.tar.gz | ARM64 Linux (Raspberry Pi 4, embedded SBCs) |
ratify-c-*-armv7-linux.tar.gz | ARM32 Linux (Raspberry Pi 2/3) |
ratify-c-*-x86_64-macos.tar.gz | macOS Intel |
ratify-c-*-aarch64-macos.tar.gz | macOS Apple Silicon |
ratify-c-*-x86_64-windows.zip | Windows x86-64 |
Each archive contains lib/libratify_c.a, lib/libratify_c.so (or .dylib/.dll),
and include/ratify.h. The header is also committed to the repo at
sdks/c/include/ratify.h
— you can vendor it directly without downloading a release.
Option 2 — Build from source (requires Rust 1.70+)
Section titled “Option 2 — Build from source (requires Rust 1.70+)”If your target isn’t in the release archives, or you need a custom feature flag:
git clone https://github.com/identities-ai/ratify-protocolcd ratify-protocol/sdks/ccargo build --releaseOutputs:
target/release/libratify_c.a — static librarytarget/release/libratify_c.so — shared library (Linux)target/release/libratify_c.dylib — shared library (macOS)include/ratify.h — C/C++ headerCross-compile for embedded targets with cross:
cargo install cross --git https://github.com/cross-rs/cross
# ARM64 — Raspberry Pi 4, embedded Linuxcross build --release --target aarch64-unknown-linux-gnu
# ARM32 — Raspberry Pi 2/3cross build --release --target armv7-unknown-linux-gnueabihf
# ARM Cortex-M4/M7 — FreeRTOS / Zephyrrustup target add thumbv7em-none-eabihfcargo build --release --target thumbv7em-none-eabihf --features custom-entropy
# RISC-V 64cross build --release --target riscv64gc-unknown-linux-gnuQuick start — Delegate → Present → Verify
Section titled “Quick start — Delegate → Present → Verify”The complete agent authorization flow in C. All API entry/exit conditions are explicitly handled.
#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#include "ratify.h"
static int fail(const char *step, char *err) { fprintf(stderr, "FAIL: %s — %s\n", step, err ? err : "unknown error"); ratify_error_free(err); return 1;}
int main(void) { printf("Ratify %s — C SDK example\n\n", ratify_version());
char *err = NULL;
/* 1. Generate identities */ RatifyHumanRoot *root = NULL; if (ratify_human_root_generate(&root) != RatifyOk) return fail("ratify_human_root_generate", NULL);
RatifyAgent *agent = NULL; if (ratify_agent_generate("MyDroneBot", "drone", &agent) != RatifyOk) { ratify_human_root_free(root); return fail("ratify_agent_generate", NULL); }
/* 2. Issue a DelegationCert */ int64_t now = (int64_t)time(NULL); RatifyDelegationCert *cert = NULL; if (ratify_delegation_issue(root, agent, "[\"physical:enter\"]", now, now + 3600LL, &cert, &err) != RatifyOk) return fail("ratify_delegation_issue", err);
char *cert_json = ratify_delegation_cert_to_json(cert, &err); if (!cert_json) return fail("ratify_delegation_cert_to_json", err);
/* 3. Generate challenge and build ProofBundle */ uint8_t challenge[32]; if (ratify_challenge_generate(challenge, 32) != RatifyOk) return fail("ratify_challenge_generate", NULL);
RatifyProofBundle *bundle = NULL; if (ratify_proof_bundle_create(agent, cert_json, challenge, 32, now, &bundle, &err) != RatifyOk) return fail("ratify_proof_bundle_create", err); ratify_string_free(cert_json);
char *bundle_json = ratify_proof_bundle_to_json(bundle, &err); if (!bundle_json) return fail("ratify_proof_bundle_to_json", err);
/* 4. Verify */ RatifyVerifyResult *result = NULL; ratify_verify_bundle(bundle_json, "physical:enter", now, &result, &err);
if (ratify_verify_result_is_valid(result)) { char *agent_id = ratify_verify_result_agent_id(result); printf("authorized agent: %s\n", agent_id); ratify_string_free(agent_id); } else { char *status = ratify_verify_result_identity_status(result); printf("rejected: %s\n", status); ratify_string_free(status); }
/* 5. Cleanup */ ratify_verify_result_free(result); ratify_string_free(bundle_json); ratify_proof_bundle_free(bundle); ratify_delegation_cert_free(cert); ratify_agent_free(agent); ratify_human_root_free(root); return 0;}Build and run:
# macOScc example.c -I include -L target/release \ -lratify_c -lpthread -framework Security -framework CoreFoundation \ -o example && ./example
# Linuxcc example.c -I include -L target/release \ -lratify_c -lpthread -ldl -lm -o example && ./exampleVerifier-side: branching on identity status
Section titled “Verifier-side: branching on identity status”ratify_verify_result_identity_status returns a stable string constant you can compare
directly. These values are byte-identical across every Ratify SDK.
char *status = ratify_verify_result_identity_status(result);
if (strcmp(status, "authorized_agent") == 0) { /* ✓ All checks passed. Use ratify_verify_result_agent_id / ratify_verify_result_human_id to log the delegation chain. */
} else if (strcmp(status, "expired") == 0) { /* ✗ At least one cert in the chain is past its expires_at. */
} else if (strcmp(status, "revoked") == 0) { /* ✗ A cert ID matched a revoked entry from your revocation callback. */
} else if (strcmp(status, "scope_denied") == 0) { /* ✗ The required scope is not in the chain's effective scope. */
} else if (strcmp(status, "constraint_denied") == 0) { /* ✗ A geo / time / amount / rate constraint was violated. */
} else if (strcmp(status, "constraint_unverifiable") == 0) { /* ✗ A constraint present on a cert had no context to evaluate (e.g. geo_circle but has_location == 0). Fail-closed. */
} else if (strcmp(status, "constraint_unknown") == 0) { /* ✗ Unknown constraint type — future-proof fail-closed behavior. */
} else if (strcmp(status, "delegation_not_authorized") == 0) { /* ✗ A cert in the chain sub-delegated without identity:delegate. */
} else { /* "invalid" — bad signature, broken chain, stale challenge, etc. ratify_verify_result_error_reason() carries a stable error code. */ char *reason = ratify_verify_result_error_reason(result); fprintf(stderr, "invalid: %s\n", reason); ratify_string_free(reason);}
ratify_string_free(status);ratify_verify_result_free(result);JSON transport
Section titled “JSON transport”The C API works entirely in JSON strings. ratify_proof_bundle_to_json serializes a bundle;
ratify_verify_bundle accepts the JSON string directly. This is the natural transport layer:
/* Agent side: serialize bundle to JSON, send over your transport. The JSON string is what you PUT in an HTTP header, publish to MQTT, write to a CAN-bus frame, or pass over any other channel. */char *bundle_json = ratify_proof_bundle_to_json(bundle, &err);/* send bundle_json to the verifier ... */
/* Verifier side: receive the JSON string, verify inline — no deserialization step needed. */RatifyVerifyResult *result = NULL;ratify_verify_bundle(bundle_json, "physical:enter", (int64_t)time(NULL), &result, &err);Similarly, certs are serialized with ratify_delegation_cert_to_json for storage or
forwarding, and the verifier accepts them as-is.
Constraints (geo / time / amount / rate)
Section titled “Constraints (geo / time / amount / rate)”Supply a RatifyVerifierContext when a delegation may carry constraints.
Use ratify_verify_bundle_opts instead of the simple ratify_verify_bundle:
/* Gate: drone must be within a pre-authorized geo circle */RatifyVerifierContext ctx = {0};ctx.current_lat = 37.7749;ctx.current_lon = -122.4194;ctx.has_location = 1; /* MUST set this; lat/lon are ignored if 0 */
RatifyVerifyOptions opts = {0};opts.required_scope = "drone:deliver";opts.context = &ctx;
RatifyVerifyResult *result = NULL;char *err = NULL;ratify_verify_bundle_opts(bundle_json, &opts, &result, &err);
/* result is fail-closed on constraint violation: identity_status = "constraint_denied" if outside radius identity_status = "constraint_unverifiable" if has_location == 0 */Other context fields:
ctx.current_speed_mps = 12.5; ctx.has_speed = 1; /* max_speed_mps constraint */ctx.current_amount = 99.0; ctx.has_amount = 1; /* max_amount constraint */ctx.current_rate = 3.0; ctx.has_rate = 1; /* max_rate constraint */Sub-delegation
Section titled “Sub-delegation”Agent-to-agent delegation uses the same primitives. Alice grants Agent A the
identity:delegate scope; A can then issue a delegation to Agent B. Without
identity:delegate, any sub-delegation attempt is rejected with
delegation_not_authorized.
/* ── Alice → Agent A ─────────────────────────────────────────────────── */RatifyDelegationCert *cert_a = NULL;/* "[\"meeting:attend\",\"identity:delegate\"]" grants sub-delegation right */ratify_delegation_issue(root, agent_a, "[\"meeting:attend\",\"identity:delegate\"]", now, now + 86400LL, &cert_a, &err);char *cert_a_json = ratify_delegation_cert_to_json(cert_a, &err);
/* ── Agent A → Agent B (subset of A's grant) ─────────────────────────── *//* Agent A re-wraps itself as a "root" for B's cert: use ratify_issue_sub_delegation. B's cert scope must be a subset; expiry must be ≤ A's cert expiry. */RatifyDelegationCert *cert_b = NULL;ratify_sub_delegation_issue(agent_a, agent_b, "[\"meeting:attend\"]", /* no identity:delegate — B cannot sub-delegate further */ now, now + 3600LL, cert_a_json, &cert_b, &err);char *cert_b_json = ratify_delegation_cert_to_json(cert_b, &err);
/* ── Build and verify the two-hop chain ──────────────────────────────── *//* Pass both cert JSONs as a JSON array: "[cert_b_json, cert_a_json]" Order: leaf first (cert closest to agent), root last. */char chain[8192];snprintf(chain, sizeof(chain), "[%s,%s]", cert_b_json, cert_a_json);
RatifyProofBundle *bundle = NULL;ratify_proof_bundle_create_chain(agent_b, chain, challenge, 32, now, &bundle, &err);char *bundle_json = ratify_proof_bundle_to_json(bundle, &err);
RatifyVerifyResult *result = NULL;ratify_verify_bundle(bundle_json, "meeting:attend", now, &result, &err);/* result.human_id is Alice; result.agent_id is Agent B */Revocation
Section titled “Revocation”Pass a revocation callback in RatifyVerifyOptions. The callback receives the cert_id
string for each cert in the chain and must return 1 (revoked), 0 (not revoked), or
-1 (lookup failed — fail-closed, same as revoked).
typedef struct { const char **revoked_ids; size_t count; } RevocationDB;
static int is_revoked(const char *cert_id, void *userdata) { RevocationDB *db = (RevocationDB *)userdata; for (size_t i = 0; i < db->count; i++) { if (strcmp(cert_id, db->revoked_ids[i]) == 0) return 1; /* revoked */ } return 0; /* not revoked */ /* return -1 on lookup error to fail-closed */}
/* Wire the callback */RevocationDB my_db = { .revoked_ids = revoked, .count = n_revoked };
RatifyVerifyOptions opts = {0};opts.required_scope = "physical:enter";opts.revocation_fn = is_revoked;opts.revocation_userdata = &my_db;
RatifyVerifyResult *result = NULL;char *err = NULL;ratify_verify_bundle_opts(bundle_json, &opts, &result, &err);/* identity_status == "revoked" if any cert_id is in the revoked list */Integration
Section titled “Integration”cmake_minimum_required(VERSION 3.20)project(my_agent C)
set(RATIFY_SDK_DIR "${CMAKE_SOURCE_DIR}/vendor/ratify-c")
add_library(ratify STATIC IMPORTED)set_target_properties(ratify PROPERTIES IMPORTED_LOCATION "${RATIFY_SDK_DIR}/lib/libratify_c.a" INTERFACE_INCLUDE_DIRECTORIES "${RATIFY_SDK_DIR}/include")
add_executable(my_agent main.c)target_link_libraries(my_agent PRIVATE ratify pthread dl m)project('my_agent', 'c')
ratify_dep = declare_dependency( include_directories: include_directories('vendor/ratify-c/include'), link_args: [ '-L' + meson.source_root() / 'vendor/ratify-c/lib', '-lratify_c', '-lpthread', '-ldl', '-lm', ],)
executable('my_agent', 'main.c', dependencies: [ratify_dep])RATIFY_SDK = vendor/ratify-c
CFLAGS = -I$(RATIFY_SDK)/includeLDFLAGS = -L$(RATIFY_SDK)/lib -lratify_c -lpthread -ldl -lm
my_agent: main.c $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)#include "ratify.h" // extern "C" guards are built in — works as-is#include <string>#include <stdexcept>
class RatifyVerifier {public: bool verify(const std::string& bundle_json, const std::string& scope) { RatifyVerifyResult* result = nullptr; char* err = nullptr; auto status = ratify_verify_bundle( bundle_json.c_str(), scope.empty() ? nullptr : scope.c_str(), 0, // 0 = use system clock &result, &err ); if (status != RatifyOk || !result) { std::string msg = err ? err : "unknown error"; ratify_error_free(err); throw std::runtime_error("verify failed: " + msg); } bool valid = ratify_verify_result_is_valid(result) != 0; ratify_verify_result_free(result); return valid; }};Embedded / RTOS — FreeRTOS custom entropy
Section titled “Embedded / RTOS — FreeRTOS custom entropy”On standard OS targets (Linux, macOS, Raspberry Pi) entropy is automatic. On RTOS targets
without /dev/urandom, enable the custom-entropy Cargo feature and register your hardware TRNG:
Cargo.toml:
ratify-c = { path = "…", features = ["custom-entropy"] }Application startup (STM32 example):
#include "ratify.h"
static int my_entropy(uint8_t *buf, size_t len) { for (size_t i = 0; i < len; i += 4) { uint32_t rnd; if (HAL_RNG_GenerateRandomNumber(&hrng, &rnd) != HAL_OK) return -1; /* -1 = entropy unavailable → library halts */ size_t copy = (len - i < 4) ? (len - i) : 4; memcpy(buf + i, &rnd, copy); } return 0;}
int main(void) { /* Must be called before ratify_challenge_generate() or ratify_delegation_issue() */ ratify_set_entropy_source(my_entropy); /* … rest of your RTOS application */}The library halts if ratify_set_entropy_source() was never called when custom-entropy is
enabled — generating certs with weak randomness is worse than not running at all.
no_std + alloc
Section titled “no_std + alloc”The C SDK is compatible with RTOS environments that provide a heap (alloc) but not the full
Rust std. Bare-metal Cortex-M targets with no heap at all should use the Rust SDK directly
with #[no_std] + alloc.
Supported targets
Section titled “Supported targets”| Architecture | Target triple | Example hardware |
|---|---|---|
| x86-64 | x86_64-unknown-linux-gnu | Intel/AMD server, Linux PC |
| ARM64 | aarch64-unknown-linux-gnu | Raspberry Pi 4, embedded Linux, Apple Silicon |
| ARM32 | armv7-unknown-linux-gnueabihf | Raspberry Pi 2/3, older embedded Linux |
| ARM Cortex-M4/M7 | thumbv7em-none-eabihf | STM32, NXP — FreeRTOS, Zephyr |
| x86-32 | i686-unknown-linux-gnu | Legacy industrial, 32-bit Linux |
| RISC-V 64 | riscv64gc-unknown-linux-gnu | SiFive, emerging IoT |
| macOS ARM64 | aarch64-apple-darwin | Apple Silicon Mac |
| macOS x86-64 | x86_64-apple-darwin | Intel Mac |
| Windows x86-64 | x86_64-pc-windows-msvc | Native Windows |
A Raspberry Pi test script is available at
sdks/c/scripts/test-raspberry-pi.sh.
Conformance
Section titled “Conformance”The C SDK passes all 63 canonical fixtures — the same set used by Go, TypeScript, Python, and Rust. This includes all verify fixtures (proof-bundle verification, constraints, session/stream binding, revocation) and all non-verify fixture kinds (scope expansion, revocation lists, revocation push, key rotation, session tokens, transaction receipts, witness entries).
# All 63 conformance fixturescargo test --test conformance
# Core ABI unit tests (null pointers, malformed JSON, round-trips, bad-argument detection)cargo test --test api
# Advanced ABI tests (providers, receipt helpers, entropy hooks)cargo test --test advancedTotal: 123 tests (63 conformance + 60 ABI unit tests across api and advanced).
Memory management
Section titled “Memory management”Every function that returns a heap-allocated value documents which _free function to call.
NULL is always safe to pass to _free functions.
ratify_human_root_free(root);ratify_agent_free(agent);ratify_delegation_cert_free(cert);ratify_proof_bundle_free(bundle);ratify_verify_result_free(result);ratify_string_free(any_string); /* for *_to_json, *_id, *_status, *_reason */ratify_error_free(err); /* for err_out parameters */Where to next
Section titled “Where to next”- Physical AI guide — embedding the verifier on robots and controllers
- Protocol concepts: Delegate → Present → Verify — every primitive in depth
- Constraints — geo, time, amount, and rate gate vocabulary
- SDK packages — version table and publishing process
- C SDK README — full API reference