Repository: openwsn-berkeley/lakers (language: python)

Issue:
Re-enable C API
From version [0.2.0](https://github.com/openwsn-berkeley/lakers/releases/tag/REL-0.2.0) to [0.3.0](https://github.com/openwsn-berkeley/lakers/releases/tag/v0.3.0) the library had support for being compiled as a static library + automatic generation of C headers, enabling seamless integration with C projects. From [0.4.0](https://github.com/openwsn-berkeley/lakers/releases/tag/v0.4.0) onwards, however, the API changed, including typestatification and (currently ongoing) split message preparation/processing. 

While things were being moved around, it made sense to temporarily remove the C API. This issue aims at re-enabling it, so that `lakers` can also be made available for the embedded C world.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 792f47d3..1ac3c2fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ cscope*
 .DS_Store
 .vscode
 *.fst
+
+bin
diff --git a/Cargo.toml b/Cargo.toml
index 1493b3fc..cbab365b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ members = [
   "examples/coap",
   "examples/edhoc-rs-no_std",
   "examples/edhoc-rs-cc2538",
+  "lakers-c",
 ]
 
 # reduced "default-members", should include only packages that can be built and
diff --git a/build_for_c.sh b/build_for_c.sh
deleted file mode 100755
index 8117fbd7..00000000
--- a/build_for_c.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# This script builds the static library for a cortex-m4 target
-# It also generates the header files for the c wrapper
-# The script takes one argument: the cargo feature to use
-
-# This script should not be necessary, it exists due to several flags clashing and preventing CI from passing.
-# The main reason being that the build behaves differently then crate-type is set to staticlib,
-# for example, running tests required a panic handler and an "eh_personality" function, but then
-# this would clash when building the static library or the no_std example.
-
-cargo_features=$1
-
-if [[ $cargo_features != "crypto-cryptocell310" && $cargo_features != "crypto-psa-baremetal" ]]; then
-    echo "Select one of: crypto-cryptocell310, crypto-psa-baremetal"
-    echo "Example: ./build_static_lib.sh crypto-cryptocell310"
-    exit 1
-fi
-
-original_value=`grep crate-type lib/Cargo.toml`
-
-new_value='crate-type = ["staticlib"]'
-echo "Changing crate-type to:   $new_value"
-sed -i -E "s/crate-type.*/$new_value/" lib/Cargo.toml
-
-# generate the static library
-cargo build --target thumbv7em-none-eabihf --package edhoc-rs --package lakers-crypto --package lakers-ead  --features="$cargo_features" --release
-
-# generate the headers
-cbindgen --config consts/cbindgen.toml --crate lakers-shared --output ./target/include/lakers_shared.h -v
-cbindgen --config lib/cbindgen.toml --crate edhoc-rs --output ./target/include/edhoc_rs.h -v
-
-# zip to a single file
-cd target
-zip -r staticlib-"$cargo_features"-thumbv7em-none-eabihf.zip include/
-zip -u -j staticlib-"$cargo_features"-thumbv7em-none-eabihf.zip thumbv7em-none-eabihf/release/libedhoc_rs.a
-cd -
-
-echo "Reverting crate-type to original value:   $original_value"
-sed -i -E "s/crate-type.*/$original_value/" lib/Cargo.toml
diff --git a/ead/lakers-ead-authz/cbindgen.toml b/ead/lakers-ead-authz/cbindgen.toml
new file mode 100644
index 00000000..5d1d638b
--- /dev/null
+++ b/ead/lakers-ead-authz/cbindgen.toml
@@ -0,0 +1,16 @@
+# call with:
+# cbindgen --config ead/lakers-ead-authz/cbindgen.toml --crate lakers-ead-authz --output target/include/lakers_ead_authz.h -vv
+
+language = "C"
+header = """
+/*
+ * ================================================================================================
+ *  WARNING: This file is automatically generated by cbindgen. Manual edits are likely to be lost.
+ * ================================================================================================
+ */"""
+include_guard = "LAKERS_EAD_AUTHZ_H"
+
+[export]
+include = [
+    "ZeroTouchDevice", "ZeroTouchDeviceWaitEAD2", "ZeroTouchDeviceDone"
+]
diff --git a/ead/lakers-ead-authz/src/device.rs b/ead/lakers-ead-authz/src/device.rs
index 664d2e6f..c5de277f 100644
--- a/ead/lakers-ead-authz/src/device.rs
+++ b/ead/lakers-ead-authz/src/device.rs
@@ -2,6 +2,7 @@ use super::shared::*;
 use lakers_shared::{Crypto as CryptoTrait, *};
 
 #[derive(Debug)]
+#[repr(C)]
 pub struct ZeroTouchDevice {
     pub(crate) id_u: EdhocMessageBuffer, // identifier of the device (U), equivalent to ID_CRED_I in EDHOC
     pub g_w: BytesP256ElemLen,           // public key of the enrollment server (W)
@@ -9,12 +10,14 @@ pub struct ZeroTouchDevice {
 }
 
 #[derive(Debug)]
+#[repr(C)]
 pub struct ZeroTouchDeviceWaitEAD2 {
     prk: BytesHashLen,
     pub h_message_1: BytesHashLen,
 }
 
 #[derive(Debug)]
+#[repr(C)]
 pub struct ZeroTouchDeviceDone {
     pub voucher: BytesMac,
 }
diff --git a/ead/lakers-ead-authz/src/lib.rs b/ead/lakers-ead-authz/src/lib.rs
index 96c19f09..9434e24d 100644
--- a/ead/lakers-ead-authz/src/lib.rs
+++ b/ead/lakers-ead-authz/src/lib.rs
@@ -8,7 +8,7 @@ mod shared;
 mod test_vectors;
 
 pub use authenticator::ZeroTouchAuthenticator;
-pub use device::ZeroTouchDevice;
+pub use device::{ZeroTouchDevice, ZeroTouchDeviceDone, ZeroTouchDeviceWaitEAD2};
 pub use server::ZeroTouchServer;
 
 #[cfg(test)]
diff --git a/examples/coap/Cargo.toml b/examples/coap/Cargo.toml
index be560a83..f6dbe433 100644
--- a/examples/coap/Cargo.toml
+++ b/examples/coap/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
-edhoc-rs = { package = "lakers", path = "../../lib" }
+edhoc-rs = { package = "lakers", path = "../../lib", features = ["ead-authz"] }
 lakers-crypto = { path = "../../crypto/", features = [ "hacspec" ] }
 hexlit = "0.5.3"
 coap = { version = "0.13" }
diff --git a/examples/coap/src/bin/coapserver.rs b/examples/coap/src/bin/coapserver.rs
index 7e3994ca..57af48be 100644
--- a/examples/coap/src/bin/coapserver.rs
+++ b/examples/coap/src/bin/coapserver.rs
@@ -3,21 +3,29 @@ use edhoc_rs::*;
 use hexlit::hex;
 use std::net::UdpSocket;
 
-const _ID_CRED_I: &[u8] = &hex!("a104412b");
+const ID_CRED_I: &[u8] = &hex!("a104412b");
 const ID_CRED_R: &[u8] = &hex!("a104410a");
 const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
-const _G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6");
-const _G_I_Y_COORD: &[u8] =
-    &hex!("6e5de611388a4b8a8211334ac7d37ecb52a387d257e6db3c2a93df21ff3affc8");
 const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072");
 const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac");
 
+// ead authz
+const W_TV: &[u8] = &hex!("4E5E15AB35008C15B89E91F9F329164D4AACD53D9923672CE0019F9ACD98573F");
+
 fn main() {
-    let mut buf = [0; 100];
+    let mut buf = [0; MAX_MESSAGE_SIZE_LEN];
     let socket = UdpSocket::bind("127.0.0.1:5683").unwrap();
 
     let mut edhoc_connections = Vec::new();
 
+    // ead authz server (W)
+    let acl = EdhocMessageBuffer::new_from_slice(&[ID_CRED_I[3]]).unwrap(); // [kid]
+    let server = ZeroTouchServer::new(
+        W_TV.try_into().unwrap(),
+        CRED_R.try_into().unwrap(),
+        Some(acl),
+    );
+
     println!("Waiting for CoAP messages...");
     loop {
         let (size, src) = socket.recv_from(&mut buf).expect("Didn't receive data");
@@ -34,21 +42,42 @@ fn main() {
                 let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap();
                 let responder = EdhocResponder::new(lakers_crypto::default_crypto(), &R, cred_r);
 
-                let result = responder.process_message_1(
-                    &request.message.payload[1..]
-                        .try_into()
-                        .expect("wrong length"),
-                );
+                let message_1: EdhocMessageBuffer = request.message.payload[1..]
+                    .try_into()
+                    .expect("wrong length");
+                let result = responder.process_message_1(&message_1);
 
-                if let Ok((responder, _ead_1)) = result {
+                if let Ok((responder, ead_1)) = result {
                     let c_r =
                         generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto());
+                    let ead_2 = if let Some(ead_1) = ead_1 {
+                        let authenticator = ZeroTouchAuthenticator::default();
+                        let (authenticator, _loc_w, voucher_request) =
+                            authenticator.process_ead_1(&ead_1, &message_1).unwrap();
+
+                        // mock a request to the server
+                        let voucher_response = server
+                            .handle_voucher_request(
+                                &mut lakers_crypto::default_crypto(),
+                                &voucher_request,
+                            )
+                            .unwrap();
+
+                        let res = authenticator.prepare_ead_2(&voucher_response);
+                        assert!(res.is_ok());
+                        authenticator.prepare_ead_2(&voucher_response).ok()
+                    } else {
+                        None
+                    };
                     let (responder, message_2) = responder
-                        .prepare_message_2(CredentialTransfer::ByReference, Some(c_r), &None)
+                        .prepare_message_2(CredentialTransfer::ByReference, Some(c_r), &ead_2)
                         .unwrap();
                     response.message.payload = Vec::from(message_2.as_slice());
                     // save edhoc connection
                     edhoc_connections.push((c_r, responder));
+                } else {
+                    println!("msg1 err");
+                    response.set_status(ResponseType::BadRequest);
                 }
             } else {
                 // potentially message 3
@@ -62,7 +91,7 @@ fn main() {
                     EdhocMessageBuffer::new_from_slice(&request.message.payload[1..]).unwrap();
                 let Ok((responder, id_cred_i, _ead_3)) = responder.parse_message_3(&message_3)
                 else {
-                    println!("EDHOC processing error: {:?}", message_3);
+                    println!("EDHOC error at parse_message_3: {:?}", message_3);
                     // We don't get another chance, it's popped and can't be used any further
                     // anyway legally
                     continue;
@@ -70,7 +99,7 @@ fn main() {
                 let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap();
                 let valid_cred_i = credential_check_or_fetch(Some(cred_i), id_cred_i).unwrap();
                 let Ok((mut responder, prk_out)) = responder.verify_message_3(valid_cred_i) else {
-                    println!("EDHOC processing error: {:?}", valid_cred_i);
+                    println!("EDHOC error at verify_message_3: {:?}", valid_cred_i);
                     continue;
                 };
 
diff --git a/examples/lakers-c-native/.gitignore b/examples/lakers-c-native/.gitignore
new file mode 100644
index 00000000..9643b44a
--- /dev/null
+++ b/examples/lakers-c-native/.gitignore
@@ -0,0 +1,3 @@
+*.o
+lakers_ffi_native
+lakers_c_native
diff --git a/examples/lakers-c-native/Makefile b/examples/lakers-c-native/Makefile
new file mode 100644
index 00000000..69e2c91d
--- /dev/null
+++ b/examples/lakers-c-native/Makefile
@@ -0,0 +1,32 @@
+CC=gcc
+CFLAGS=-Wall -I../../target/include
+
+TARGET=lakers_c_native
+
+SRC=main.c
+
+OBJ=$(SRC:.c=.o)
+
+HOST_TRIPLE := $(shell rustc -vV | sed -n 's|host: ||p')
+
+LIB_DIR=../../target/$(HOST_TRIPLE)/release/
+LIB_NAME=lakers_c
+
+ifeq ($(LAKERS_EAD), authz)
+	CFLAGS += -DLAKERS_EAD_AUTHZ
+endif
+
+all: $(TARGET)
+
+# rule for building the target executable
+$(TARGET): $(OBJ)
+	$(CC) $(CFLAGS) -ggdb -O0 -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) -lcoap-3
+
+# rule for building object files
+%.o: %.c
+	$(CC) $(CFLAGS) -ggdb -c $<
+
+clean:
+	rm -f $(OBJ) $(TARGET)
+
+.PHONY: all clean
diff --git a/examples/lakers-c-native/README.md b/examples/lakers-c-native/README.md
new file mode 100644
index 00000000..5f94b8c7
--- /dev/null
+++ b/examples/lakers-c-native/README.md
@@ -0,0 +1,19 @@
+# Lakers C Native
+
+Example of `lakers` in C for non-embedded targets.
+
+Compile and run:
+```
+make && ./lakers_c_native
+```
+
+Compile and run, leveraging the [lake-authz draft](https://www.ietf.org/archive/id/draft-ietf-lake-authz-00.html) for zero-touch device enrollment:
+```
+make LAKERS_EAD=authz && ./lakers_c_native
+```
+
+# Requirements
+
+- See the README in the `lakers-c` crate.
+- Install [libcoap](https://libcoap.net/install.html):
+  - tested with the following configuration: `./configure --disable-doxygen --disable-manpages --disable-dtls --disable-oscore`
diff --git a/examples/lakers-c-native/main.c b/examples/lakers-c-native/main.c
new file mode 100644
index 00000000..0e2e405d
--- /dev/null
+++ b/examples/lakers-c-native/main.c
@@ -0,0 +1,204 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include "lakers_shared.h"
+#include "lakers_ead_authz.h"
+#include "lakers.h"
+#include <coap3/coap.h>
+#include <arpa/inet.h>
+
+#ifdef LAKERS_EAD_AUTHZ
+static const uint8_t ID_U[] = {0xa1, 0x04, 0x41, 0x2b};
+static const uint8_t ID_U_LEN = sizeof(ID_U) / sizeof(ID_U[0]);
+static const BytesP256ElemLen G_W = {0xFF, 0xA4, 0xF1, 0x02, 0x13, 0x40, 0x29, 0xB3, 0xB1, 0x56, 0x89, 0x0B, 0x88, 0xC9, 0xD9, 0x61, 0x95, 0x01, 0x19, 0x65, 0x74, 0x17, 0x4D, 0xCB, 0x68, 0xA0, 0x7D, 0xB0, 0x58, 0x8E, 0x4D, 0x41};
+static const uint8_t LOC_W[] = {0x63, 0x6F, 0x61, 0x70, 0x3A, 0x2F, 0x2F, 0x65, 0x6E, 0x72, 0x6F, 0x6C, 0x6C, 0x6D, 0x65, 0x6E, 0x74, 0x2E, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72};
+static const uint8_t LOC_W_LEN = sizeof(LOC_W) / sizeof(LOC_W[0]);
+static const uint8_t SS = 2;
+#endif
+
+static const uint8_t CRED_I[] = {0xA2, 0x02, 0x77, 0x34, 0x32, 0x2D, 0x35, 0x30, 0x2D, 0x33, 0x31, 0x2D, 0x46, 0x46, 0x2D, 0x45, 0x46, 0x2D, 0x33, 0x37, 0x2D, 0x33, 0x32, 0x2D, 0x33, 0x39, 0x08, 0xA1, 0x01, 0xA5, 0x01, 0x02, 0x02, 0x41, 0x2B, 0x20, 0x01, 0x21, 0x58, 0x20, 0xAC, 0x75, 0xE9, 0xEC, 0xE3, 0xE5, 0x0B, 0xFC, 0x8E, 0xD6, 0x03, 0x99, 0x88, 0x95, 0x22, 0x40, 0x5C, 0x47, 0xBF, 0x16, 0xDF, 0x96, 0x66, 0x0A, 0x41, 0x29, 0x8C, 0xB4, 0x30, 0x7F, 0x7E, 0xB6, 0x22, 0x58, 0x20, 0x6E, 0x5D, 0xE6, 0x11, 0x38, 0x8A, 0x4B, 0x8A, 0x82, 0x11, 0x33, 0x4A, 0xC7, 0xD3, 0x7E, 0xCB, 0x52, 0xA3, 0x87, 0xD2, 0x57, 0xE6, 0xDB, 0x3C, 0x2A, 0x93, 0xDF, 0x21, 0xFF, 0x3A, 0xFF, 0xC8};
+static const uint8_t CRED_R[] = {0xA2, 0x02, 0x60, 0x08, 0xA1, 0x01, 0xA5, 0x01, 0x02, 0x02, 0x41, 0x0A, 0x20, 0x01, 0x21, 0x58, 0x20, 0xBB, 0xC3, 0x49, 0x60, 0x52, 0x6E, 0xA4, 0xD3, 0x2E, 0x94, 0x0C, 0xAD, 0x2A, 0x23, 0x41, 0x48, 0xDD, 0xC2, 0x17, 0x91, 0xA1, 0x2A, 0xFB, 0xCB, 0xAC, 0x93, 0x62, 0x20, 0x46, 0xDD, 0x44, 0xF0, 0x22, 0x58, 0x20, 0x45, 0x19, 0xE2, 0x57, 0x23, 0x6B, 0x2A, 0x0C, 0xE2, 0x02, 0x3F, 0x09, 0x31, 0xF1, 0xF3, 0x86, 0xCA, 0x7A, 0xFD, 0xA6, 0x4F, 0xCD, 0xE0, 0x10, 0x8C, 0x22, 0x4C, 0x51, 0xEA, 0xBF, 0x60, 0x72};
+static const BytesP256ElemLen I = {0xfb, 0x13, 0xad, 0xeb, 0x65, 0x18, 0xce, 0xe5, 0xf8, 0x84, 0x17, 0x66, 0x08, 0x41, 0x14, 0x2e, 0x83, 0x0a, 0x81, 0xfe, 0x33, 0x43, 0x80, 0xa9, 0x53, 0x40, 0x6a, 0x13, 0x05, 0xe8, 0x70, 0x6b};
+
+static coap_context_t *ctx = NULL;
+static coap_session_t *session = NULL;
+static int has_coap_response = 0;
+static uint8_t coap_response_payload[MAX_MESSAGE_SIZE_LEN];
+static size_t coap_response_payload_len;
+
+void print_hex(uint8_t *arr, size_t len)
+{
+    printf("%ld bytes: ", len);
+    for (int i = 0; i < len; i++) {
+        printf("%02X", arr[i]);
+    }
+    printf("\n");
+}
+
+static coap_response_t message_handler(coap_session_t *session COAP_UNUSED,
+                                       const coap_pdu_t *sent,
+                                       const coap_pdu_t *received,
+                                       const coap_mid_t id COAP_UNUSED)
+{
+    has_coap_response = 1;
+    // coap_show_pdu(COAP_LOG_WARN, received);
+    const uint8_t *data;
+    coap_get_data(received, &coap_response_payload_len, &data);
+    memcpy(coap_response_payload, data, coap_response_payload_len);
+    puts("received coap response");
+    print_hex((uint8_t *)coap_response_payload, coap_response_payload_len);
+    return COAP_RESPONSE_OK;
+}
+
+int coap_send_edhoc_message(uint8_t *edhoc_msg, size_t edhoc_msg_len, uint8_t value_to_prepend)
+{
+    printf("sending coap message of size %zu+1\n", edhoc_msg_len);
+    coap_pdu_t *pdu = coap_pdu_init(COAP_MESSAGE_CON,
+                                    COAP_REQUEST_CODE_GET,
+                                    coap_new_message_id(session),
+                                    coap_session_max_pdu_size(session));
+    coap_add_option(pdu, COAP_OPTION_URI_PATH, 17, (const uint8_t *)".well-known/edhoc");
+    uint8_t payload[MAX_MESSAGE_SIZE_LEN];
+    payload[0] = value_to_prepend;
+    memcpy(payload + 1, edhoc_msg, edhoc_msg_len);
+    print_hex(payload, edhoc_msg_len+1);
+    coap_add_data(pdu, edhoc_msg_len + 1, payload);
+    // coap_show_pdu(COAP_LOG_WARN, pdu);
+    if (coap_send(session, pdu) == COAP_INVALID_MID)
+    {
+        coap_log_err("cannot send CoAP pdu\n");
+        return -1;
+    }
+    while (has_coap_response == 0) {
+        coap_io_process(ctx, COAP_IO_WAIT);
+    }
+    has_coap_response = 0;
+
+    return 0;
+}
+
+int main(void)
+{
+    printf("Calling lakers from C!\n");
+
+    // coap init
+    coap_address_t dst;
+    coap_startup();
+    coap_set_log_level(COAP_LOG_WARN);
+    coap_address_init(&dst);
+    dst.addr.sin.sin_family = AF_INET;
+    dst.addr.sin.sin_port = htons(5683);
+    dst.size = sizeof(dst.addr.sin);
+    if (inet_pton(AF_INET, "127.0.0.1", &dst.addr.sin.sin_addr) <= 0) {
+        printf("Error converting the IP address\n");
+        return -1;
+    }
+    if (!(ctx = coap_new_context(NULL)))
+    {
+        coap_log_emerg("cannot create libcoap context\n");
+        goto finish;
+    }
+    if (!(session = coap_new_client_session(ctx, NULL, &dst,
+                                            COAP_PROTO_UDP)))
+    {
+        coap_log_emerg("cannot create client session\n");
+        goto finish;
+    }
+    coap_register_response_handler(ctx, message_handler);
+
+    // lakers init
+    puts("loading credentials.");
+    CredentialRPK cred_i = {0}, cred_r = {0};
+    credential_rpk_new(CRED_I, 107, &cred_i);
+    credential_rpk_new(CRED_R, 84, &cred_r);
+    puts("creating edhoc initiator.");
+    EdhocInitiatorC initiator = initiator_new();
+
+#ifdef LAKERS_EAD_AUTHZ
+    puts("creating ead-authz device.");
+    ZeroTouchDevice device = authz_device_new(ID_U, ID_U_LEN, &G_W, LOC_W, LOC_W_LEN);
+    puts("computing authz_secret.");
+    BytesP256ElemLen authz_secret;
+    initiator_compute_ephemeral_secret(&initiator, &G_W, &authz_secret);
+    puts("computing ead_1.");
+    ZeroTouchDeviceWaitEAD2 device_wait;
+    EADItemC ead_1;
+    authz_device_prepare_ead_1(&device, &authz_secret, SS, &device_wait, &ead_1);
+    print_hex(ead_1.value.content, ead_1.value.len);
+#endif
+
+    puts("Begin test: edhoc initiator.");
+    EdhocMessageBuffer message_1;
+    EdhocInitiatorWaitM2C initiator_wait_m2;
+#ifdef LAKERS_EAD_AUTHZ
+    int res = initiator_prepare_message_1(&initiator, NULL, &ead_1, &initiator_wait_m2, &message_1);
+    memcpy(device_wait.h_message_1, initiator_wait_m2.state.h_message_1, SHA256_DIGEST_LEN);
+#else
+    int res = initiator_prepare_message_1(&initiator, NULL, NULL, &initiator_wait_m2, &message_1);
+#endif
+    if (res != 0) {
+        printf("Error prep msg1: %d\n", res);
+        return 1;
+    }
+    print_hex(message_1.content, message_1.len);
+
+    puts("sending msg1");
+    coap_send_edhoc_message(message_1.content, message_1.len, 0xf5);
+
+    puts("processing msg2");
+    EdhocMessageBuffer message_2 = {.len = coap_response_payload_len};
+    memcpy(message_2.content, coap_response_payload, coap_response_payload_len);
+    EdhocInitiatorProcessingM2C initiator_processing_m2;
+    uint8_t c_r;
+    CredentialRPK fetched_cred_r = {0};
+#ifdef LAKERS_EAD_AUTHZ
+    EADItemC ead_2;
+    res = initiator_parse_message_2(&initiator_wait_m2, &message_2, cred_r, &initiator_processing_m2, &c_r, &fetched_cred_r, &ead_2);
+#else
+    res = initiator_parse_message_2(&initiator_wait_m2, &message_2, cred_r, &initiator_processing_m2, &c_r, &fetched_cred_r, NULL);
+#endif
+    if (res != 0) {
+        printf("Error parse msg2: %d\n", res);
+        return 1;
+    }
+#ifdef LAKERS_EAD_AUTHZ
+    puts("processing ead2");
+    ZeroTouchDeviceDone device_done;
+    res = authz_device_process_ead_2(&device_wait, &ead_2, cred_r, &device_done);
+    if (res != 0) {
+        printf("Error process ead2 (authz): %d\n", res);
+        return 1;
+    } else {
+        puts("ead-authz voucher received and validated");
+    }
+#endif
+    EdhocInitiatorProcessedM2C initiator_processed_m2;
+    initiator_verify_message_2(&initiator_processing_m2, &I, cred_i, fetched_cred_r, &initiator_processed_m2);
+    if (res != 0) {
+        printf("Error verify msg2: %d\n", res);
+        return 1;
+    }
+
+    puts("preparing msg3");
+    EdhocInitiatorDoneC initiator_done;
+    EdhocMessageBuffer message_3;
+    uint8_t prk_out[SHA256_DIGEST_LEN];
+    res = initiator_prepare_message_3(&initiator_processed_m2, ByReference, NULL, &initiator_done, &message_3, prk_out);
+    if (res != 0) {
+        printf("Error prep msg3: %d\n", res);
+        return 1;
+    }
+    print_hex(message_3.content, message_3.len);
+
+    puts("sending msg3");
+    coap_send_edhoc_message(message_3.content, message_3.len, c_r);
+
+    puts("All went good.");
+
+finish:
+    coap_session_release(session);
+    coap_free_context(ctx);
+    coap_cleanup();
+
+    return 0;
+}
diff --git a/examples/lakers-c-riot/.gitignore b/examples/lakers-c-riot/.gitignore
new file mode 100644
index 00000000..ba077a40
--- /dev/null
+++ b/examples/lakers-c-riot/.gitignore
@@ -0,0 +1,1 @@
+bin
diff --git a/examples/lakers-c-riot/Makefile b/examples/lakers-c-riot/Makefile
new file mode 100644
index 00000000..0de0b146
--- /dev/null
+++ b/examples/lakers-c-riot/Makefile
@@ -0,0 +1,33 @@
+# name of your application
+APPLICATION = lakers-c-riot
+
+# If no BOARD is found in the environment, use this default:
+BOARD ?= nrf52840dk
+
+# adjust these according to your needs
+INCLUDES += -I$(CURDIR)/../../target/include
+ARCHIVES += $(CURDIR)/../../target/thumbv7em-none-eabihf/release/liblakers_c.a
+
+ifeq ($(EDHOC_CRYPTO), CRYPTO_PSA)
+	CFLAGS += -D$(EDHOC_CRYPTO)
+else
+	CFLAGS += -DCRYPTO_CRYPTOCELL310
+endif
+
+# This is actually only needed in the CRYPTO_CRYPTOCELL310 configuration
+CFLAGS += -DTHREAD_STACKSIZE_DEFAULT=16384 -DISR_STACKSIZE=16384
+
+USEMODULE += od
+USEMODULE += ztimer
+USEMODULE += ztimer_msec
+USEMODULE += ps
+
+# This has to be the absolute path to the RIOT base directory:
+RIOTBASE ?= $(CURDIR)/../../../RIOT
+
+DEVELHELP ?= 1
+
+# flags for development only
+CFLAGS += -Wno-error=unused-variable -Wno-error=unused-const-variable -Wno-error=unused-function -Wno-error=unused-parameter -Wno-error=pedantic
+
+include $(RIOTBASE)/Makefile.include
diff --git a/examples/lakers-c-riot/README.md b/examples/lakers-c-riot/README.md
new file mode 100644
index 00000000..2931cef5
--- /dev/null
+++ b/examples/lakers-c-riot/README.md
@@ -0,0 +1,13 @@
+# Lakers C RIOT
+
+Basic example of `lakers` in C for embedded targets, using the RIOT operating system.
+
+Compile and run:
+```
+make flash term
+```
+
+# Requirements
+
+- See the README in the `lakers-c` crate.
+- [RIOT](https://github.com/RIOT-OS/RIOT)'s source code must be available in a local folder, which can be set via the `RIOTBASE` variable.
diff --git a/examples/lakers-c-riot/main.c b/examples/lakers-c-riot/main.c
new file mode 100644
index 00000000..4957329d
--- /dev/null
+++ b/examples/lakers-c-riot/main.c
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include <string.h>
+#include "od.h"
+#include "ztimer.h"
+#include "lakers.h"
+
+static const uint8_t ID_U[] = {0xa1, 0x04, 0x41, 0x2b};
+static const uint8_t ID_U_LEN = sizeof(ID_U) / sizeof(ID_U[0]);
+
+static const BytesP256ElemLen G_W = {0xFF, 0xA4, 0xF1, 0x02, 0x13, 0x40, 0x29, 0xB3, 0xB1, 0x56, 0x89, 0x0B, 0x88, 0xC9, 0xD9, 0x61, 0x95, 0x01, 0x19, 0x65, 0x74, 0x17, 0x4D, 0xCB, 0x68, 0xA0, 0x7D, 0xB0, 0x58, 0x8E, 0x4D, 0x41};
+
+static const uint8_t LOC_W[] = {0x6, 0x3, 0x6, 0xf, 0x6, 0x1, 0x7, 0x0, 0x3, 0xa, 0x2, 0xf, 0x2, 0xf, 0x6, 0x5, 0x6, 0xe, 0x7, 0x2, 0x6, 0xf, 0x6, 0xc, 0x6, 0xc, 0x6, 0xd, 0x6, 0x5, 0x6, 0xe, 0x7, 0x4, 0x2, 0xe, 0x7, 0x3, 0x6, 0x5, 0x7, 0x2, 0x7, 0x6, 0x6, 0x5, 0x7, 0x2};
+static const uint8_t LOC_W_LEN = sizeof(LOC_W) / sizeof(LOC_W[0]);
+
+static const uint8_t SS = 2;
+
+static const uint8_t CRED_I[] = {0xA2, 0x02, 0x77, 0x34, 0x32, 0x2D, 0x35, 0x30, 0x2D, 0x33, 0x31, 0x2D, 0x46, 0x46, 0x2D, 0x45, 0x46, 0x2D, 0x33, 0x37, 0x2D, 0x33, 0x32, 0x2D, 0x33, 0x39, 0x08, 0xA1, 0x01, 0xA5, 0x01, 0x02, 0x02, 0x41, 0x2B, 0x20, 0x01, 0x21, 0x58, 0x20, 0xAC, 0x75, 0xE9, 0xEC, 0xE3, 0xE5, 0x0B, 0xFC, 0x8E, 0xD6, 0x03, 0x99, 0x88, 0x95, 0x22, 0x40, 0x5C, 0x47, 0xBF, 0x16, 0xDF, 0x96, 0x66, 0x0A, 0x41, 0x29, 0x8C, 0xB4, 0x30, 0x7F, 0x7E, 0xB6, 0x22, 0x58, 0x20, 0x6E, 0x5D, 0xE6, 0x11, 0x38, 0x8A, 0x4B, 0x8A, 0x82, 0x11, 0x33, 0x4A, 0xC7, 0xD3, 0x7E, 0xCB, 0x52, 0xA3, 0x87, 0xD2, 0x57, 0xE6, 0xDB, 0x3C, 0x2A, 0x93, 0xDF, 0x21, 0xFF, 0x3A, 0xFF, 0xC8};
+static const uint8_t CRED_R[] = {0xA2, 0x02, 0x60, 0x08, 0xA1, 0x01, 0xA5, 0x01, 0x02, 0x02, 0x41, 0x0A, 0x20, 0x01, 0x21, 0x58, 0x20, 0xBB, 0xC3, 0x49, 0x60, 0x52, 0x6E, 0xA4, 0xD3, 0x2E, 0x94, 0x0C, 0xAD, 0x2A, 0x23, 0x41, 0x48, 0xDD, 0xC2, 0x17, 0x91, 0xA1, 0x2A, 0xFB, 0xCB, 0xAC, 0x93, 0x62, 0x20, 0x46, 0xDD, 0x44, 0xF0, 0x22, 0x58, 0x20, 0x45, 0x19, 0xE2, 0x57, 0x23, 0x6B, 0x2A, 0x0C, 0xE2, 0x02, 0x3F, 0x09, 0x31, 0xF1, 0xF3, 0x86, 0xCA, 0x7A, 0xFD, 0xA6, 0x4F, 0xCD, 0xE0, 0x10, 0x8C, 0x22, 0x4C, 0x51, 0xEA, 0xBF, 0x60, 0x72};
+// static const uint8_t I[] = {0xfb, 0x13, 0xad, 0xeb, 0x65, 0x18, 0xce, 0xe5, 0xf8, 0x84, 0x17, 0x66, 0x08, 0x41, 0x14, 0x2e, 0x83, 0x0a, 0x81, 0xfe, 0x33, 0x43, 0x80, 0xa9, 0x53, 0x40, 0x6a, 0x13, 0x05, 0xe8, 0x70, 0x6b};
+
+#ifdef CRYPTO_PSA
+extern void mbedtls_memory_buffer_alloc_init(uint8_t *buf, size_t len);
+#endif
+
+int main(void)
+ {
+    ztimer_sleep(ZTIMER_MSEC, 500); // wait for serial connection to become ready
+    puts("Calling lakers from C!");
+
+#ifdef CRYPTO_PSA
+    // memory buffer for mbedtls, required by crypto-psa-baremetal backend
+    uint8_t buffer[4096 * 2] = {0};
+    mbedtls_memory_buffer_alloc_init(buffer, 4096 * 2);
+#endif
+
+    puts("Begin test: generate key pair.");
+    uint8_t out_private_key[32] = {0};
+    uint8_t out_public_key[32] = {0};
+    p256_generate_key_pair_from_c(out_private_key, out_public_key);
+    puts("End test: generate key pair.");
+
+    puts("loading credentials.");
+    CredentialRPK cred_i = {0}, cred_r = {0};
+    credential_rpk_new(CRED_I, 107, &cred_i);
+    credential_rpk_new(CRED_R, 84, &cred_r);
+
+    puts("creating edhoc initiator.");
+    EdhocInitiatorC initiator = initiator_new();
+
+    puts("creating ead-authz device.");
+    ZeroTouchDevice device = authz_device_new(ID_U, ID_U_LEN, &G_W, LOC_W, LOC_W_LEN);
+
+    puts("computing authz_secret.");
+    BytesP256ElemLen authz_secret;
+    initiator_compute_ephemeral_secret(&initiator, &G_W, &authz_secret);
+    // od_hex_dump(authz_secret, 32, OD_WIDTH_DEFAULT);
+
+    puts("computing ead_1.");
+    ZeroTouchDeviceWaitEAD2 device_wait;
+    EADItemC ead_1;
+    authz_device_prepare_ead_1(&device, &authz_secret, SS, &device_wait, &ead_1);
+    od_hex_dump(ead_1.value.content, ead_1.value.len, OD_WIDTH_DEFAULT);
+
+    puts("Begin test: edhoc initiator.");
+    EdhocMessageBuffer message_1;
+    EdhocInitiatorWaitM2C initiator_wait_m2;
+    // int res = initiator_prepare_message_1(&initiator, NULL, NULL, &initiator_wait_m2, &message_1); // if no EAD is used
+    int res = initiator_prepare_message_1(&initiator, NULL, &ead_1, &initiator_wait_m2, &message_1);
+    od_hex_dump(message_1.content, message_1.len, OD_WIDTH_DEFAULT);
+    if (res != 0) printf("Error prep msg1: %d\n", res);
+
+    // TODO: continue the handshake. requires a communication stack to reach the responder.
+
+    puts("All went good.");
+    return 0;
+}
diff --git a/examples/traces-zeroconf.ipynb b/examples/traces-zeroconf.ipynb
index 8d9471da..23231262 100644
--- a/examples/traces-zeroconf.ipynb
+++ b/examples/traces-zeroconf.ipynb
@@ -15,7 +15,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 17,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -38,7 +38,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 23,
    "metadata": {},
    "outputs": [
     {
@@ -84,11 +84,18 @@
     "        elif type(v) == int:\n",
     "            if fmt == \"rust\":\n",
     "                print(f'const {prefix}{k.upper()}_TV: u8 = {v};')\n",
-    "            else:\n",
+    "            elif fmt == \"rust\":\n",
+    "                print(f'static const uint8_t {prefix}{k.upper()} = {v};')\n",
+    "            elif fmt == \"python\":\n",
     "                print(f'{prefix}{k:<8} = {v}')\n",
     "        else:\n",
     "            if fmt == \"rust\":\n",
     "                print(f'const {prefix}{k.upper()}_TV: &[u8] = &hex!(\"{v}\");')\n",
+    "            elif fmt == \"c\":\n",
+    "                pairs = [\"0x\"+\"\".join(v[e:e+2])+\", \" for e in range(0, len(v), 2)]\n",
+    "                c_values = \"\".join(pairs)[:-2]\n",
+    "                print(f'static const uint8_t {prefix}{k.upper()}[] = {{{c_values}}};')\n",
+    "                print(f'static const size_t {prefix}{k.upper()}_LEN = {len(pairs)};')\n",
     "            elif fmt == \"python\":\n",
     "                print(f'{prefix}{k:<8} = \"{v}\"')\n",
     "\n",
@@ -171,7 +178,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -180,7 +187,7 @@
      "text": [
       ".\n",
       "----------------------------------------------------------------------\n",
-      "Ran 1 test in 0.003s\n",
+      "Ran 1 test in 0.001s\n",
       "\n",
       "OK\n"
      ]
@@ -188,10 +195,10 @@
     {
      "data": {
       "text/plain": [
-       "<unittest.main.TestProgram at 0x1257a2e90>"
+       "<unittest.main.TestProgram at 0x7fa2ebfee860>"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -240,7 +247,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 15,
    "metadata": {},
    "outputs": [
     {
@@ -675,7 +682,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.11.5"
+   "version": "3.10.12"
   },
   "orig_nbformat": 4
  },
diff --git a/lakers-c/Cargo.toml b/lakers-c/Cargo.toml
new file mode 100644
index 00000000..cfe53834
--- /dev/null
+++ b/lakers-c/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "lakers-c"
+edition = "2021"
+version.workspace = true
+repository.workspace = true
+license.workspace = true
+readme.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+edhoc-rs = { package = "lakers", path = "../lib", default-features = false }
+lakers-crypto = { path = "../crypto", default-features = false }
+hexlit = "0.5.3"
+
+panic-semihosting = { version = "0.6.0", features = ["exit"] }
+embedded-alloc = "0.5.1"
+critical-section = { version = "1.1", features = ["std"], optional = true }
+
+[build-dependencies]
+cbindgen = "0.24.5"
+
+[features]
+default = [ "crypto-cryptocell310", "ead-none" ]
+crypto-rustcrypto = [ "lakers-crypto/rustcrypto", "critical-section" ]
+crypto-psa = [ "lakers-crypto/psa", "critical-section" ]
+crypto-psa-baremetal = [ "lakers-crypto/psa-baremetal" ]
+crypto-cryptocell310 = [ "lakers-crypto/cryptocell310" ]
+ead-none = [ "edhoc-rs/ead-none" ]
+ead-authz = [ "edhoc-rs/ead-authz" ]
+
+[lib]
+crate-type = ["staticlib"]
diff --git a/lakers-c/README.md b/lakers-c/README.md
new file mode 100644
index 00000000..d255c2a4
--- /dev/null
+++ b/lakers-c/README.md
@@ -0,0 +1,22 @@
+# Lakers C
+C bindings for the `lakers` crate.
+
+The `./build.sh` script will:
+1. build this library wrapper as a static library
+2. generate C headers
+3. zip all artifacts together
+
+The results will be available in the `target` folder.
+
+# Examples
+To build with the PSA backend, to run in the host:
+
+```bash
+./build.sh crypto-psa
+```
+
+To build with the CC310 backend, to run in an embedded device:
+
+```bash
+./build.sh crypto-cryptocell310
+```
diff --git a/lakers-c/build.sh b/lakers-c/build.sh
new file mode 100755
index 00000000..aee9a173
--- /dev/null
+++ b/lakers-c/build.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+set -e
+
+# This script builds the static library for a cortex-m4 target
+# It also generates the header files for the c wrapper
+# It takes one argument: the selected crypto backend
+
+feature_crypto=$1
+if [[ $feature_crypto != "crypto-cryptocell310" && $feature_crypto != "crypto-psa-baremetal" && $feature_crypto != "crypto-rustcrypto" ]]; then
+    echo "crypto should be one of: crypto-cryptocell310, crypto-psa-baremetal, crypto-rustcrypto"
+    echo "Example: ./build_static_lib.sh crypto-cryptocell310"
+    exit 1
+fi
+
+if [[ $feature_crypto == "crypto-cryptocell310" || $feature_crypto == "crypto-psa-baremetal" ]]; then
+    rust_target=thumbv7em-none-eabihf
+else # use host architecture
+    rust_target=`rustc -vV | sed -n 's|host: ||p'`
+fi
+
+# hardcoded for now
+feature_ead=ead-authz
+
+# build the static library
+cargo build --target="$rust_target" --no-default-features  --features="$feature_crypto, $feature_ead" --release
+# example just for testing:
+#   cargo build --target=thumbv7em-none-eabihf --no-default-features --features="crypto-cryptocell310, ead-authz" --release
+
+# generate the headers
+cbindgen --config ../shared/cbindgen.toml --crate lakers-shared --output ../target/include/lakers_shared.h -v
+cbindgen --config ../ead/lakers-ead-authz/cbindgen.toml --crate lakers-ead-authz --output ../target/include/lakers_ead_authz.h -v
+cbindgen --config ./cbindgen.toml --crate lakers-c --output ../target/include/lakers.h -v
+
+# # zip to a single file
+cd ../target
+zip -r lakers-c-"$feature_crypto"-"$rust_target".zip include/
+zip -u -j lakers-c-"$feature_crypto"-"$rust_target".zip "$rust_target"/release/liblakers_c.a
+cd -
diff --git a/lakers-c/cbindgen.toml b/lakers-c/cbindgen.toml
new file mode 100644
index 00000000..a90f737d
--- /dev/null
+++ b/lakers-c/cbindgen.toml
@@ -0,0 +1,15 @@
+# call with:
+# cbindgen --config lakers-c/cbindgen.toml --crate lakers-c --output target/include/lakers.h -vv
+
+language = "C"
+header = """
+/*
+ * ================================================================================================
+ *  WARNING: This file is automatically generated by cbindgen. Manual edits are likely to be lost.
+ * ================================================================================================
+ */"""
+include_guard = "LAKERS_C_H"
+includes = ["lakers_shared.h", "lakers_ead_authz.h"]
+
+[parse.expand]
+all_features = true
diff --git a/lakers-c/src/ead_authz.rs b/lakers-c/src/ead_authz.rs
new file mode 100644
index 00000000..0db47556
--- /dev/null
+++ b/lakers-c/src/ead_authz.rs
@@ -0,0 +1,59 @@
+use crate::*;
+use core::slice;
+use lakers_crypto::default_crypto;
+
+#[no_mangle]
+pub unsafe extern "C" fn authz_device_new(
+    id_u: *const u8,
+    id_u_len: usize,
+    g_w: *const BytesP256ElemLen,
+    loc_w: *const u8,
+    loc_w_len: usize,
+) -> ZeroTouchDevice {
+    ZeroTouchDevice::new(
+        EdhocMessageBuffer::new_from_slice(unsafe { slice::from_raw_parts(id_u, id_u_len) })
+            .expect("Wrong length"),
+        *g_w,
+        EdhocMessageBuffer::new_from_slice(unsafe { slice::from_raw_parts(loc_w, loc_w_len) })
+            .expect("Wrong length"),
+    )
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn authz_device_prepare_ead_1(
+    // input parans
+    device_c: *const ZeroTouchDevice,
+    secret: *const BytesP256ElemLen,
+    ss: u8,
+    // output parans
+    device_c_out: *mut ZeroTouchDeviceWaitEAD2,
+    ead_1_c_out: *mut EADItemC,
+) -> i8 {
+    let (device, ead_1) = (*device_c).prepare_ead_1(&mut default_crypto(), *secret, ss);
+    *device_c_out = device;
+    EADItemC::copy_into_c(ead_1, ead_1_c_out);
+
+    0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn authz_device_process_ead_2(
+    // input parans
+    device: *mut ZeroTouchDeviceWaitEAD2,
+    ead_2_c: *mut EADItemC,
+    cred_v: CredentialRPK,
+    // output parans
+    device_c_out: *mut ZeroTouchDeviceDone,
+) -> i8 {
+    match (*device).process_ead_2(
+        &mut default_crypto(),
+        (*ead_2_c).to_rust(),
+        cred_v.value.as_slice(),
+    ) {
+        Ok(device) => {
+            *device_c_out = device;
+            0
+        }
+        Err(_) => -1,
+    }
+}
diff --git a/lakers-c/src/initiator.rs b/lakers-c/src/initiator.rs
new file mode 100644
index 00000000..6117e73b
--- /dev/null
+++ b/lakers-c/src/initiator.rs
@@ -0,0 +1,208 @@
+use core::slice;
+
+use edhoc_rs::*;
+use lakers_crypto::{default_crypto, CryptoTrait};
+
+use crate::*;
+
+/// structs compatible with the C FFI
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct EdhocInitiatorC {
+    pub state: InitiatorStart,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct EdhocInitiatorWaitM2C {
+    pub state: WaitM2,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct EdhocInitiatorProcessingM2C {
+    pub state: ProcessingM2C,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct EdhocInitiatorProcessedM2C {
+    state: ProcessedM2,
+    cred_i: CredentialRPK,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct EdhocInitiatorDoneC {
+    state: Completed,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn initiator_new() -> EdhocInitiatorC {
+    // we only support a single cipher suite which is already CBOR-encoded
+    let mut suites_i: BytesSuites = [0x0; SUITES_LEN];
+    let suites_i_len = EDHOC_SUPPORTED_SUITES.len();
+    suites_i[0..suites_i_len].copy_from_slice(&EDHOC_SUPPORTED_SUITES[..]);
+    let (x, g_x) = default_crypto().p256_generate_key_pair();
+
+    EdhocInitiatorC {
+        state: InitiatorStart {
+            x,
+            g_x,
+            suites_i,
+            suites_i_len,
+        },
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn initiator_prepare_message_1(
+    initiator_c: *mut EdhocInitiatorC,
+    // input params
+    c_i: *mut u8,
+    ead_1_c: *mut EADItemC,
+    // output params
+    initiator_c_out: *mut EdhocInitiatorWaitM2C,
+    message_1: *mut EdhocMessageBuffer,
+) -> i8 {
+    let state = core::ptr::read(&(*initiator_c).state);
+
+    let c_i = if c_i.is_null() {
+        generate_connection_identifier_cbor(&mut default_crypto())
+    } else {
+        *c_i
+    };
+
+    let ead_1 = if ead_1_c.is_null() {
+        None
+    } else {
+        let ead_1 = (*ead_1_c).to_rust();
+        Some(ead_1)
+    };
+
+    let result = match i_prepare_message_1(state, &mut default_crypto(), c_i, &ead_1) {
+        Ok((state, msg_1)) => {
+            *message_1 = msg_1;
+            *initiator_c_out = EdhocInitiatorWaitM2C { state };
+            0
+        }
+        Err(err) => err as i8,
+    };
+
+    result
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn initiator_parse_message_2(
+    // input params
+    initiator_c: *mut EdhocInitiatorWaitM2C,
+    message_2: *const EdhocMessageBuffer,
+    expected_cred_r: CredentialRPK,
+    // output params
+    initiator_c_out: *mut EdhocInitiatorProcessingM2C,
+    c_r_out: *mut u8,
+    valid_cred_r_out: *mut CredentialRPK,
+    ead_2_c_out: *mut EADItemC,
+) -> i8 {
+    // manually take `state` because Rust cannot move out of a dereferenced raw pointer directly
+    // raw pointers do not have ownership information, requiring manual handling of the data
+    let state = core::ptr::read(&(*initiator_c).state);
+
+    let result = match i_parse_message_2(state, &mut default_crypto(), &(*message_2)) {
+        Ok((state, c_r, id_cred_r, ead_2)) => {
+            ProcessingM2C::copy_into_c(state, &mut (*initiator_c_out).state);
+            *c_r_out = c_r;
+
+            // NOTE: checking here to avoid having IdCredOwnedC being passed across the ffi boundary
+            let Ok(valid_cred_r) = credential_check_or_fetch(Some(expected_cred_r), id_cred_r)
+            else {
+                return -1;
+            };
+            *valid_cred_r_out = valid_cred_r;
+
+            if let Some(ead_2) = ead_2 {
+                EADItemC::copy_into_c(ead_2, ead_2_c_out);
+                (*initiator_c_out).state.ead_2 = ead_2_c_out;
+            }
+
+            0
+        }
+        Err(err) => err as i8,
+    };
+
+    result
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn initiator_verify_message_2(
+    // input params
+    initiator_c: *mut EdhocInitiatorProcessingM2C,
+    i: *const BytesP256ElemLen,
+    // i_len: usize,
+    cred_i: CredentialRPK,
+    valid_cred_r: CredentialRPK,
+    // output params
+    initiator_c_out: *mut EdhocInitiatorProcessedM2C,
+) -> i8 {
+    let state = core::ptr::read(&(*initiator_c).state).to_rust();
+
+    match i_verify_message_2(state, &mut default_crypto(), valid_cred_r, &(*i)) {
+        Ok(state) => {
+            *initiator_c_out = EdhocInitiatorProcessedM2C { state, cred_i };
+            0
+        }
+        Err(err) => err as i8,
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn initiator_prepare_message_3(
+    // input params
+    initiator_c: *mut EdhocInitiatorProcessedM2C,
+    cred_transfer: CredentialTransfer,
+    ead_3_c: *mut EADItemC,
+    // output params
+    initiator_c_out: *mut EdhocInitiatorDoneC,
+    message_3: *mut EdhocMessageBuffer,
+    prk_out_c: *mut [u8; SHA256_DIGEST_LEN],
+) -> i8 {
+    let mut state = core::ptr::read(&(*initiator_c).state);
+
+    let ead_3 = if ead_3_c.is_null() {
+        None
+    } else {
+        let ead_3 = (*ead_3_c).to_rust();
+        Some(ead_3)
+    };
+
+    match i_prepare_message_3(
+        &mut state,
+        &mut default_crypto(),
+        (*initiator_c).cred_i,
+        cred_transfer,
+        &ead_3,
+    ) {
+        Ok((state, msg_3, prk_out)) => {
+            *initiator_c_out = EdhocInitiatorDoneC { state };
+            *message_3 = msg_3;
+            *prk_out_c = prk_out;
+            0
+        }
+        Err(err) => err as i8,
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn initiator_compute_ephemeral_secret(
+    initiator_c: *const EdhocInitiatorC,
+    g_a: *const BytesP256ElemLen,
+    secret_c_out: *mut BytesP256ElemLen,
+) -> i8 {
+    let state = core::ptr::read(&(*initiator_c).state);
+
+    let secret = default_crypto().p256_ecdh(&state.x, &(*g_a));
+    core::ptr::copy_nonoverlapping(secret.as_ptr(), secret_c_out as *mut u8, secret.len());
+
+    0
+}
diff --git a/lakers-c/src/lib.rs b/lakers-c/src/lib.rs
new file mode 100644
index 00000000..bad3a560
--- /dev/null
+++ b/lakers-c/src/lib.rs
@@ -0,0 +1,126 @@
+#![no_std]
+/// This module contains the FFI bindings for the lakers-c library.
+/// Normally the structs can be derived from the Rust structs, except in cases
+/// where we need to hide fields that are not compatible with C, such as `Option<..>`.
+/// Specifically in the case of `Option<..>` we use a pointer instead, where `NULL` indicates `None`.
+///
+/// Example command to compile this module for the nRF52840:
+/// cargo build --target='thumbv7em-none-eabihf' --no-default-features --features="crypto-cryptocell310"
+use edhoc_rs::*;
+use lakers_crypto::{default_crypto, CryptoTrait};
+
+pub mod ead_authz;
+pub mod initiator;
+
+// crate type staticlib requires a panic handler and an allocator
+use embedded_alloc::Heap;
+use panic_semihosting as _;
+#[global_allocator]
+static HEAP: Heap = Heap::empty();
+
+/// Note that while the Rust version supports optional value to indicate an empty value,
+/// in the C version we use an empty buffer for that case.
+#[derive(Clone, Debug)]
+#[repr(C)]
+pub struct EADItemC {
+    pub label: u8,
+    pub is_critical: bool,
+    pub value: EdhocMessageBuffer,
+}
+
+impl EADItemC {
+    pub fn to_rust(&self) -> EADItem {
+        let value = Some(self.value);
+
+        EADItem {
+            label: self.label,
+            is_critical: self.is_critical,
+            value,
+        }
+    }
+
+    pub unsafe fn copy_into_c(mut ead: EADItem, ead_c: *mut EADItemC) {
+        (*ead_c).label = ead.label;
+        (*ead_c).is_critical = ead.is_critical;
+        if let Some(value) = ead.value {
+            (*ead_c).value = value;
+        }
+    }
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct ProcessingM2C {
+    pub mac_2: BytesMac2,
+    pub prk_2e: BytesHashLen,
+    pub th_2: BytesHashLen,
+    pub x: BytesP256ElemLen,
+    pub g_y: BytesP256ElemLen,
+    pub plaintext_2: EdhocMessageBuffer,
+    pub ead_2: *mut EADItemC,
+}
+
+impl ProcessingM2C {
+    pub fn to_rust(&self) -> ProcessingM2 {
+        ProcessingM2 {
+            mac_2: self.mac_2,
+            prk_2e: self.prk_2e,
+            th_2: self.th_2,
+            x: self.x,
+            g_y: self.g_y,
+            plaintext_2: self.plaintext_2,
+            ead_2: if self.ead_2.is_null() {
+                None
+            } else {
+                Some(unsafe { (*self.ead_2).to_rust() })
+            },
+        }
+    }
+
+    /// note that it is a shallow copy
+    pub unsafe fn copy_into_c(processing_m2: ProcessingM2, processing_m2_c: *mut ProcessingM2C) {
+        (*processing_m2_c).mac_2 = processing_m2.mac_2;
+        (*processing_m2_c).prk_2e = processing_m2.prk_2e;
+        (*processing_m2_c).th_2 = processing_m2.th_2;
+        (*processing_m2_c).x = processing_m2.x;
+        (*processing_m2_c).g_y = processing_m2.g_y;
+        (*processing_m2_c).plaintext_2 = processing_m2.plaintext_2;
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn credential_rpk_new(
+    value: *const u8,
+    value_len: usize,
+    cred: *mut CredentialRPK,
+) -> i8 {
+    let value = core::slice::from_raw_parts(value, value_len);
+    match CredentialRPK::new(EdhocMessageBuffer::new_from_slice(value).unwrap()) {
+        Ok(cred_rpk) => {
+            *cred = cred_rpk;
+            0
+        }
+        Err(_) => -1,
+    }
+}
+
+// This function is useful to test the FFI
+#[no_mangle]
+pub extern "C" fn p256_generate_key_pair_from_c(out_private_key: *mut u8, out_public_key: *mut u8) {
+    let (private_key, public_key) = default_crypto().p256_generate_key_pair();
+
+    unsafe {
+        // copy the arrays to the pointers received from C
+        // this makes sure that data is not dropped when the function returns
+        core::ptr::copy_nonoverlapping(
+            private_key.as_ptr(),
+            out_private_key,
+            edhoc_rs::P256_ELEM_LEN,
+        );
+        core::ptr::copy_nonoverlapping(
+            public_key.as_ptr(),
+            out_public_key,
+            edhoc_rs::P256_ELEM_LEN,
+        );
+    }
+}
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index e043dff5..7325ef2a 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -21,7 +21,7 @@ pub use {lakers_shared::Crypto as CryptoTrait, lakers_shared::*};
 pub use lakers_ead::*;
 
 mod edhoc;
-use edhoc::*;
+pub use edhoc::*;
 
 // TODO: clean these structs and remove the cred_x whre they are not needed anymore
 /// Starting point for performing EDHOC in the role of the Initiator.
diff --git a/shared/cbindgen.toml b/shared/cbindgen.toml
new file mode 100644
index 00000000..aec3919d
--- /dev/null
+++ b/shared/cbindgen.toml
@@ -0,0 +1,19 @@
+# call with:
+# cbindgen --config shared/cbindgen.toml --crate lakers-shared --output target/include/lakers_shared.h -vv
+
+language = "C"
+header = """
+/*
+ * ================================================================================================
+ *  WARNING: This file is automatically generated by cbindgen. Manual edits are likely to be lost.
+ * ================================================================================================
+ */"""
+include_guard = "LAKERS_SHARED_H"
+
+[export]
+include = [
+    "EdhocMessageBuffer", "BytesMac", "BytesMac2",
+    "EADItemC", "CredentialRPK", "CredentialTransfer",
+    "InitiatorStart", "WaitM2", "Completed", "ProcessedM2",
+    "EdhocInitiatorC", "EdhocInitiatorWaitM2C", "EdhocInitiatorProcessingM2C", "EdhocInitiatorProcessedM2C", "EdhocInitiatorDoneC",
+]
diff --git a/shared/src/cred.rs b/shared/src/cred.rs
index 59bef426..239ccb17 100644
--- a/shared/src/cred.rs
+++ b/shared/src/cred.rs
@@ -1,6 +1,7 @@
 use super::*;
 
 #[derive(Clone, Copy, Debug)]
+#[repr(C)]
 pub struct CredentialRPK {
     pub value: EdhocMessageBuffer,
     pub public_key: BytesP256ElemLen, // could be a reference, but safe Rust doesn't allow self-referencing structs
diff --git a/shared/src/lib.rs b/shared/src/lib.rs
index 44fd5310..373737bb 100644
--- a/shared/src/lib.rs
+++ b/shared/src/lib.rs
@@ -112,6 +112,7 @@ pub enum EDHOCError {
 }
 
 #[derive(Debug)]
+#[repr(C)]
 pub struct InitiatorStart {
     pub suites_i: BytesSuites,
     pub suites_i_len: usize,
@@ -134,7 +135,8 @@ pub struct ProcessingM1 {
     pub h_message_1: BytesHashLen,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
+#[repr(C)]
 pub struct WaitM2 {
     pub x: BytesP256ElemLen, // ephemeral private key of the initiator
     pub h_message_1: BytesHashLen,
@@ -147,7 +149,8 @@ pub struct WaitM3 {
     pub th_3: BytesHashLen,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Default)]
+#[repr(C)]
 pub struct ProcessingM2 {
     pub mac_2: BytesMac2,
     pub prk_2e: BytesHashLen,
@@ -159,6 +162,7 @@ pub struct ProcessingM2 {
 }
 
 #[derive(Debug)]
+#[repr(C)]
 pub struct ProcessedM2 {
     pub prk_3e2m: BytesHashLen,
     pub prk_4e3m: BytesHashLen,
@@ -184,11 +188,13 @@ pub struct PreparingM3 {
 }
 
 #[derive(Debug)]
+#[repr(C)]
 pub struct Completed {
     pub prk_out: BytesHashLen,
     pub prk_exporter: BytesHashLen,
 }
 
+#[repr(C)]
 pub enum CredentialTransfer {
     ByReference,
     ByValue,


Test patch:
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 34f3320f..a6af8b7a 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -91,7 +91,7 @@ jobs:
 
         # generate the fstar files
         cargo-hax -C -p lakers --no-default-features --features='lakers-crypto/hacspec, ead-none' --release \; into -i '-lakers::generate_connection_identifier_cbor -lakers::generate_connection_identifier' fstar
-        cargo-hax -C -p lakers-shared \; into fstar
+        cargo-hax -C -p lakers-shared \; into -i '-lakers_shared::ffi::**' fstar
 
         # even if fstar generation was ok, fail if something is not implemented
         [ -z "$(find -name *.fst -type f | xargs grep 'something is not implemented yet')" ]