Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C/C++ FFI Bridge

edgesentry-bridge is a separate Rust crate that exposes Ed25519 signing and BLAKE3 hash-chain verification as a stable C ABI. C and C++ firmware or gateways can call the same security logic as the Rust library without a full rewrite.


Building the library

cargo build -p edgesentry-bridge --release

This produces:

PlatformFile
macOStarget/release/libedgesentry_bridge.dylib and .a
Linuxtarget/release/libedgesentry_bridge.so and .a

The header crates/edgesentry-bridge/include/edgesentry_bridge.h is regenerated automatically by build.rs using cbindgen.


Linking from C/C++

macOS:

cc -o my_app main.c \
   -I path/to/edgesentry-bridge/include \
   -L path/to/target/release \
   -ledgesentry_bridge \
   -framework Security -framework CoreFoundation

Linux:

cc -o my_app main.c \
   -I path/to/edgesentry-bridge/include \
   -L path/to/target/release \
   -ledgesentry_bridge \
   -lpthread -ldl

A ready-made Makefile is provided in crates/edgesentry-bridge/examples/c_integration/.


API reference

Error codes

ConstantValueMeaning
EDS_OK0Success
EDS_ERR_NULL_PTR-1A required pointer was NULL
EDS_ERR_INVALID_UTF8-2String argument is not valid UTF-8
EDS_ERR_INVALID_KEY-3Key or hash buffer is invalid
EDS_ERR_STRING_TOO_LONG-4String exceeds fixed buffer size
EDS_ERR_CHAIN_INVALID-5Hash-chain verification failed
EDS_ERR_PANIC-6Unexpected internal error
EDS_ERR_HASH_MISMATCH-7Payload hash does not match expected value
EDS_ERR_BAD_SIGNATURE-8Ed25519 signature is invalid

After any call that returns a negative error code, call eds_last_error_message() to retrieve a human-readable description of the failure.

Record struct

typedef struct {
    uint64_t sequence;           /* monotonic record index (starts at 1) */
    uint64_t timestamp_ms;       /* Unix epoch in milliseconds           */
    uint8_t  payload_hash[32];   /* BLAKE3 hash of the raw payload        */
    uint8_t  signature[64];      /* Ed25519 signature over payload_hash   */
    uint8_t  prev_record_hash[32]; /* hash of preceding record (zero for first) */
    uint8_t  device_id[256];     /* null-terminated device identifier     */
    uint8_t  object_ref[512];    /* null-terminated storage reference     */
} EdsAuditRecord;

EdsAuditRecord is caller-allocated. Rust never calls malloc or returns a heap pointer — no _free function is needed.

Functions

/* Generate an Ed25519 keypair via OS CSPRNG.
   private_key_out and public_key_out must each point to 32 bytes. */
int32_t eds_keygen(uint8_t *private_key_out, uint8_t *public_key_out);

/* Hash payload with BLAKE3, sign with Ed25519, fill *out.
   Pass NULL for prev_record_hash to use the zero hash (first record). */
int32_t eds_sign_record(const char    *device_id,
                        uint64_t       sequence,
                        uint64_t       timestamp_ms,
                        const uint8_t *payload,
                        size_t         payload_len,
                        const uint8_t *prev_record_hash,
                        const char    *object_ref,
                        const uint8_t *private_key,
                        EdsAuditRecord *out);

/* Compute the per-record hash (used as prev_record_hash for the next record).
   hash_out must point to 32 bytes. */
int32_t eds_record_hash(const EdsAuditRecord *record, uint8_t *hash_out);

/* Verify Ed25519 signature. Returns 1 valid, 0 invalid, negative on error. */
int32_t eds_verify_record(const EdsAuditRecord *record,
                          const uint8_t *public_key);

/* Verify the entire hash chain. Returns EDS_OK or EDS_ERR_CHAIN_INVALID. */
int32_t eds_verify_chain(const EdsAuditRecord *records, size_t count);

/* Verify a software update before installation (CLS-03 / STAR-2 R2.2).
   Checks BLAKE3(payload) == payload_hash, then verifies the Ed25519
   publisher signature over payload_hash.
   payload_hash must point to 32 bytes; signature to 64 bytes;
   publisher_key to 32 bytes.
   Returns EDS_OK, EDS_ERR_HASH_MISMATCH, EDS_ERR_BAD_SIGNATURE, or
   EDS_ERR_INVALID_KEY / EDS_ERR_NULL_PTR on bad inputs. */
int32_t eds_verify_update(const uint8_t *payload,
                          size_t         payload_len,
                          const uint8_t *payload_hash,
                          const uint8_t *signature,
                          const uint8_t *publisher_key);

/* Return a thread-local human-readable description of the last error.
   The pointer is valid until the next eds_* call on this thread.
   Returns "" when no error has occurred.  Never returns NULL. */
const char *eds_last_error_message(void);

Minimal C example

#include "edgesentry_bridge.h"
#include <string.h>
#include <assert.h>

int main(void) {
    uint8_t priv_key[32], pub_key[32];
    if (eds_keygen(priv_key, pub_key) != EDS_OK) {
        fprintf(stderr, "keygen failed: %s\n", eds_last_error_message());
        return 1;
    }

    const char *payload = "check=door,status=ok";
    EdsAuditRecord rec;
    memset(&rec, 0, sizeof(rec));

    int rc = eds_sign_record("lift-01", 1, 1700000000000ULL,
                             (const uint8_t *)payload, strlen(payload),
                             NULL,              /* zero hash — first record */
                             "lift-01/1.bin",
                             priv_key, &rec);
    if (rc != EDS_OK) {
        fprintf(stderr, "sign_record failed: %s\n", eds_last_error_message());
        return 1;
    }

    assert(eds_verify_record(&rec, pub_key) == 1);
    return 0;
}

See the full example in crates/edgesentry-bridge/examples/c_integration/main.c.


Memory safety conventions

RuleDetail
No heap allocationEdsAuditRecord is caller-allocated; Rust never calls malloc
NULL-checkedEvery pointer argument is checked; EDS_ERR_NULL_PTR returned on failure
Fixed-size stringsdevice_id max 255 chars; object_ref max 511 chars — truncated inputs return EDS_ERR_STRING_TOO_LONG
Panic safetystd::panic::catch_unwind wraps every FFI function; a Rust panic returns EDS_ERR_PANIC instead of unwinding across the C boundary
Key sizesprivate_key and public_key must point to exactly 32 bytes; hash buffers to 32 bytes; signature buffer to 64 bytes

HSM path

For CLS Level 4, the private key should never exist as an extractable byte array. The planned HSM integration (#54) will delegate the eds_sign_record operation to an HSM-backed provider without exposing key bytes to the caller.