Bindings API
Look up the canonical JSON, hashing, signing, and verification helpers the Chio SDKs share, with the C ABI and its ownership rules.
On this page
The bindings contract fixes how a Chio SDK canonicalizes, hashes, signs, and verifies protocol objects, so a receipt checked in Python returns the same verification result as the Rust reference.
chio-binding-helpers defines the contract in Rust. The TypeScript, Python, and Go SDKs mirror its API shape, input and output rules, and error codes; another language links the C ABI or implements the contract against the shared conformance vectors. Transport, sessions, and authentication sit above this layer, in the language SDKs.
Try verify_receipt, signer_trusted, or chio_buffer_free. Each entry has its own link.
Synopsis
The crate root re-exports one module per concern. These snake_case names are the reference names; a binding renames them to its own convention. The smallest useful call verifies one stored receipt against the kernel key you pinned.
use chio_binding_helpers::verify_receipt_json_with_trusted_signer_hex;
// receipt_json: one stored receipt; kernel_key_hex: the kernel key you pinned.
let verification =
verify_receipt_json_with_trusted_signer_hex(&receipt_json, &[kernel_key_hex])?;
assert!(verification.signature_valid);
assert!(verification.signer_trusted);
assert!(verification.ok);| Module | Functions | Types |
|---|---|---|
canonical | canonicalize_ | |
capability | parse_, capability_, verify_, verify_ | Capability, Capability |
error | Error, ErrorCode, Result | |
hashing | sha256_, sha256_hex_utf8 | |
manifest | parse_, signed_, verify_, verify_ | Manifest |
receipt | parse_, receipt_, verify_receipt, verify_, verify_, verify_, verify_ | Receipt, Receipt |
signing | is_, is_, public_, sign_, verify_, sign_, verify_ | Canonical, Utf8 |
Scope
The contract covers canonical JSON, hashing, signing, and verification of protocol objects. Transport, authentication, session state, and orchestration live in the language SDKs.
What the contract owns
- Canonical JSON helpers (RFC 8785).
- SHA-256 hashing helpers for both bytes and UTF-8 strings.
- Ed25519 signing and verification helpers.
- Receipt parsing and verification helpers.
- Capability parsing and verification helpers.
- Signed manifest parsing and verification helpers.
- Delegation-chain shape validity, returned as a field of capability verification and walked through
chio-core. - Stable, bindings-oriented error codes.
Runtime and integration responsibilities
The language SDK or the embedding host owns these:
- Session state machines.
- Remote HTTP or stream transports.
- Auth discovery, OAuth, token exchange, and token providers.
- Task orchestration and nested callback routers.
- Trust-control service clients.
- Kernel execution, which belongs to the selected runtime or embedding host.
Public functions
Each module's functions, with the rule each one applies.
Canonical JSON
canonicalize_json_str(input: &str) -> Result<String>
RFC 8785 JSON Canonicalization Scheme. The helper sorts object keys lexicographically by UTF-16 code unit, emits numbers in ECMAScript Number.prototype.toString form, and writes no insignificant whitespace. Two inputs that parse to the same JSON value produce the same output.
Hashing
sha256_hex_bytes(input: &[u8]) -> Stringsha256_hex_utf8(input: &str) -> String
SHA-256 over the input, hex-encoded lowercase. sha256_hex_utf8 calls sha256_hex_bytes on the UTF-8 encoding of the string, with no byte-order mark.
Ed25519 signing
is_valid_public_key_hex(value): parses as a Chio public key. Bare hex is read as Ed25519 (64 lowercase hex characters); ap256:orp384:prefix selects an ECDSA key, and ahybrid:prefix selects a hybrid classical and post-quantum key.is_valid_signature_hex(value): parses as a Chio signature. Bare hex is read as Ed25519 (128 lowercase hex characters); the samep256:,p384:, andhybrid:prefixes select the matching algorithm.public_key_hex_matches(left, right): case-insensitive equality after normalizing the hex. The comparison is an ordinary string comparison and runs in variable time.sign_utf8_message_ed25519(input, seed_hex) -> Utf8MessageSignatureverify_utf8_message_ed25519(input, public_key_hex, signature_hex) -> boolsign_json_str_ed25519(input, seed_hex) -> CanonicalJsonSignatureverify_json_str_signature_ed25519(input, public_key_hex, signature_hex) -> bool
The JSON helpers canonicalize the input before they sign or verify it, so an already-canonical JSON string and its non-canonical equivalent produce the same signature. CanonicalJsonSignature returns that canonical form alongside public_key_hex and signature_hex.
Receipts
parse_receipt_json(input) -> ChioReceiptreceipt_body_canonical_json(receipt) -> Stringverify_receipt(receipt) -> ReceiptVerificationverify_receipt_json(input) -> ReceiptVerificationverify_receipt_with_trusted_signers(receipt, trusted_signers) -> ReceiptVerificationverify_receipt_with_trusted_signer_hex(receipt, trusted_signers) -> ReceiptVerificationverify_receipt_json_with_trusted_signer_hex(input, trusted_signers) -> ReceiptVerification
The helper checks the canonical-body signature against the embedded kernel_key, recomputes the receipt ID from the body, and compares the parameter hash with SHA-256 of the canonical action arguments. Signature verification dispatches on the key and signature prefix, accepting Ed25519 (bare hex), p256:, p384:, and hybrid: material.
verify_receipt calls verify_receipt_with_trusted_signers(receipt, &[]). The trusted-signer variants also require the receipt's kernel_key to appear in the supplied signer set. With an empty signer set the helper still reports each check, and signer_trusted, ok, and authorized are false.
ReceiptVerification carries signature_valid, parameter_hash_valid, receipt_id_valid, decision (a ReceiptDecisionKind of Allow, Deny, Cancelled, Incomplete, or None), receipt_kind, boundary_class, trust_level, result, authorized, signer_key_hex, signer_trusted, and ok. Read the fields this way:
signature_valid,parameter_hash_valid,receipt_id_valid: the three integrity checks. Read them individually when diagnosing a failure.signer_trusted: the embedded signer matches a key in the supplied, nonempty trust set.ok: integrity and signer trust passed. A valid denial can set it to true.authorized: integrity and signer trust passed for a recorded mediated decision at thepreventboundary with anAllowdecision.result: a label derived from receipt semantics and decision, separate from the aggregate verification result.
A valid trace or advisory receipt is evidence and leaves authorized false. authorized describes the recorded decision; authorizing a new request and checking its freshness happen at the kernel.
Capabilities
parse_capability_json(input) -> CapabilityTokencapability_body_canonical_json(capability) -> Stringverify_capability(capability, now_secs: u64, max_delegation_depth: Option<u32>) -> CapabilityVerificationverify_capability_json(input, now_secs: u64, max_delegation_depth: Option<u32>) -> CapabilityVerification
verify_capability takes the parsed token, the current Unix timestamp in seconds, and an optional maximum delegation depth. The returned CapabilityVerification carries signature_valid, delegation_chain_shape_valid, time_valid, and a time_status of valid, not_yet_valid, or expired.
use std::time::{SystemTime, UNIX_EPOCH};
use chio_binding_helpers::{parse_capability_json, verify_capability};
let cap = parse_capability_json(&json)?;
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let max_delegation_depth = Some(8_u32);
let verification = verify_capability(&cap, now, max_delegation_depth)?;
assert!(verification.signature_valid);
assert!(verification.delegation_chain_shape_valid);
assert!(verification.time_valid);The delegation-chain walk validates chain shape: each per-link signature, delegator-to-delegatee continuity, timestamp ordering, and chain length against max_delegation_depth. The walk leaves parent and child scope comparison, scope attenuation, and chain binding to the stricter chio-core path, which this call never invokes. That is why the field is named delegation_chain_shape_valid.
The crate exposes no standalone verify_delegation_chain or check_attenuation. A binding calls the verification helper and reads the field; direct chain and attenuation inspection lives in chio-core.
Signed manifests
parse_signed_manifest_json(input) -> SignedManifestsigned_manifest_body_canonical_json(signed_manifest) -> Stringverify_signed_manifest(signed_manifest) -> ManifestVerificationverify_signed_manifest_json(input) -> ManifestVerification
ManifestVerification carries structure_valid, signature_valid, embedded_public_key_valid, and embedded_public_key_matches_signer.
C ABI
chio-bindings-ffi wraps the same helpers behind a C calling convention. Its Cargo.toml declares crate-type = ["staticlib", "cdylib", "rlib"], so one build produces a static archive, a shared library, and a Rust library. A language without a Rust toolchain links the shared library and calls the symbols below.
Two #[repr(C)] structs carry every result. ChioFfiBuffer is a ptr and a len. ChioFfiResult is a status, an error_code, and a data buffer. Status is CHIO_FFI_STATUS_OK, CHIO_FFI_STATUS_ERROR, CHIO_FFI_STATUS_PANIC, or CHIO_FFI_STATUS_NULL_ARGUMENT, and error_code is the numeric form of the error codes below. The caller frees every returned buffer with chio_buffer_free.
| Symbol | Parameters | Returns | Behavior |
|---|---|---|---|
chio_ | ChioFfiBuffer | void | Releases a buffer the library allocated. The caller owns every buffer it receives. |
chio_ | () | u32 | Returns CHIO_ |
chio_ | () | ChioFfiResult | JSON with crate_name, crate_version, abi_version, target, and features. |
chio_ | input_json | ChioFfiResult | canonicalize_ |
chio_ | input_utf8 | ChioFfiResult | sha256_hex_utf8. |
chio_ | input, input_len | ChioFfiResult | sha256_ |
chio_ | input_utf8, seed_hex | ChioFfiResult | sign_ |
chio_ | input_utf8, public_key_hex, signature_hex | ChioFfiResult | verify_ |
chio_ | input_json, seed_hex | ChioFfiResult | sign_ |
chio_ | input_json, public_key_hex, signature_hex | ChioFfiResult | verify_ |
chio_ | input_json, now_secs, max_ | ChioFfiResult | verify_ |
chio_ | input_json | ChioFfiResult | verify_ |
chio_ | input_json, trusted_ | ChioFfiResult | verify_ |
chio_ | input_json | ChioFfiResult | verify_ |
The table holds the 14 exported symbols, every #[no_mangle] pub extern "C" item in the crate. Pointer arguments are C strings unless a length parameter follows. String entrypoints reject a null argument with CHIO_FFI_STATUS_NULL_ARGUMENT. For byte input, a null pointer is accepted only when the length is zero. A non-null pointer must reference readable input for the entire call; null checks cannot establish that a foreign pointer is valid.
Input and output rules
Prefer these shapes at the binding boundary:
- JSON-string input for structured payloads.
- UTF-8 string input for signed text helpers.
- Byte-slice input when byte identity matters.
- Verification result structs, so a caller reads which check failed.
Keep these out of it:
- Deep internal Rust types.
- Opaque handles, unless they wrap reusable compiled objects.
- Async APIs in the invariants layer.
- Ownership-sensitive runtime state.
Error codes
ErrorCode enumerates the codes, and Error::code maps every error the crate raises onto one of them. The enum serializes in snake_case, so the serialized value is the code below. An SDK exposes it through a typed error class. Depend on the code, never on the Rust enum spelling or message text; removing or renaming a code breaks that dependency.
invalid_public_key: Public key hex is malformed or the wrong length.invalid_hex: Hex decoding failed.invalid_signature: Signature hex is malformed or the wrong length.json: JSON parsing failed.canonical_json: RFC 8785 canonicalization refused the input.capability_expired: Capability is pastexpires_at.capability_not_yet_valid: Capability is beforeissued_at.capability_revoked: Capability appears in a revocation list.delegation_chain_broken: A link in the delegation chain fails its signature check.attenuation_violation: Child scope exceeds parent scope.scope_mismatch: The requested action is not within the capability scope.signature_verification_failed: Signature verification returned false.delegation_depth_exceeded: Chain depth exceeds the supplied limit.invalid_hash_length: A hash field is not 32 bytes.merkle_proof_failed: A Merkle inclusion proof did not reproduce the root.empty_tree: A Merkle operation was requested on an empty tree.invalid_proof_index: A Merkle proof index is out of range.empty_manifest: A signed manifest has no tools.duplicate_tool_name: A signed manifest contains two tools with the same name.invalid_tool_name: A manifest tool name is empty or otherwise invalid.invalid_input_schema: A tool input schema is not a JSON object.invalid_output_schema: A tool output schema is not a JSON object.duplicate_server_tool: A server-tool allowlist entry appears more than once.invalid_manifest_field: A required manifest field is missing or malformed.invalid_required_permission: A declared required permission is empty or malformed.duplicate_required_permission: A required permission is declared more than once.unsupported_schema: The schema identifier is unknown to this binding.manifest_verification_failed: The manifest body signature failed to verify.
Cross-language conformance
The corpus lives at tests/bindings/vectors/, one directory per family, each with a v1.json of inputs and expected outputs. MANIFEST.sha256 pins the bytes of every vector file, so a binding compares against the same corpus the Rust reference does.
| Family | Cases | Covers |
|---|---|---|
canonical/ | Key ordering by UTF-16 code unit, escaping, i64 and u64 boundaries, subnormals, negative zero, and supplementary- | |
capability/ | 20 | Signature, time-bound, and delegation- |
eval/v1.json | The golden chio. | |
hashing/v1.json | SHA-256 over UTF-8 inputs: empty, block-aligned FIPS lengths, an embedded null, combining marks, and a supplementary- | |
manifest/ | Signed manifests: valid, tampered signature, mismatched embedded key, duplicate tool name, unsupported schema, and empty tools. | |
receipt/v1.json | 14 | Allow and deny receipts, a wrong parameter hash, a tampered signature, and the trace and advisory receipt kinds. |
signing/v1.json | Ed25519 over UTF-8 messages and over canonical JSON, across several seeds and at 64-byte and 128-byte block boundaries. |
Cross-language verification requires RFC 8785 canonical JSON. Every binding implements key sorting, whitespace removal, ECMAScript number formatting, and the specified handling of -0, infinities, and lone surrogates. chio conformance run runs the corpus against a peer adapter and compares every result with the Rust reference. Fetch the published peer, or pass your own binding with --peer-binary.
chio conformance fetch-peers --language python
chio conformance run --peer pythonpeer python ./.chio-peers/python/chio-conformance-peer canonical pass capability pass eval pass hashing pass manifest pass receipt pass signing pass conformant python matches the Rust reference on every vector
Every Chio release also runs each SDK's own suite against the corpus before it is tagged; the source tests list them.
Change rules
The crate doc comment fixes the boundary: session runtime, transport, auth, and callback orchestration stay in the language-native SDKs. Three rules follow from it.
- A new public entrypoint carries an owning use case, a unit test, or both.
- A change that widens scope into transport or runtime behavior is rejected by default.
- An SDK consumes helpers through this facade.
chio-coreandchio-manifestare declared inCargo.tomlso the facade can reach them; a binding reads the facade.
Versioning
Both crates take the workspace version, 0.1.0, and set publish = false, so an SDK reaches them through a path dependency inside the workspace. The C ABI carries a second number: CHIO_FFI_ABI_VERSION, returned by chio_ffi_abi_version and repeated in the chio_ffi_build_info JSON, which a caller reads to confirm the struct layout it compiled against.
Implementation sources
The Rust helper source defines the contract in src/canonical.rs, src/hashing.rs, src/signing.rs, src/receipt.rs, src/capability.rs, src/manifest.rs, and src/error.rs, where ErrorCode carries serde(rename_all = "snake_case").
The C ABI implementation is crates/sdk/chio-bindings-ffi/src/lib.rs and its Cargo.toml. The conformance corpus is tests/bindings/vectors/.