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

Issue:
Introduce Credential and IdCredential as successors to CredentialRpk
Closes: #272, #273

This is a first sketch of how I'd go about #272; the design includes some choices influenced by my assessment of #273. The two new types should eventually replace CredentialRpk completely (and also remove the need for BytesIdCred).

If I'm wrong about #273, Credential may still have a `.value_type`, but it wouldn't be needed for `.by_value()` any more, only for `.by_thumbprint()` (which we don't have yet).

@geonnave, is this a good direction?


Discussion / hints:
This needed updates after understanding the need for #281.
Yes I think this is in the right direction.
Not really sure how `ByValueStyle` will be used, but I think it will become clearer as it progresses.
Thanks for the contributions so far.

Fix patch:
diff --git a/ead/lakers-ead-authz/src/server.rs b/ead/lakers-ead-authz/src/server.rs
index 9471af75..60f3c3fd 100644
--- a/ead/lakers-ead-authz/src/server.rs
+++ b/ead/lakers-ead-authz/src/server.rs
@@ -31,7 +31,7 @@ impl ZeroTouchServer {
         vreq: &EdhocMessageBuffer,
     ) -> Result<EdhocMessageBuffer, EDHOCError> {
         let (message_1, opaque_state) = parse_voucher_request(vreq)?;
-        let (_method, _suites_i, _suites_i_len, g_x, _c_i, ead_1) = parse_message_1(&message_1)?;
+        let (_method, _suites_i, g_x, _c_i, ead_1) = parse_message_1(&message_1)?;
         let prk = compute_prk(crypto, &self.w, &g_x);
 
         let (_loc_w, enc_id) = parse_ead_1_value(&ead_1.unwrap().value.unwrap())?;
@@ -72,7 +72,7 @@ impl ZeroTouchServerUserAcl {
         vreq: &EdhocMessageBuffer,
     ) -> Result<EdhocMessageBuffer, EDHOCError> {
         let (message_1, _opaque_state) = parse_voucher_request(vreq)?;
-        let (_method, _suites_i, _suites_i_len, g_x, _c_i, ead_1) = parse_message_1(&message_1)?;
+        let (_method, _suites_i, g_x, _c_i, ead_1) = parse_message_1(&message_1)?;
         let prk = compute_prk(crypto, &self.w, &g_x);
 
         let (_loc_w, enc_id) = parse_ead_1_value(&ead_1.unwrap().value.unwrap())?;
@@ -87,7 +87,7 @@ impl ZeroTouchServerUserAcl {
         vreq: &EdhocMessageBuffer,
     ) -> Result<EdhocMessageBuffer, EDHOCError> {
         let (message_1, opaque_state) = parse_voucher_request(vreq)?;
-        let (_method, _suites_i, _suites_i_len, g_x, _c_i, _ead_1) = parse_message_1(&message_1)?;
+        let (_method, _suites_i, g_x, _c_i, _ead_1) = parse_message_1(&message_1)?;
         let prk = compute_prk(crypto, &self.w, &g_x);
 
         // compute hash
diff --git a/examples/coap/src/bin/coapclient.rs b/examples/coap/src/bin/coapclient.rs
index 1078e654..ae80c7e8 100644
--- a/examples/coap/src/bin/coapclient.rs
+++ b/examples/coap/src/bin/coapclient.rs
@@ -30,10 +30,14 @@ fn client_handshake() -> Result<(), EDHOCError> {
     let timeout = Duration::new(5, 0);
     println!("Client request: {}", url);
 
-    let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap();
-    let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap();
+    let cred_i: Credential = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap();
+    let cred_r: Credential = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap();
 
-    let initiator = EdhocInitiator::new(lakers_crypto::default_crypto());
+    let initiator = EdhocInitiator::new(
+        lakers_crypto::default_crypto(),
+        EDHOCMethod::StatStat,
+        EDHOCSuite::CipherSuite2,
+    );
 
     // Send Message 1 over CoAP and convert the response to byte
     let mut msg_1_buf = Vec::from([0xf5u8]); // EDHOC message_1 when transported over CoAP is prepended with CBOR true
@@ -50,9 +54,10 @@ fn client_handshake() -> Result<(), EDHOCError> {
     println!("message_2 len = {}", response.message.payload.len());
 
     let message_2 = EdhocMessageBuffer::new_from_slice(&response.message.payload[..]).unwrap();
-    let (initiator, c_r, id_cred_r, _ead_2) = initiator.parse_message_2(&message_2)?;
+    let (mut initiator, c_r, id_cred_r, _ead_2) = initiator.parse_message_2(&message_2)?;
     let valid_cred_r = credential_check_or_fetch(Some(cred_r), id_cred_r).unwrap();
-    let initiator = initiator.verify_message_2(&I, cred_i, valid_cred_r)?;
+    initiator.set_identity(I.try_into().unwrap(), cred_i)?;
+    let initiator = initiator.verify_message_2(valid_cred_r)?;
 
     let mut msg_3 = Vec::from(c_r.as_cbor());
     let (mut initiator, message_3, prk_out) =
diff --git a/examples/coap/src/bin/coapserver-coaphandler.rs b/examples/coap/src/bin/coapserver-coaphandler.rs
index 9705ff3c..895982ec 100644
--- a/examples/coap/src/bin/coapserver-coaphandler.rs
+++ b/examples/coap/src/bin/coapserver-coaphandler.rs
@@ -75,7 +75,7 @@ enum EdhocResponse {
     // take up a slot there anyway) if we make it an enum.
     OkSend2 {
         c_r: ConnId,
-        responder: EdhocResponderProcessedM1<'static, Crypto>,
+        responder: EdhocResponderProcessedM1<Crypto>,
         // FIXME: Is the ead_2 the most practical data to store here? An easy alternative is the
         // voucher_response; ideal would be the voucher, bu that is only internal to prepare_ead_2.
         //
@@ -107,16 +107,20 @@ impl coap_handler::Handler for EdhocHandler {
 
         if starts_with_true {
             let cred_r =
-                CredentialRPK::new(CRED_R.try_into().expect("Static credential is too large"))
+                Credential::parse_ccs(CRED_R.try_into().expect("Static credential is too large"))
                     .expect("Static credential is not processable");
 
             let message_1 =
                 &EdhocMessageBuffer::new_from_slice(&request.payload()[1..]).map_err(too_small)?;
 
-            let (responder, _c_i, ead_1) =
-                EdhocResponder::new(lakers_crypto::default_crypto(), &R, cred_r)
-                    .process_message_1(message_1)
-                    .map_err(render_error)?;
+            let (responder, _c_i, ead_1) = EdhocResponder::new(
+                lakers_crypto::default_crypto(),
+                EDHOCMethod::StatStat,
+                R.try_into().expect("Wrong length of responder private key"),
+                cred_r,
+            )
+            .process_message_1(message_1)
+            .map_err(render_error)?;
 
             let ead_2 = if let Some(ead_1) = ead_1 {
                 let authenticator = ZeroTouchAuthenticator::default();
@@ -174,7 +178,7 @@ impl coap_handler::Handler for EdhocHandler {
                 render_error(e)
             })?;
             let cred_i =
-                CredentialRPK::new(CRED_I.try_into().expect("Static credential is too large"))
+                Credential::parse_ccs(CRED_I.try_into().expect("Static credential is too large"))
                     .expect("Static credential is not processable");
             let valid_cred_i =
                 credential_check_or_fetch(Some(cred_i), id_cred_i).map_err(render_error)?;
diff --git a/examples/coap/src/bin/coapserver.rs b/examples/coap/src/bin/coapserver.rs
index 63293f36..a114dc67 100644
--- a/examples/coap/src/bin/coapserver.rs
+++ b/examples/coap/src/bin/coapserver.rs
@@ -44,8 +44,13 @@ fn main() {
             println!("Received message from {}", src);
             // This is an EDHOC message
             if request.message.payload[0] == 0xf5 {
-                let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap();
-                let responder = EdhocResponder::new(lakers_crypto::default_crypto(), &R, cred_r);
+                let cred_r: Credential = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap();
+                let responder = EdhocResponder::new(
+                    lakers_crypto::default_crypto(),
+                    EDHOCMethod::StatStat,
+                    R.try_into().unwrap(),
+                    cred_r,
+                );
 
                 let message_1: EdhocMessageBuffer = request.message.payload[1..]
                     .try_into()
@@ -101,7 +106,7 @@ fn main() {
                     // anyway legally
                     continue;
                 };
-                let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap();
+                let cred_i = Credential::parse_ccs(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 error at verify_message_3: {:?}", valid_cred_i);
diff --git a/examples/lakers-c-native/main.c b/examples/lakers-c-native/main.c
index fa765f1c..35b87549 100644
--- a/examples/lakers-c-native/main.c
+++ b/examples/lakers-c-native/main.c
@@ -112,9 +112,9 @@ int main(void)
 
     // lakers init
     puts("loading credentials.");
-    CredentialRPK cred_i = {0}, cred_r = {0};
-    credential_rpk_new(&cred_i, CRED_I, 107);
-    credential_rpk_new(&cred_r, CRED_R, 84);
+    CredentialC cred_i = {0}, cred_r = {0};
+    credential_new(&cred_i, CRED_I, 107);
+    credential_new(&cred_r, CRED_R, 84);
     puts("creating edhoc initiator.");
     EdhocInitiator initiator = {0};
     initiator_new(&initiator);
@@ -153,7 +153,7 @@ int main(void)
     memcpy(message_2.content, coap_response_payload, coap_response_payload_len);
     EADItemC ead_2 = {0};
     uint8_t c_r;
-    CredentialRPK id_cred_r = {0};
+    CredentialC id_cred_r = {0};
 #ifdef LAKERS_EAD_AUTHZ
     // res = initiator_parse_message_2(&initiator, &message_2, &cred_r, &c_r, &id_cred_r, &ead_2);
     res = initiator_parse_message_2(&initiator, &message_2, &c_r, &id_cred_r, &ead_2);
@@ -166,14 +166,15 @@ int main(void)
         return 1;
     }
     // FIXME: failing on native when cred_expected is NULL (memory allocation of 48 bytes failed)
-    res = credential_check_or_fetch(&cred_r, &id_cred_r);
+    CredentialC fetched_cred_r = {0};
+    res = credential_check_or_fetch(&cred_r, &id_cred_r, &fetched_cred_r);
     if (res != 0) {
         printf("Error handling credential: %d\n", res);
         return 1;
     }
 #ifdef LAKERS_EAD_AUTHZ
     puts("processing ead2");
-    res = authz_device_process_ead_2(&device, &ead_2, &id_cred_r);
+    res = authz_device_process_ead_2(&device, &ead_2, &fetched_cred_r);
     if (res != 0) {
         printf("Error process ead2 (authz): %d\n", res);
         return 1;
@@ -181,7 +182,7 @@ int main(void)
         puts("ead-authz voucher received and validated");
     }
 #endif
-    res = initiator_verify_message_2(&initiator, &I, &cred_i, &id_cred_r);
+    res = initiator_verify_message_2(&initiator, &I, &cred_i, &fetched_cred_r);
     if (res != 0) {
         printf("Error verify msg2: %d\n", res);
         return 1;
diff --git a/examples/lakers-no_std/src/main.rs b/examples/lakers-no_std/src/main.rs
index bf859276..61cf22db 100644
--- a/examples/lakers-no_std/src/main.rs
+++ b/examples/lakers-no_std/src/main.rs
@@ -62,7 +62,11 @@ fn main() -> ! {
     const _C_R_TV: [u8; 1] = hex!("27");
 
     fn test_new_initiator() {
-        let _initiator = EdhocInitiator::new(lakers_crypto::default_crypto());
+        let _initiator = EdhocInitiator::new(
+            lakers_crypto::default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
     }
 
     test_new_initiator();
@@ -81,7 +85,11 @@ fn main() -> ! {
     println!("Test test_p256_keys passed.");
 
     fn test_prepare_message_1() {
-        let mut initiator = EdhocInitiator::new(lakers_crypto::default_crypto());
+        let mut initiator = EdhocInitiator::new(
+            lakers_crypto::default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
 
         let c_i =
             generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).as_slice();
@@ -93,11 +101,20 @@ fn main() -> ! {
     println!("Test test_prepare_message_1 passed.");
 
     fn test_handshake() {
-        let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap();
-        let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap();
-
-        let mut initiator = EdhocInitiator::new(lakers_crypto::default_crypto());
-        let responder = EdhocResponder::new(lakers_crypto::default_crypto(), R, cred_r.clone());
+        let cred_i = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap();
+        let cred_r = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap();
+
+        let initiator = EdhocInitiator::new(
+            lakers_crypto::default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
+        let responder = EdhocResponder::new(
+            lakers_crypto::default_crypto(),
+            EDHOCMethod::StatStat,
+            R.try_into().expect("Wrong length of responder private key"),
+            cred_r.clone(),
+        );
 
         let (initiator, message_1) = initiator.prepare_message_1(None, &None).unwrap();
 
@@ -106,9 +123,16 @@ fn main() -> ! {
             .prepare_message_2(CredentialTransfer::ByReference, None, &None)
             .unwrap();
 
-        let (initiator, c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap();
+        let (mut initiator, _c_r, id_cred_r, _ead_2) =
+            initiator.parse_message_2(&message_2).unwrap();
         let valid_cred_r = credential_check_or_fetch(Some(cred_r), id_cred_r).unwrap();
-        let initiator = initiator.verify_message_2(I, cred_i, valid_cred_r).unwrap();
+        initiator
+            .set_identity(
+                I.try_into().expect("Wrong length of initiator private key"),
+                cred_i.clone(),
+            )
+            .unwrap(); // exposing own identity only after validating cred_r
+        let initiator = initiator.verify_message_2(valid_cred_r).unwrap();
 
         let (mut initiator, message_3, i_prk_out) = initiator
             .prepare_message_3(CredentialTransfer::ByReference, &None)
diff --git a/lakers-c/cbindgen.toml b/lakers-c/cbindgen.toml
index 3354f038..18aa3a1f 100644
--- a/lakers-c/cbindgen.toml
+++ b/lakers-c/cbindgen.toml
@@ -14,3 +14,8 @@ cpp_compat = true
 
 [parse.expand]
 all_features = true
+
+[export]
+include = [
+    "CredentialC"
+]
diff --git a/lakers-c/src/ead_authz.rs b/lakers-c/src/ead_authz.rs
index 85feae2b..0d72715f 100644
--- a/lakers-c/src/ead_authz.rs
+++ b/lakers-c/src/ead_authz.rs
@@ -57,12 +57,13 @@ pub unsafe extern "C" fn authz_device_process_ead_2(
     // input parans
     device_c: *mut EadAuthzDevice,
     ead_2_c: *mut EADItemC,
-    cred_v: *mut CredentialRPK,
+    cred_v: *mut CredentialC,
 ) -> i8 {
     let crypto = &mut default_crypto();
     let device = &(*device_c);
     let ead_2 = (*ead_2_c).to_rust();
-    let cred_v = (*cred_v).value.as_slice();
+    let cred_v = (*cred_v).to_rust();
+    let cred_v = cred_v.bytes.as_slice();
     match device.wait_ead2.process_ead_2(crypto, ead_2, cred_v) {
         Ok(device) => {
             (*device_c).done = device;
diff --git a/lakers-c/src/initiator.rs b/lakers-c/src/initiator.rs
index 4f07cc09..3d3802b3 100644
--- a/lakers-c/src/initiator.rs
+++ b/lakers-c/src/initiator.rs
@@ -1,5 +1,5 @@
 use lakers::{
-    EdhocInitiator as EdhocInitiatorRust, // alias to conflict with the C-compatible struct
+    // EdhocInitiator as EdhocInitiatorRust, // alias to conflict with the C-compatible struct
     *,
 };
 use lakers_crypto::{default_crypto, CryptoTrait};
@@ -15,23 +15,22 @@ pub struct EdhocInitiator {
     pub wait_m2: WaitM2,
     pub processing_m2: ProcessingM2C,
     pub processed_m2: ProcessedM2,
-    pub cred_i: *mut CredentialRPK,
+    pub cred_i: *mut CredentialC,
     pub completed: Completed,
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn initiator_new(initiator: *mut EdhocInitiator) -> i8 {
-    // 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();
+    let mut crypto = default_crypto();
+    let suites_i =
+        prepare_suites_i(&crypto.supported_suites(), EDHOCSuite::CipherSuite2.into()).unwrap();
+    let (x, g_x) = crypto.p256_generate_key_pair();
 
     let start = InitiatorStart {
         x,
         g_x,
         suites_i,
-        suites_i_len,
+        method: EDHOCMethod::StatStat.into(),
     };
 
     core::ptr::write(&mut (*initiator).start, start);
@@ -87,7 +86,7 @@ pub unsafe extern "C" fn initiator_parse_message_2(
     message_2: *const EdhocMessageBuffer,
     // output params
     c_r_out: *mut u8,
-    id_cred_r_out: *mut CredentialRPK,
+    id_cred_r_out: *mut IdCred,
     ead_2_c_out: *mut EADItemC,
 ) -> i8 {
     // this is a parsing function, so all output parameters are mandatory
@@ -130,8 +129,8 @@ pub unsafe extern "C" fn initiator_verify_message_2(
     // input params
     initiator_c: *mut EdhocInitiator,
     i: *const BytesP256ElemLen,
-    mut cred_i: *mut CredentialRPK,
-    valid_cred_r: *mut CredentialRPK,
+    cred_i: *mut CredentialC,
+    valid_cred_r: *mut CredentialC,
 ) -> i8 {
     if initiator_c.is_null() || i.is_null() {
         return -1;
@@ -140,7 +139,7 @@ pub unsafe extern "C" fn initiator_verify_message_2(
 
     let state = core::ptr::read(&(*initiator_c).processing_m2).to_rust();
 
-    match i_verify_message_2(&state, crypto, *valid_cred_r, &(*i)) {
+    match i_verify_message_2(&state, crypto, (*valid_cred_r).to_rust(), &(*i)) {
         Ok(state) => {
             (*initiator_c).processed_m2 = state;
             (*initiator_c).cred_i = cred_i;
@@ -177,7 +176,7 @@ pub unsafe extern "C" fn initiator_prepare_message_3(
     match i_prepare_message_3(
         &state,
         crypto,
-        *(*initiator_c).cred_i,
+        (*(*initiator_c).cred_i).to_rust(),
         cred_transfer,
         &ead_3,
     ) {
diff --git a/lakers-c/src/lib.rs b/lakers-c/src/lib.rs
index 1476e30c..8cf80f79 100644
--- a/lakers-c/src/lib.rs
+++ b/lakers-c/src/lib.rs
@@ -59,6 +59,7 @@ pub struct ProcessingM2C {
     pub g_y: BytesP256ElemLen,
     pub plaintext_2: EdhocMessageBuffer,
     pub c_r: u8,
+    pub id_cred_r: IdCred,
     pub ead_2: *mut EADItemC,
 }
 
@@ -72,6 +73,7 @@ impl Default for ProcessingM2C {
             g_y: Default::default(),
             plaintext_2: Default::default(),
             c_r: Default::default(),
+            id_cred_r: Default::default(),
             ead_2: core::ptr::null_mut(),
         }
     }
@@ -87,6 +89,7 @@ impl ProcessingM2C {
             g_y: self.g_y,
             plaintext_2: self.plaintext_2,
             c_r: ConnId::from_int_raw(self.c_r),
+            id_cred_r: self.id_cred_r.clone(),
             ead_2: if self.ead_2.is_null() {
                 None
             } else {
@@ -110,19 +113,50 @@ impl ProcessingM2C {
         let c_r = processing_m2.c_r.as_slice();
         assert_eq!(c_r.len(), 1, "C API only supports short C_R");
         (*processing_m2_c).c_r = c_r[0];
+        (*processing_m2_c).id_cred_r = processing_m2.id_cred_r;
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[repr(C)]
+pub struct CredentialC {
+    pub bytes: BufferCred,
+    pub key: CredentialKey,
+    /// differs from Rust: here we assume the kid is always present
+    /// this is to simplify the C API, since C doesn't support Option<T>
+    /// the alternative would be to use a pointer, but then we need to care about memory management
+    pub kid: BufferKid,
+    pub cred_type: CredentialType,
+}
+
+impl CredentialC {
+    pub fn to_rust(&self) -> Credential {
+        Credential {
+            bytes: self.bytes,
+            key: self.key,
+            kid: Some(self.kid),
+            cred_type: self.cred_type,
+        }
+    }
+
+    pub unsafe fn copy_into_c(cred: Credential, cred_c: *mut CredentialC) {
+        (*cred_c).bytes = cred.bytes;
+        (*cred_c).key = cred.key;
+        (*cred_c).kid = cred.kid.unwrap();
+        (*cred_c).cred_type = cred.cred_type;
     }
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn credential_rpk_new(
-    cred: *mut CredentialRPK,
+pub unsafe extern "C" fn credential_new(
+    cred: *mut CredentialC,
     value: *const u8,
     value_len: usize,
 ) -> 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;
+    match Credential::parse_ccs(value) {
+        Ok(cred_parsed) => {
+            CredentialC::copy_into_c(cred_parsed, cred);
             0
         }
         Err(_) => -1,
@@ -131,19 +165,20 @@ pub unsafe extern "C" fn credential_rpk_new(
 
 #[no_mangle]
 pub unsafe extern "C" fn credential_check_or_fetch(
-    cred_expected: *mut CredentialRPK,
-    id_cred_received: *mut CredentialRPK,
+    cred_expected: *mut CredentialC,
+    id_cred_received: *mut IdCred,
+    cred_out: *mut CredentialC,
 ) -> i8 {
     let cred_expected = if cred_expected.is_null() {
         None
     } else {
-        Some(*cred_expected)
+        Some((*cred_expected).to_rust())
     };
 
     let id_cred_received_value = *id_cred_received;
     match credential_check_or_fetch_rust(cred_expected, id_cred_received_value) {
         Ok(valid_cred) => {
-            *id_cred_received = valid_cred;
+            CredentialC::copy_into_c(valid_cred, cred_out);
             0
         }
         Err(err) => err as i8,
diff --git a/lakers-python/src/ead_authz/server.rs b/lakers-python/src/ead_authz/server.rs
index 4154d17b..99022528 100644
--- a/lakers-python/src/ead_authz/server.rs
+++ b/lakers-python/src/ead_authz/server.rs
@@ -11,6 +11,7 @@ pub struct PyAuthzEnrollmentServer {
 #[pymethods]
 impl PyAuthzEnrollmentServer {
     #[new]
+    #[pyo3(signature = (w, cred_v, acl=None))]
     pub fn new(w: Vec<u8>, cred_v: Vec<u8>, acl: Option<Vec<u8>>) -> Self {
         let mut w_arr = BytesP256ElemLen::default();
         w_arr.copy_from_slice(&w.as_slice());
diff --git a/lakers-python/src/initiator.rs b/lakers-python/src/initiator.rs
index 87cb8ae0..1687f82c 100644
--- a/lakers-python/src/initiator.rs
+++ b/lakers-python/src/initiator.rs
@@ -4,7 +4,7 @@ use pyo3::{prelude::*, types::PyBytes};
 
 #[pyclass(name = "EdhocInitiator")]
 pub struct PyEdhocInitiator {
-    cred_i: Option<CredentialRPK>,
+    cred_i: Option<Credential>,
     start: InitiatorStart,
     wait_m2: WaitM2,
     processing_m2: ProcessingM2,
@@ -16,19 +16,18 @@ pub struct PyEdhocInitiator {
 impl PyEdhocInitiator {
     #[new]
     fn new() -> Self {
-        // 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();
+        let mut crypto = default_crypto();
+        let suites_i =
+            prepare_suites_i(&crypto.supported_suites(), EDHOCSuite::CipherSuite2.into()).unwrap();
+        let (x, g_x) = crypto.p256_generate_key_pair();
 
         Self {
             cred_i: None,
             start: InitiatorStart {
                 x,
                 g_x,
+                method: EDHOCMethod::StatStat.into(),
                 suites_i,
-                suites_i_len,
             },
             wait_m2: WaitM2::default(),
             processing_m2: ProcessingM2::default(),
@@ -37,6 +36,7 @@ impl PyEdhocInitiator {
         }
     }
 
+    #[pyo3(signature = (c_i=None, ead_1=None))]
     fn prepare_message_1<'a>(
         &mut self,
         py: Python<'a>,
@@ -69,13 +69,11 @@ impl PyEdhocInitiator {
         match i_parse_message_2(&self.wait_m2, &mut default_crypto(), &message_2) {
             Ok((state, c_r, id_cred_r, ead_2)) => {
                 self.processing_m2 = state;
-                let id_cred_r = if id_cred_r.reference_only() {
-                    PyBytes::new_bound(py, &[id_cred_r.kid])
-                } else {
-                    PyBytes::new_bound(py, id_cred_r.value.as_slice())
-                };
-                let c_r = PyBytes::new_bound(py, c_r.as_slice());
-                Ok((c_r, id_cred_r, ead_2))
+                Ok((
+                    PyBytes::new_bound(py, c_r.as_slice()),
+                    PyBytes::new_bound(py, id_cred_r.bytes.as_slice()),
+                    ead_2,
+                ))
             }
             Err(error) => Err(error.into()),
         }
@@ -84,8 +82,8 @@ impl PyEdhocInitiator {
     pub fn verify_message_2(
         &mut self,
         i: Vec<u8>,
-        cred_i: super::AutoCredentialRPK,
-        valid_cred_r: super::AutoCredentialRPK,
+        cred_i: super::AutoCredential,
+        valid_cred_r: super::AutoCredential,
     ) -> PyResult<()> {
         let cred_i = cred_i.to_credential()?;
         let valid_cred_r = valid_cred_r.to_credential()?;
@@ -107,6 +105,7 @@ impl PyEdhocInitiator {
         }
     }
 
+    #[pyo3(signature = (cred_transfer, ead_3=None))]
     pub fn prepare_message_3<'a>(
         &mut self,
         py: Python<'a>,
@@ -185,6 +184,6 @@ impl PyEdhocInitiator {
     }
 
     pub fn selected_cipher_suite(&self) -> PyResult<u8> {
-        Ok(self.start.suites_i[self.start.suites_i_len - 1])
+        Ok(self.start.suites_i[self.start.suites_i.len() - 1])
     }
 }
diff --git a/lakers-python/src/lib.rs b/lakers-python/src/lib.rs
index 1c50c7ae..4406e1bc 100644
--- a/lakers-python/src/lib.rs
+++ b/lakers-python/src/lib.rs
@@ -14,34 +14,21 @@ mod responder;
 // This is because the incoming lists of bytes are automatically converted to `Vec<u8>` by pyo3,
 // but the outgoing ones must be explicitly converted to `PyBytes`.
 
+// NOTE: using inverted parameters from rust version (credential_check_or_fetch)
+// since, in Python, parameters that can be None come later
 #[pyfunction(name = "credential_check_or_fetch")]
-// FIXME: using inverted parameters from rust version (credential_check_or_fetch)
-// since, in Python, by convention, parameters that can be None come later
+#[pyo3(signature = (id_cred_received, cred_expected=None))]
 pub fn py_credential_check_or_fetch<'a>(
     py: Python<'a>,
     id_cred_received: Vec<u8>,
-    cred_expected: Option<AutoCredentialRPK>,
+    cred_expected: Option<AutoCredential>,
 ) -> PyResult<Bound<'a, PyBytes>> {
-    let cred_expected = cred_expected.map(|c| c.to_credential()).transpose()?;
+    let valid_cred = credential_check_or_fetch(
+        cred_expected.map(|c| c.to_credential()).transpose()?,
+        IdCred::from_full_value(id_cred_received.as_slice())?,
+    )?;
 
-    let valid_cred = if id_cred_received.len() == 1 {
-        credential_check_or_fetch(
-            cred_expected,
-            CredentialRPK {
-                kid: id_cred_received[0],
-                value: Default::default(),
-                public_key: Default::default(),
-            },
-        )?
-    } else {
-        credential_check_or_fetch(
-            cred_expected,
-            CredentialRPK::new(
-                EdhocMessageBuffer::new_from_slice(id_cred_received.as_slice()).unwrap(),
-            )?,
-        )?
-    };
-    Ok(PyBytes::new_bound(py, valid_cred.value.as_slice()))
+    Ok(PyBytes::new_bound(py, valid_cred.bytes.as_slice()))
 }
 
 /// this function is useful to test the python bindings
@@ -57,23 +44,23 @@ fn p256_generate_key_pair<'a>(
 }
 
 /// Helper for PyO3 converted functions that behave like passing an argument through a
-/// `CredentialRPK` constructor; use this in an argument and then call [self.to_credential()].
+/// `Credential` constructor; use this in an argument and then call [self.to_credential()].
 /// The resulting function will accept both a bytes-ish object (and pass it through
-/// [CredentialRPK::new()] or a preexisting [CredentialRPK].
+/// [Credential::new()] or a preexisting [Credential].
 #[derive(FromPyObject)]
-enum AutoCredentialRPK {
+pub enum AutoCredential {
     #[pyo3(transparent, annotation = "bytes")]
     Parse(Vec<u8>),
-    #[pyo3(transparent, annotation = "CredentialRPK")]
-    Existing(lakers_shared::CredentialRPK),
+    #[pyo3(transparent, annotation = "Credential")]
+    Existing(lakers_shared::Credential),
 }
 
-impl AutoCredentialRPK {
-    fn to_credential(self) -> PyResult<CredentialRPK> {
-        use AutoCredentialRPK::*;
+impl AutoCredential {
+    pub fn to_credential(self) -> PyResult<Credential> {
+        use AutoCredential::*;
         Ok(match self {
             Existing(e) => e,
-            Parse(v) => CredentialRPK::new(EdhocMessageBuffer::new_from_slice(&v)?)?,
+            Parse(v) => Credential::parse_ccs(v.as_slice())?,
         })
     }
 }
@@ -89,7 +76,7 @@ fn lakers_python(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
     m.add_class::<responder::PyEdhocResponder>()?;
     m.add_class::<lakers::CredentialTransfer>()?;
     m.add_class::<lakers::EADItem>()?;
-    m.add_class::<lakers::CredentialRPK>()?;
+    m.add_class::<lakers::Credential>()?;
     // ead-authz items
     m.add_class::<ead_authz::PyAuthzDevice>()?;
     m.add_class::<ead_authz::PyAuthzAutenticator>()?;
diff --git a/lakers-python/src/responder.rs b/lakers-python/src/responder.rs
index 35888782..396ef442 100644
--- a/lakers-python/src/responder.rs
+++ b/lakers-python/src/responder.rs
@@ -5,7 +5,7 @@ use pyo3::{prelude::*, types::PyBytes};
 #[pyclass(name = "EdhocResponder")]
 pub struct PyEdhocResponder {
     r: Vec<u8>,
-    cred_r: CredentialRPK,
+    cred_r: Credential,
     start: ResponderStart,
     processing_m1: ProcessingM1,
     wait_m3: WaitM3,
@@ -16,7 +16,7 @@ pub struct PyEdhocResponder {
 #[pymethods]
 impl PyEdhocResponder {
     #[new]
-    fn new(r: Vec<u8>, cred_r: super::AutoCredentialRPK) -> PyResult<Self> {
+    fn new(r: Vec<u8>, cred_r: super::AutoCredential) -> PyResult<Self> {
         let (y, g_y) = default_crypto().p256_generate_key_pair();
 
         let cred_r = cred_r.to_credential()?;
@@ -24,7 +24,11 @@ impl PyEdhocResponder {
         Ok(Self {
             r,
             cred_r,
-            start: ResponderStart { y, g_y },
+            start: ResponderStart {
+                method: EDHOCMethod::StatStat.into(),
+                y,
+                g_y,
+            },
             processing_m1: ProcessingM1::default(),
             wait_m3: WaitM3::default(),
             processing_m3: ProcessingM3::default(),
@@ -46,6 +50,7 @@ impl PyEdhocResponder {
         Ok((c_i, ead_1))
     }
 
+    #[pyo3(signature = (cred_transfer, c_r=None, ead_2=None))]
     fn prepare_message_2<'a>(
         &mut self,
         py: Python<'a>,
@@ -88,12 +93,7 @@ impl PyEdhocResponder {
         match r_parse_message_3(&mut self.wait_m3, &mut default_crypto(), &message_3) {
             Ok((state, id_cred_i, ead_3)) => {
                 self.processing_m3 = state;
-                let id_cred_i = if id_cred_i.reference_only() {
-                    PyBytes::new_bound(py, &[id_cred_i.kid])
-                } else {
-                    PyBytes::new_bound(py, id_cred_i.value.as_slice())
-                };
-                Ok((id_cred_i, ead_3))
+                Ok((PyBytes::new_bound(py, id_cred_i.bytes.as_slice()), ead_3))
             }
             Err(error) => Err(error.into()),
         }
@@ -102,7 +102,7 @@ impl PyEdhocResponder {
     pub fn verify_message_3<'a>(
         &mut self,
         py: Python<'a>,
-        valid_cred_i: super::AutoCredentialRPK,
+        valid_cred_i: super::AutoCredential,
     ) -> PyResult<Bound<'a, PyBytes>> {
         let valid_cred_i = valid_cred_i.to_credential()?;
         match r_verify_message_3(&mut self.processing_m3, &mut default_crypto(), valid_cred_i) {
diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs
index ae3b0c67..446f264d 100644
--- a/lib/src/edhoc.rs
+++ b/lib/src/edhoc.rs
@@ -24,10 +24,6 @@ pub fn edhoc_key_update(
     context: &BytesMaxContextBuffer,
     context_len: usize,
 ) -> BytesHashLen {
-    // FIXME: Normally we would decompose `state` here, but hax disallows aliasing a `mut` item.
-    // The best fix for this is to change state from a tuple-struct to a regular struct.
-    // In the code below, `state.6` means `mut prk_out` and `state.7` means `mut prk_exporter`
-
     // new PRK_out
     let prk_new_buf = edhoc_kdf(
         crypto,
@@ -60,11 +56,11 @@ pub fn r_process_message_1(
 ) -> Result<(ProcessingM1, ConnId, Option<EADItem>), EDHOCError> {
     // Step 1: decode message_1
     // g_x will be saved to the state
-    if let Ok((method, suites_i, suites_i_len, g_x, c_i, ead_1)) = parse_message_1(message_1) {
+    if let Ok((method, suites_i, g_x, c_i, ead_1)) = parse_message_1(message_1) {
         // verify that the method is supported
         if method == EDHOC_METHOD {
             // Step 2: verify that the selected cipher suite is supported
-            if suites_i[suites_i_len - 1] == EDHOC_SUPPORTED_SUITES[0] {
+            if suites_i[suites_i.len() - 1] == EDHOC_SUPPORTED_SUITES[0] {
                 // hash message_1 and save the hash to the state to avoid saving the whole message
                 let mut message_1_buf: BytesMaxBuffer = [0x00; MAX_BUFFER_LEN];
                 message_1_buf[..message_1.len].copy_from_slice(message_1.as_slice());
@@ -95,7 +91,7 @@ pub fn r_process_message_1(
 pub fn r_prepare_message_2(
     state: &ProcessingM1,
     crypto: &mut impl CryptoTrait,
-    cred_r: CredentialRPK,
+    cred_r: Credential,
     r: &BytesP256ElemLen, // R's static private DH key
     c_r: ConnId,
     cred_transfer: CredentialTransfer,
@@ -109,28 +105,28 @@ pub fn r_prepare_message_2(
     let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2);
     let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.g_x);
 
+    let id_cred_r = match cred_transfer {
+        CredentialTransfer::ByValue => cred_r.by_value()?,
+        CredentialTransfer::ByReference => cred_r.by_kid()?,
+    };
+
     // compute MAC_2
     let mac_2 = compute_mac_2(
         crypto,
         &prk_3e2m,
         c_r,
-        &cred_r.get_id_cred(),
-        cred_r.value.as_slice(),
+        id_cred_r.as_full_value(),
+        cred_r.bytes.as_slice(),
         &th_2,
         ead_2,
     );
 
-    let id_cred_r = match cred_transfer {
-        CredentialTransfer::ByValue => IdCred::FullCredential(cred_r.value.as_slice()),
-        CredentialTransfer::ByReference => IdCred::CompactKid(cred_r.kid),
-    };
-
     // compute ciphertext_2
-    let plaintext_2 = encode_plaintext_2(c_r, &id_cred_r, &mac_2, &ead_2)?;
+    let plaintext_2 = encode_plaintext_2(c_r, id_cred_r.as_encoded_value(), &mac_2, &ead_2)?;
 
     // step is actually from processing of message_3
     // but we do it here to avoid storing plaintext_2 in State
-    let th_3 = compute_th_3(crypto, &th_2, &plaintext_2, cred_r.value.as_slice());
+    let th_3 = compute_th_3(crypto, &th_2, &plaintext_2, cred_r.bytes.as_slice());
 
     let mut ct: BufferCiphertext2 = BufferCiphertext2::new();
     ct.fill_with_slice(plaintext_2.as_slice()).unwrap(); // TODO(hax): can we prove with hax that this won't panic since they use the same underlying buffer length?
@@ -151,48 +147,24 @@ pub fn r_prepare_message_2(
     ))
 }
 
-// FIXME fetch ID_CRED_I and CRED_I based on kid
 pub fn r_parse_message_3(
     state: &mut WaitM3,
     crypto: &mut impl CryptoTrait,
     message_3: &BufferMessage3,
-) -> Result<(ProcessingM3, CredentialRPK, Option<EADItem>), EDHOCError> {
+) -> Result<(ProcessingM3, IdCred, Option<EADItem>), EDHOCError> {
     let plaintext_3 = decrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, message_3);
 
     if let Ok(plaintext_3) = plaintext_3 {
         let decoded_p3_res = decode_plaintext_3(&plaintext_3);
 
         if let Ok((id_cred_i, mac_3, ead_3)) = decoded_p3_res {
-            let id_cred_i = match id_cred_i {
-                IdCred::CompactKid(kid) => CredentialRPK {
-                    value: Default::default(),
-                    public_key: Default::default(),
-                    kid,
-                },
-                IdCred::FullCredential(cred) => {
-                    let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else {
-                        return Err(EDHOCError::ParsingError);
-                    };
-                    if let Ok(parsed_rpk) = CredentialRPK::new(buffer) {
-                        parsed_rpk
-                    } else {
-                        // This is incomplete, and the application will need to fill in the gaps --
-                        // just as in the CompactKid case the CredentialRPK is also incomplete.
-                        CredentialRPK {
-                            value: buffer,
-                            public_key: Default::default(),
-                            kid: Default::default(),
-                        }
-                    }
-                }
-            };
-
             Ok((
                 ProcessingM3 {
                     mac_3,
                     y: state.y,
                     prk_3e2m: state.prk_3e2m,
                     th_3: state.th_3,
+                    id_cred_i: id_cred_i.clone(), // needed for compute_mac_3
                     plaintext_3, // NOTE: this is needed for th_4, which needs valid_cred_i, which is only available at the 'verify' step
                     ead_3: ead_3.clone(), // NOTE: this clone could be avoided by using a reference or an index to the ead_3 item in plaintext_3
                 },
@@ -211,20 +183,25 @@ pub fn r_parse_message_3(
 pub fn r_verify_message_3(
     state: &mut ProcessingM3,
     crypto: &mut impl CryptoTrait,
-    valid_cred_i: CredentialRPK,
+    valid_cred_i: Credential,
 ) -> Result<(Completed, BytesHashLen), EDHOCError> {
     // compute salt_4e3m
     let salt_4e3m = compute_salt_4e3m(crypto, &state.prk_3e2m, &state.th_3);
-    // TODO compute prk_4e3m
-    let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, &state.y, &valid_cred_i.public_key);
+
+    let prk_4e3m = match valid_cred_i.key {
+        CredentialKey::EC2Compact(public_key) => {
+            compute_prk_4e3m(crypto, &salt_4e3m, &state.y, &public_key)
+        }
+        CredentialKey::Symmetric(_psk) => todo!("PSK not implemented"),
+    };
 
     // compute mac_3
     let expected_mac_3 = compute_mac_3(
         crypto,
         &prk_4e3m,
         &state.th_3,
-        &valid_cred_i.get_id_cred(),
-        valid_cred_i.value.as_slice(),
+        state.id_cred_i.as_full_value(),
+        valid_cred_i.bytes.as_slice(),
         &state.ead_3,
     );
 
@@ -234,7 +211,7 @@ pub fn r_verify_message_3(
             crypto,
             &state.th_3,
             &state.plaintext_3,
-            valid_cred_i.value.as_slice(),
+            valid_cred_i.bytes.as_slice(),
         );
 
         let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN];
@@ -284,14 +261,7 @@ pub fn i_prepare_message_1(
     ead_1: &Option<EADItem>, // FIXME: make it a list of EADItem
 ) -> Result<(WaitM2, BufferMessage1), EDHOCError> {
     // Encode message_1 as a sequence of CBOR encoded data items as specified in Section 5.2.1
-    let message_1 = encode_message_1(
-        EDHOC_METHOD,
-        &state.suites_i,
-        state.suites_i_len,
-        &state.g_x,
-        c_i,
-        ead_1,
-    )?;
+    let message_1 = encode_message_1(state.method, &state.suites_i, &state.g_x, c_i, ead_1)?;
 
     let mut message_1_buf: BytesMaxBuffer = [0x00; MAX_BUFFER_LEN];
     message_1_buf[..message_1.len].copy_from_slice(message_1.as_slice());
@@ -313,7 +283,7 @@ pub fn i_parse_message_2<'a>(
     state: &WaitM2,
     crypto: &mut impl CryptoTrait,
     message_2: &BufferMessage2,
-) -> Result<(ProcessingM2, ConnId, CredentialRPK, Option<EADItem>), EDHOCError> {
+) -> Result<(ProcessingM2, ConnId, IdCred, Option<EADItem>), EDHOCError> {
     let res = parse_message_2(message_2);
     if let Ok((g_y, ciphertext_2)) = res {
         let th_2 = compute_th_2(crypto, &g_y, &state.h_message_1);
@@ -335,21 +305,8 @@ pub fn i_parse_message_2<'a>(
                 g_y,
                 plaintext_2: plaintext_2,
                 c_r: c_r_2,
-                ead_2: ead_2.clone(), // needed for compute_mac_2
-            };
-
-            let id_cred_r = match id_cred_r {
-                IdCred::CompactKid(kid) => CredentialRPK {
-                    value: Default::default(),
-                    public_key: Default::default(),
-                    kid,
-                },
-                IdCred::FullCredential(cred) => {
-                    let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else {
-                        return Err(EDHOCError::ParsingError);
-                    };
-                    CredentialRPK::new(buffer)?
-                }
+                id_cred_r: id_cred_r.clone(), // needed for compute_mac_2
+                ead_2: ead_2.clone(),         // needed for compute_mac_2
             };
 
             Ok((state, c_r_2, id_cred_r, ead_2))
@@ -364,20 +321,25 @@ pub fn i_parse_message_2<'a>(
 pub fn i_verify_message_2(
     state: &ProcessingM2,
     crypto: &mut impl CryptoTrait,
-    valid_cred_r: CredentialRPK, // TODO: have a struct to hold credentials to avoid re-computing
-    i: &BytesP256ElemLen,        // I's static private DH key
+    valid_cred_r: Credential,
+    i: &BytesP256ElemLen, // I's static private DH key
 ) -> Result<ProcessedM2, EDHOCError> {
     // verify mac_2
     let salt_3e2m = compute_salt_3e2m(crypto, &state.prk_2e, &state.th_2);
 
-    let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, &state.x, &valid_cred_r.public_key);
+    let prk_3e2m = match valid_cred_r.key {
+        CredentialKey::EC2Compact(public_key) => {
+            compute_prk_3e2m(crypto, &salt_3e2m, &state.x, &public_key)
+        }
+        CredentialKey::Symmetric(_psk) => todo!("PSK not implemented"),
+    };
 
     let expected_mac_2 = compute_mac_2(
         crypto,
         &prk_3e2m,
         state.c_r,
-        &valid_cred_r.get_id_cred(),
-        valid_cred_r.value.as_slice(),
+        state.id_cred_r.as_full_value(),
+        valid_cred_r.bytes.as_slice(),
         &state.th_2,
         &state.ead_2,
     );
@@ -389,7 +351,7 @@ pub fn i_verify_message_2(
             crypto,
             &state.th_2,
             &state.plaintext_2,
-            valid_cred_r.value.as_slice(),
+            valid_cred_r.bytes.as_slice(),
         );
         // message 3 processing
 
@@ -412,27 +374,28 @@ pub fn i_verify_message_2(
 pub fn i_prepare_message_3(
     state: &ProcessedM2,
     crypto: &mut impl CryptoTrait,
-    cred_i: CredentialRPK,
+    cred_i: Credential,
     cred_transfer: CredentialTransfer,
     ead_3: &Option<EADItem>, // FIXME: make it a list of EADItem
 ) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> {
+    let id_cred_i = match cred_transfer {
+        CredentialTransfer::ByValue => cred_i.by_value()?,
+        CredentialTransfer::ByReference => cred_i.by_kid()?,
+    };
+
     let mac_3 = compute_mac_3(
         crypto,
         &state.prk_4e3m,
         &state.th_3,
-        &cred_i.get_id_cred(),
-        cred_i.value.as_slice(),
+        id_cred_i.as_full_value(),
+        cred_i.bytes.as_slice(),
         ead_3,
     );
 
-    let id_cred_i = match cred_transfer {
-        CredentialTransfer::ByValue => IdCred::FullCredential(cred_i.value.as_slice()),
-        CredentialTransfer::ByReference => IdCred::CompactKid(cred_i.kid),
-    };
-    let plaintext_3 = encode_plaintext_3(&id_cred_i, &mac_3, &ead_3)?;
+    let plaintext_3 = encode_plaintext_3(id_cred_i.as_encoded_value(), &mac_3, &ead_3)?;
     let message_3 = encrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, &plaintext_3);
 
-    let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, cred_i.value.as_slice());
+    let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, cred_i.bytes.as_slice());
 
     let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN];
     th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]);
@@ -508,8 +471,7 @@ fn encode_ead_item(ead_1: &EADItem) -> Result<EdhocMessageBuffer, EDHOCError> {
 
 fn encode_message_1(
     method: u8,
-    suites: &BytesSuites,
-    suites_len: usize,
+    suites: &EdhocBuffer<MAX_SUITES_LEN>,
     g_x: &BytesP256ElemLen,
     c_i: ConnId,
     ead_1: &Option<EADItem>,
@@ -519,7 +481,7 @@ fn encode_message_1(
 
     output.content[0] = method; // CBOR unsigned int less than 24 is encoded verbatim
 
-    if suites_len == 1 {
+    if suites.len == 1 {
         // only one suite, will be encoded as a single integer
         if suites[0] <= CBOR_UINT_1BYTE {
             output.content[1] = suites[0];
@@ -531,9 +493,9 @@ fn encode_message_1(
         }
     } else {
         // several suites, will be encoded as an array
-        output.content[1] = CBOR_MAJOR_ARRAY + (suites_len as u8);
+        output.content[1] = CBOR_MAJOR_ARRAY + (suites.len as u8);
         raw_suites_len += 1;
-        for &suite in suites[0..suites_len].iter() {
+        for &suite in suites.as_slice().iter() {
             if suite <= CBOR_UINT_1BYTE {
                 output.content[1 + raw_suites_len] = suite;
                 raw_suites_len += 1;
@@ -651,14 +613,17 @@ fn edhoc_kdf(
 }
 
 fn encode_plaintext_3(
-    id_cred_i: &IdCred,
+    id_cred_i: &[u8],
     mac_3: &BytesMac3,
     ead_3: &Option<EADItem>,
 ) -> Result<BufferPlaintext3, EDHOCError> {
     let mut plaintext_3: BufferPlaintext3 = BufferPlaintext3::new();
 
     // plaintext: P = ( ? PAD, ID_CRED_I / bstr / int, Signature_or_MAC_3, ? EAD_3 )
-    id_cred_i.write_to_message(&mut plaintext_3)?;
+    // id_cred_i.write_to_message(&mut plaintext_3)?;
+    plaintext_3
+        .extend_from_slice(id_cred_i)
+        .or(Err(EDHOCError::EncodingError))?;
     let offset_cred = plaintext_3.len;
     plaintext_3.content[offset_cred] = CBOR_MAJOR_BYTE_STRING | MAC_LENGTH_3 as u8;
     plaintext_3.content[offset_cred + 1..][..mac_3.len()].copy_from_slice(&mac_3[..]);
@@ -801,7 +766,7 @@ fn decrypt_message_3(
 // output must hold id_cred.len() + cred.len()
 fn encode_kdf_context(
     c_r: Option<ConnId>, // only present for MAC_2
-    id_cred: &BytesIdCred,
+    id_cred: &[u8],
     th: &BytesHashLen,
     cred: &[u8],
     ead: &Option<EADItem>,
@@ -817,7 +782,7 @@ fn encode_kdf_context(
     } else {
         0 // no u8 encoded
     };
-    output[output_len..output_len + id_cred.len()].copy_from_slice(&id_cred[..]);
+    output[output_len..output_len + id_cred.len()].copy_from_slice(&id_cred);
     output[output_len + id_cred.len()] = CBOR_BYTE_STRING;
     output[output_len + id_cred.len() + 1] = SHA256_DIGEST_LEN as u8;
     output[output_len + id_cred.len() + 2..output_len + id_cred.len() + 2 + th.len()]
@@ -843,7 +808,7 @@ fn compute_mac_3(
     crypto: &mut impl CryptoTrait,
     prk_4e3m: &BytesHashLen,
     th_3: &BytesHashLen,
-    id_cred_i: &BytesIdCred,
+    id_cred_i: &[u8],
     cred_i: &[u8],
     ead_3: &Option<EADItem>,
 ) -> BytesMac3 {
@@ -869,7 +834,7 @@ fn compute_mac_2(
     crypto: &mut impl CryptoTrait,
     prk_3e2m: &BytesHashLen,
     c_r: ConnId,
-    id_cred_r: &BytesIdCred,
+    id_cred_r: &[u8],
     cred_r: &[u8],
     th_2: &BytesHashLen,
     ead_2: &Option<EADItem>,
@@ -888,7 +853,7 @@ fn compute_mac_2(
 
 fn encode_plaintext_2(
     c_r: ConnId,
-    id_cred_r: &IdCred,
+    id_cred_r: &[u8],
     mac_2: &BytesMac2,
     ead_2: &Option<EADItem>,
 ) -> Result<BufferPlaintext2, EDHOCError> {
@@ -898,7 +863,10 @@ fn encode_plaintext_2(
     plaintext_2
         .extend_from_slice(c_r)
         .or(Err(EDHOCError::EncodingError))?;
-    id_cred_r.write_to_message(&mut plaintext_2)?;
+    // id_cred_r.write_to_message(&mut plaintext_2)?;
+    plaintext_2
+        .extend_from_slice(id_cred_r)
+        .or(Err(EDHOCError::EncodingError))?;
     let offset_cred = plaintext_2.len;
 
     plaintext_2.content[offset_cred] = CBOR_MAJOR_BYTE_STRING | MAC_LENGTH_2 as u8;
@@ -1040,7 +1008,7 @@ mod tests {
 
     // message_1 (first_time)
     const METHOD_TV_FIRST_TIME: u8 = 0x03;
-    const SUITES_I_TV_FIRST_TIME: BytesSuites = hex!("060000000000000000");
+    const SUITES_I_TV_FIRST_TIME: &str = "06";
     const G_X_TV_FIRST_TIME: BytesP256ElemLen =
         hex!("741a13d7ba048fbb615e94386aa3b61bea5b3d8f65f32620b749bee8d278efa9");
     const C_I_TV_FIRST_TIME: ConnId = ConnId::from_int_raw(0x0e);
@@ -1050,7 +1018,7 @@ mod tests {
     // message_1 (second time)
     const METHOD_TV: u8 = 0x03;
     // manually modified test vector to include a single supported cipher suite
-    const SUITES_I_TV: BytesSuites = hex!("060200000000000000");
+    const SUITES_I_TV: &str = "0602";
     const G_X_TV: BytesP256ElemLen =
         hex!("8af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6");
     const C_I_TV: ConnId = ConnId::from_int_raw(0x37);
@@ -1148,16 +1116,9 @@ mod tests {
 
     #[test]
     fn test_encode_message_1() {
-        let suites_i_tv_len: usize = 2;
-        let message_1 = encode_message_1(
-            METHOD_TV,
-            &SUITES_I_TV,
-            suites_i_tv_len,
-            &G_X_TV,
-            C_I_TV,
-            &None::<EADItem>,
-        )
-        .unwrap();
+        let suites_i_tv = EdhocBuffer::from_hex(SUITES_I_TV);
+        let message_1 =
+            encode_message_1(METHOD_TV, &suites_i_tv, &G_X_TV, C_I_TV, &None::<EADItem>).unwrap();
 
         assert_eq!(message_1.len, 39);
         assert_eq!(message_1, BufferMessage1::from_hex(MESSAGE_1_TV));
@@ -1166,31 +1127,29 @@ mod tests {
     #[test]
     fn test_parse_suites_i() {
         let message_1_tv = BufferMessage1::from_hex(MESSAGE_1_TV);
+        let suites_i_tv = EdhocBuffer::from_hex(SUITES_I_TV);
         // skip the fist byte (method)
         let decoder = CBORDecoder::new(&message_1_tv.content[1..message_1_tv.len]);
         let res = parse_suites_i(decoder);
         assert!(res.is_ok());
-        let (suites_i, _suites_i_len, _decoder) = res.unwrap();
-        assert_eq!(suites_i, SUITES_I_TV);
+        let (suites_i, _decoder) = res.unwrap();
+        assert_eq!(suites_i, suites_i_tv);
 
         let message_1_tv = BufferMessage1::from_hex(MESSAGE_1_TV_SUITE_ONLY_A);
         // skip the fist byte (method)
         let decoder = CBORDecoder::new(&message_1_tv.content[1..message_1_tv.len]);
         let res = parse_suites_i(decoder);
         assert!(res.is_ok());
-        let (suites_i, _suites_i_len, _decoder) = res.unwrap();
+        let (suites_i, _decoder) = res.unwrap();
         assert_eq!(suites_i[0], 0x18);
 
-        // let (suites_i, suites_i_len, raw_suites_len) =
-        //     parse_suites_i(&BufferMessage1::from_hex(MESSAGE_1_TV_SUITE_ONLY_B)).unwrap();
-
         let message_1_tv = BufferMessage1::from_hex(MESSAGE_1_TV_SUITE_ONLY_B);
         // skip the fist byte (method)
         let decoder = CBORDecoder::new(&message_1_tv.content[1..message_1_tv.len]);
         let res = parse_suites_i(decoder);
         assert!(res.is_ok());
-        let (suites_i, suites_i_len, _decoder) = res.unwrap();
-        assert_eq!(suites_i_len, 2);
+        let (suites_i, _decoder) = res.unwrap();
+        assert_eq!(suites_i.len(), 2);
         assert_eq!(suites_i[0], 0x02);
         assert_eq!(suites_i[1], 0x01);
 
@@ -1199,8 +1158,8 @@ mod tests {
         let decoder = CBORDecoder::new(&message_1_tv.content[1..message_1_tv.len]);
         let res = parse_suites_i(decoder);
         assert!(res.is_ok());
-        let (suites_i, suites_i_len, _decoder) = res.unwrap();
-        assert_eq!(suites_i_len, 2);
+        let (suites_i, _decoder) = res.unwrap();
+        assert_eq!(suites_i.len(), 2);
         assert_eq!(suites_i[0], 0x02);
         assert_eq!(suites_i[1], 0x19);
 
@@ -1215,14 +1174,16 @@ mod tests {
     fn test_parse_message_1() {
         let message_1_tv_first_time = BufferMessage1::from_hex(MESSAGE_1_TV_FIRST_TIME);
         let message_1_tv = BufferMessage1::from_hex(MESSAGE_1_TV);
+        let suites_i_tv_first_time = EdhocBuffer::from_hex(SUITES_I_TV_FIRST_TIME);
+        let suites_i_tv = EdhocBuffer::from_hex(SUITES_I_TV);
 
         // first time message_1 parsing
         let res = parse_message_1(&message_1_tv_first_time);
         assert!(res.is_ok());
-        let (method, suites_i, _suites_i_len, g_x, c_i, ead_1) = res.unwrap();
+        let (method, suites_i, g_x, c_i, ead_1) = res.unwrap();
 
         assert_eq!(method, METHOD_TV_FIRST_TIME);
-        assert_eq!(suites_i, SUITES_I_TV_FIRST_TIME);
+        assert_eq!(suites_i, suites_i_tv_first_time);
         assert_eq!(g_x, G_X_TV_FIRST_TIME);
         assert_eq!(c_i, C_I_TV_FIRST_TIME);
         assert!(ead_1.is_none());
@@ -1230,10 +1191,10 @@ mod tests {
         // second time message_1
         let res = parse_message_1(&message_1_tv);
         assert!(res.is_ok());
-        let (method, suites_i, _suites_i_len, g_x, c_i, ead_1) = res.unwrap();
+        let (method, suites_i, g_x, c_i, ead_1) = res.unwrap();
 
         assert_eq!(method, METHOD_TV);
-        assert_eq!(suites_i, SUITES_I_TV);
+        assert_eq!(suites_i, suites_i_tv);
         assert_eq!(g_x, G_X_TV);
         assert_eq!(c_i, C_I_TV);
         assert!(ead_1.is_none());
@@ -1410,7 +1371,9 @@ mod tests {
         let plaintext_2_tv = BufferPlaintext2::from_hex(PLAINTEXT_2_TV);
         let plaintext_2 = encode_plaintext_2(
             C_R_TV,
-            &IdCred::CompactKid(ID_CRED_R_TV[ID_CRED_R_TV.len() - 1]),
+            IdCred::from_full_value(&ID_CRED_R_TV[..])
+                .unwrap()
+                .as_encoded_value(),
             &MAC_2_TV,
             &None::<EADItem>,
         )
@@ -1438,11 +1401,7 @@ mod tests {
         assert!(plaintext_2.is_ok());
         let (c_r, id_cred_r, mac_2, ead_2) = plaintext_2.unwrap();
         assert_eq!(c_r, C_R_TV);
-        let id_cred_r = match id_cred_r {
-            IdCred::CompactKid(id_cred_r) => id_cred_r,
-            _ => panic!("Invalid ID_CRED_R"),
-        };
-        assert_eq!(id_cred_r, ID_CRED_R_TV[3]);
+        assert_eq!(id_cred_r.as_full_value(), ID_CRED_R_TV);
         assert_eq!(mac_2, MAC_2_TV);
         assert!(ead_2.is_none());
     }
@@ -1495,26 +1454,25 @@ mod tests {
     #[test]
     fn test_encode_plaintext_3() {
         let plaintext_3_tv = BufferPlaintext3::from_hex(PLAINTEXT_3_TV);
-        let kid_tv = ID_CRED_I_TV[ID_CRED_I_TV.len() - 1];
-        let plaintext_3 =
-            encode_plaintext_3(&IdCred::CompactKid(kid_tv), &MAC_3_TV, &None::<EADItem>).unwrap();
+        let plaintext_3 = encode_plaintext_3(
+            IdCred::from_full_value(&ID_CRED_I_TV[..])
+                .unwrap()
+                .as_encoded_value(),
+            &MAC_3_TV,
+            &None::<EADItem>,
+        )
+        .unwrap();
         assert_eq!(plaintext_3, plaintext_3_tv);
     }
 
     #[test]
     fn test_decode_plaintext_3() {
         let plaintext_3_tv = BufferPlaintext3::from_hex(PLAINTEXT_3_TV);
-        let kid_tv = ID_CRED_I_TV[ID_CRED_I_TV.len() - 1];
 
         let (id_cred_i, mac_3, ead_3) = decode_plaintext_3(&plaintext_3_tv).unwrap();
 
-        let kid = match id_cred_i {
-            IdCred::CompactKid(id_cred_i) => id_cred_i,
-            _ => panic!("Invalid ID_CRED_I"),
-        };
-
         assert_eq!(mac_3, MAC_3_TV);
-        assert_eq!(kid, kid_tv);
+        assert_eq!(id_cred_i.as_full_value(), ID_CRED_I_TV);
         assert!(ead_3.is_none());
     }
 
@@ -1537,7 +1495,7 @@ mod tests {
     #[test]
     fn test_encode_message_with_ead_item() {
         let method_tv = METHOD_TV;
-        let suites_i_tv_len: usize = 2;
+        let suites_i_tv = EdhocBuffer::from_hex(SUITES_I_TV);
         let c_i_tv = C_I_TV;
         let message_1_ead_tv = BufferMessage1::from_hex(MESSAGE_1_WITH_DUMMY_CRITICAL_EAD_TV);
         let ead_item = EADItem {
@@ -1546,14 +1504,7 @@ mod tests {
             value: Some(EdhocMessageBuffer::from_hex(EAD_DUMMY_VALUE_TV)),
         };
 
-        let res = encode_message_1(
-            method_tv,
-            &SUITES_I_TV,
-            suites_i_tv_len,
-            &G_X_TV,
-            c_i_tv,
-            &Some(ead_item),
-        );
+        let res = encode_message_1(method_tv, &suites_i_tv, &G_X_TV, c_i_tv, &Some(ead_item));
         assert!(res.is_ok());
         let message_1 = res.unwrap();
 
@@ -1563,7 +1514,7 @@ mod tests {
     #[test]
     fn test_encode_message_with_large_ead_item() {
         let method_tv = METHOD_TV;
-        let suites_i_tv_len: usize = 2;
+        let suites_i_tv = EdhocBuffer::from_hex(SUITES_I_TV);
         let c_i_tv = C_I_TV;
 
         // the actual value will be zeroed since it doesn't matter in this test
@@ -1576,14 +1527,7 @@ mod tests {
             value: Some(ead_value),
         };
 
-        let res = encode_message_1(
-            method_tv,
-            &SUITES_I_TV,
-            suites_i_tv_len,
-            &G_X_TV,
-            c_i_tv,
-            &Some(ead_item),
-        );
+        let res = encode_message_1(method_tv, &suites_i_tv, &G_X_TV, c_i_tv, &Some(ead_item));
         assert_eq!(res.unwrap_err(), EDHOCError::EadTooLongError);
     }
 
@@ -1628,7 +1572,7 @@ mod tests {
 
         let res = parse_message_1(&message_1_ead_tv);
         assert!(res.is_ok());
-        let (_method, _suites_i, _suites_i_len, _g_x, _c_i, ead_1) = res.unwrap();
+        let (_method, _suites_i, _g_x, _c_i, ead_1) = res.unwrap();
         let ead_1 = ead_1.unwrap();
         assert!(ead_1.is_critical);
         assert_eq!(ead_1.label, EAD_DUMMY_LABEL_TV);
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index b2d6c481..4e0a8139 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -24,30 +24,35 @@ pub use lakers_ead_authz::*;
 mod 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.
 #[derive(Debug)]
 pub struct EdhocInitiator<Crypto: CryptoTrait> {
-    state: InitiatorStart, // opaque state
+    state: InitiatorStart,       // opaque state
+    i: Option<BytesP256ElemLen>, // static public key of myself
+    cred_i: Option<Credential>,
     crypto: Crypto,
 }
 
 #[derive(Debug)]
 pub struct EdhocInitiatorWaitM2<Crypto: CryptoTrait> {
     state: WaitM2, // opaque state
+    i: Option<BytesP256ElemLen>,
+    cred_i: Option<Credential>,
     crypto: Crypto,
 }
 
 #[derive(Debug)]
 pub struct EdhocInitiatorProcessingM2<Crypto: CryptoTrait> {
     state: ProcessingM2, // opaque state
+    i: Option<BytesP256ElemLen>,
+    cred_i: Option<Credential>,
     crypto: Crypto,
 }
 
 #[derive(Debug)]
 pub struct EdhocInitiatorProcessedM2<Crypto: CryptoTrait> {
-    state: ProcessedM2,    // opaque state
-    cred_i: CredentialRPK, // I's full credential
+    state: ProcessedM2, // opaque state
+    cred_i: Option<Credential>,
     crypto: Crypto,
 }
 
@@ -59,18 +64,18 @@ pub struct EdhocInitiatorDone<Crypto: CryptoTrait> {
 
 /// Starting point for performing EDHOC in the role of the Responder.
 #[derive(Debug)]
-pub struct EdhocResponder<'a, Crypto: CryptoTrait> {
+pub struct EdhocResponder<Crypto: CryptoTrait> {
     state: ResponderStart, // opaque state
-    r: &'a [u8],           // private authentication key of R
-    cred_r: CredentialRPK, // R's full credential
+    r: BytesP256ElemLen,   // private authentication key of R
+    cred_r: Credential,    // R's full credential
     crypto: Crypto,
 }
 
 #[derive(Debug)]
-pub struct EdhocResponderProcessedM1<'a, Crypto: CryptoTrait> {
-    state: ProcessingM1,   // opaque state
-    r: &'a [u8],           // private authentication key of R
-    cred_r: CredentialRPK, // R's full credential
+pub struct EdhocResponderProcessedM1<Crypto: CryptoTrait> {
+    state: ProcessingM1, // opaque state
+    r: BytesP256ElemLen, // private authentication key of R
+    cred_r: Credential,  // R's full credential
     crypto: Crypto,
 }
 
@@ -92,14 +97,22 @@ pub struct EdhocResponderDone<Crypto: CryptoTrait> {
     crypto: Crypto,
 }
 
-impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> {
-    pub fn new(mut crypto: Crypto, r: &'a [u8], cred_r: CredentialRPK) -> Self {
-        trace!("Initializing EdhocInitiator");
-        assert!(r.len() == P256_ELEM_LEN);
+impl<Crypto: CryptoTrait> EdhocResponder<Crypto> {
+    pub fn new(
+        mut crypto: Crypto,
+        method: EDHOCMethod,
+        r: BytesP256ElemLen,
+        cred_r: Credential,
+    ) -> Self {
+        trace!("Initializing EdhocResponder");
         let (y, g_y) = crypto.p256_generate_key_pair();
 
         EdhocResponder {
-            state: ResponderStart { y, g_y },
+            state: ResponderStart {
+                y,
+                g_y,
+                method: method.into(),
+            },
             r,
             cred_r,
             crypto,
@@ -109,14 +122,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> {
     pub fn process_message_1(
         mut self,
         message_1: &BufferMessage1,
-    ) -> Result<
-        (
-            EdhocResponderProcessedM1<'a, Crypto>,
-            ConnId,
-            Option<EADItem>,
-        ),
-        EDHOCError,
-    > {
+    ) -> Result<(EdhocResponderProcessedM1<Crypto>, ConnId, Option<EADItem>), EDHOCError> {
         trace!("Enter process_message_1");
         let (state, c_i, ead_1) = r_process_message_1(&self.state, &mut self.crypto, message_1)?;
 
@@ -133,7 +139,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> {
     }
 }
 
-impl<'a, Crypto: CryptoTrait> EdhocResponderProcessedM1<'a, Crypto> {
+impl<Crypto: CryptoTrait> EdhocResponderProcessedM1<Crypto> {
     pub fn prepare_message_2(
         mut self,
         cred_transfer: CredentialTransfer,
@@ -150,7 +156,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderProcessedM1<'a, Crypto> {
             &self.state,
             &mut self.crypto,
             self.cred_r,
-            self.r.try_into().expect("Wrong length of private key"),
+            &self.r,
             c_r,
             cred_transfer,
             ead_2,
@@ -171,14 +177,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3<Crypto> {
     pub fn parse_message_3(
         mut self,
         message_3: &'a BufferMessage3,
-    ) -> Result<
-        (
-            EdhocResponderProcessingM3<Crypto>,
-            CredentialRPK,
-            Option<EADItem>,
-        ),
-        EDHOCError,
-    > {
+    ) -> Result<(EdhocResponderProcessingM3<Crypto>, IdCred, Option<EADItem>), EDHOCError> {
         trace!("Enter parse_message_3");
         match r_parse_message_3(&mut self.state, &mut self.crypto, message_3) {
             Ok((state, id_cred_i, ead_3)) => Ok((
@@ -197,7 +196,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3<Crypto> {
 impl<'a, Crypto: CryptoTrait> EdhocResponderProcessingM3<Crypto> {
     pub fn verify_message_3(
         mut self,
-        cred_i: CredentialRPK,
+        cred_i: Credential,
     ) -> Result<(EdhocResponderDone<Crypto>, [u8; SHA256_DIGEST_LEN]), EDHOCError> {
         trace!("Enter verify_message_3");
         match r_verify_message_3(&mut self.state, &mut self.crypto, cred_i) {
@@ -247,24 +246,29 @@ impl<Crypto: CryptoTrait> EdhocResponderDone<Crypto> {
 }
 
 impl<'a, Crypto: CryptoTrait> EdhocInitiator<Crypto> {
-    pub fn new(mut crypto: Crypto) -> Self {
+    pub fn new(mut crypto: Crypto, method: EDHOCMethod, selected_suite: EDHOCSuite) -> Self {
         trace!("Initializing EdhocInitiator");
-        // 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 suites_i = prepare_suites_i(&crypto.supported_suites(), selected_suite.into()).unwrap();
         let (x, g_x) = crypto.p256_generate_key_pair();
+
         EdhocInitiator {
             state: InitiatorStart {
                 x,
                 g_x,
+                method: method.into(),
                 suites_i,
-                suites_i_len,
             },
+            i: None,
+            cred_i: None,
             crypto,
         }
     }
 
+    pub fn set_identity(&mut self, i: BytesP256ElemLen, cred_i: Credential) {
+        self.i = Some(i);
+        self.cred_i = Some(cred_i);
+    }
+
     pub fn prepare_message_1(
         mut self,
         c_i: Option<ConnId>,
@@ -280,6 +284,8 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator<Crypto> {
             Ok((state, message_1)) => Ok((
                 EdhocInitiatorWaitM2 {
                     state,
+                    i: self.i,
+                    cred_i: self.cred_i,
                     crypto: self.crypto,
                 },
                 message_1,
@@ -293,7 +299,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator<Crypto> {
     }
 
     pub fn selected_cipher_suite(&self) -> u8 {
-        self.state.suites_i[self.state.suites_i_len - 1]
+        self.state.suites_i[self.state.suites_i.len - 1]
     }
 }
 
@@ -305,7 +311,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2<Crypto> {
         (
             EdhocInitiatorProcessingM2<Crypto>,
             ConnId,
-            CredentialRPK,
+            IdCred,
             Option<EADItem>,
         ),
         EDHOCError,
@@ -315,6 +321,8 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2<Crypto> {
             Ok((state, c_r, id_cred_r, ead_2)) => Ok((
                 EdhocInitiatorProcessingM2 {
                     state,
+                    i: self.i,
+                    cred_i: self.cred_i,
                     crypto: self.crypto,
                 },
                 c_r,
@@ -327,22 +335,31 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2<Crypto> {
 }
 
 impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessingM2<Crypto> {
+    pub fn set_identity(
+        &mut self,
+        i: BytesP256ElemLen,
+        cred_i: Credential,
+    ) -> Result<(), EDHOCError> {
+        if self.i.is_some() || self.cred_i.is_some() {
+            return Err(EDHOCError::IdentityAlreadySet);
+        }
+        self.i = Some(i);
+        self.cred_i = Some(cred_i);
+        Ok(())
+    }
+
     pub fn verify_message_2(
         mut self,
-        i: &'a [u8],
-        cred_i: CredentialRPK,
-        valid_cred_r: CredentialRPK,
+        valid_cred_r: Credential,
     ) -> Result<EdhocInitiatorProcessedM2<Crypto>, EDHOCError> {
         trace!("Enter verify_message_2");
-        match i_verify_message_2(
-            &self.state,
-            &mut self.crypto,
-            valid_cred_r,
-            i.try_into().expect("Wrong length of initiator private key"),
-        ) {
+        let Some(i) = self.i else {
+            return Err(EDHOCError::MissingIdentity);
+        };
+        match i_verify_message_2(&self.state, &mut self.crypto, valid_cred_r, &i) {
             Ok(state) => Ok(EdhocInitiatorProcessedM2 {
                 state,
-                cred_i: cred_i,
+                cred_i: self.cred_i,
                 crypto: self.crypto,
             }),
             Err(error) => Err(error),
@@ -364,10 +381,13 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessedM2<Crypto> {
         EDHOCError,
     > {
         trace!("Enter prepare_message_3");
+        let Some(cred_i) = self.cred_i else {
+            return Err(EDHOCError::MissingIdentity);
+        };
         match i_prepare_message_3(
             &mut self.state,
             &mut self.crypto,
-            self.cred_i,
+            cred_i,
             cred_transfer,
             ead_3,
         ) {
@@ -440,9 +460,9 @@ pub fn generate_connection_identifier<Crypto: CryptoTrait>(crypto: &mut Crypto)
 
 // Implements auth credential checking according to draft-tiloca-lake-implem-cons
 pub fn credential_check_or_fetch(
-    cred_expected: Option<CredentialRPK>,
-    id_cred_received: CredentialRPK,
-) -> Result<CredentialRPK, EDHOCError> {
+    cred_expected: Option<Credential>,
+    id_cred_received: IdCred,
+) -> Result<Credential, EDHOCError> {
     trace!("Enter credential_check_or_fetch");
     // Processing of auth credentials according to draft-tiloca-lake-implem-cons
     // Comments tagged with a number refer to steps in Section 4.3.1. of draft-tiloca-lake-implem-cons
@@ -451,9 +471,9 @@ pub fn credential_check_or_fetch(
         // IMPL: compare cred_i_expected with id_cred
         //   IMPL: assume cred_i_expected is well formed
         let credentials_match = if id_cred_received.reference_only() {
-            id_cred_received.kid == cred_expected.kid
+            id_cred_received.as_full_value() == cred_expected.by_kid()?.as_full_value()
         } else {
-            id_cred_received.value == cred_expected.value
+            id_cred_received.as_full_value() == cred_expected.by_value()?.as_full_value()
         };
 
         // 2. Is this authentication credential still valid?
@@ -475,14 +495,16 @@ pub fn credential_check_or_fetch(
         // 4. Is the trust model Pre-knowledge + TOFU? YES (hardcoded to YES for now)
         // 6. Validate CRED_X. Generally a CCS has to be validated only syntactically and semantically, unlike a certificate or a CWT.
         //    Is the validation successful?
-        // IMPL,NOTE: the credential has already been parsed with CredentialRPK::new in the *_parse_message_* function
         // 5. Is the authentication credential authorized for use in the context of this EDHOC session?
         // IMPL,TODO: we just skip this step for now
         // 7. Store CRED_X as valid and trusted.
         //   Pair it with consistent credential identifiers, for each supported type of credential identifier.
 
-        assert!(!id_cred_received.reference_only());
-        Ok(id_cred_received)
+        if let Some(cred) = id_cred_received.get_ccs() {
+            Ok(cred)
+        } else {
+            Err(EDHOCError::ParsingError)
+        }
     }
 
     // 8. Is this authentication credential good to use in the context of this EDHOC session?
@@ -493,8 +515,6 @@ pub fn credential_check_or_fetch(
 mod test_vectors_common {
     use hexlit::hex;
 
-    pub const ID_CRED_I: &[u8] = &hex!("a104412b");
-    pub const ID_CRED_R: &[u8] = &hex!("a104410a");
     pub const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
     pub const I: &[u8] = &hex!("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b");
     pub const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac");
@@ -516,21 +536,30 @@ mod test {
 
     #[test]
     fn test_new_initiator() {
-        let _initiator = EdhocInitiator::new(default_crypto());
+        let _initiator = EdhocInitiator::new(
+            default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
     }
 
     #[test]
     fn test_new_responder() {
         let _responder = EdhocResponder::new(
             default_crypto(),
-            R,
-            CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(),
+            EDHOCMethod::StatStat,
+            R.try_into().expect("Wrong length of responder private key"),
+            Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(),
         );
     }
 
     #[test]
     fn test_prepare_message_1() {
-        let initiator = EdhocInitiator::new(default_crypto());
+        let initiator = EdhocInitiator::new(
+            default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
 
         let c_i = generate_connection_identifier_cbor(&mut default_crypto());
         let result = initiator.prepare_message_1(Some(c_i), &None);
@@ -543,8 +572,9 @@ mod test {
         let message_1_tv = EdhocMessageBuffer::from_hex(MESSAGE_1_TV);
         let responder = EdhocResponder::new(
             default_crypto(),
-            R,
-            CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(),
+            EDHOCMethod::StatStat,
+            R.try_into().expect("Wrong length of responder private key"),
+            Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(),
         );
 
         // process message_1 first time, when unsupported suite is selected
@@ -556,8 +586,9 @@ mod test {
         // responder or initiator
         let responder = EdhocResponder::new(
             default_crypto(),
-            R,
-            CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(),
+            EDHOCMethod::StatStat,
+            R.try_into().expect("Wrong length of responder private key"),
+            Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(),
         );
 
         // process message_1 second time
@@ -574,11 +605,21 @@ mod test {
     #[cfg(feature = "test-ead-none")]
     #[test]
     fn test_handshake() {
-        let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap();
-        let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap();
+        let cred_i = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap();
+        let cred_r = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap();
 
-        let initiator = EdhocInitiator::new(default_crypto()); // can choose which identity to use after learning R's identity
-        let responder = EdhocResponder::new(default_crypto(), R, cred_r.clone()); // has to select an identity before learning who is I
+        let initiator = EdhocInitiator::new(
+            default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
+
+        let responder = EdhocResponder::new(
+            default_crypto(),
+            EDHOCMethod::StatStat,
+            R.try_into().expect("Wrong length of responder private key"),
+            cred_r.clone(),
+        ); // has to select an identity before learning who is I
 
         // ---- begin initiator handling
         // if needed: prepare ead_1
@@ -595,9 +636,16 @@ mod test {
         // ---- end responder handling
 
         // ---- being initiator handling
-        let (initiator, _c_r, id_cred_r, _ead_2) = initiator.parse_message_2(&message_2).unwrap();
+        let (mut initiator, _c_r, id_cred_r, _ead_2) =
+            initiator.parse_message_2(&message_2).unwrap();
         let valid_cred_r = credential_check_or_fetch(Some(cred_r), id_cred_r).unwrap();
-        let initiator = initiator.verify_message_2(I, cred_i, valid_cred_r).unwrap();
+        initiator
+            .set_identity(
+                I.try_into().expect("Wrong length of initiator private key"),
+                cred_i.clone(),
+            )
+            .unwrap(); // exposing own identity only after validating cred_r
+        let initiator = initiator.verify_message_2(valid_cred_r).unwrap();
 
         // if needed: prepare ead_3
         let (mut initiator, message_3, i_prk_out) = initiator
@@ -660,11 +708,11 @@ mod test_authz {
     // TODO: have a setup_test function that prepares the common objects for the ead tests
     #[test]
     fn test_handshake_authz() {
-        let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap();
-        let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap();
+        let cred_i = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap();
+        let cred_r = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap();
 
-        let mock_fetch_cred_i = |id_cred_i: CredentialRPK| -> Result<CredentialRPK, EDHOCError> {
-            if id_cred_i.kid == cred_i.kid {
+        let mock_fetch_cred_i = |id_cred_i: IdCred| -> Result<Credential, EDHOCError> {
+            if id_cred_i.as_full_value() == cred_i.by_kid()?.as_full_value() {
                 Ok(cred_i.clone())
             } else {
                 Err(EDHOCError::UnexpectedCredential)
@@ -672,8 +720,17 @@ mod test_authz {
         };
 
         // ==== initialize edhoc ====
-        let mut initiator = EdhocInitiator::new(default_crypto());
-        let responder = EdhocResponder::new(default_crypto(), R, cred_r);
+        let mut initiator = EdhocInitiator::new(
+            default_crypto(),
+            EDHOCMethod::StatStat,
+            EDHOCSuite::CipherSuite2,
+        );
+        let responder = EdhocResponder::new(
+            default_crypto(),
+            EDHOCMethod::StatStat,
+            R.try_into().expect("Wrong length of responder private key"),
+            cred_r.clone(),
+        );
 
         // ==== initialize ead-authz ====
         let device = ZeroTouchDevice::new(
@@ -683,7 +740,8 @@ mod test_authz {
         );
         let authenticator = ZeroTouchAuthenticator::default();
 
-        let acl = EdhocMessageBuffer::new_from_slice(&[cred_i.kid]).unwrap();
+        let single_byte_kid = cred_i.kid.as_ref().unwrap()[0]; // FIXME: add longer kid support in ACL
+        let acl = EdhocMessageBuffer::new_from_slice(&[single_byte_kid]).unwrap();
         let server = ZeroTouchServer::new(
             W_TV.try_into().unwrap(),
             CRED_R.try_into().unwrap(),
@@ -720,13 +778,20 @@ mod test_authz {
             .prepare_message_2(CredentialTransfer::ByValue, None, &ead_2)
             .unwrap();
 
-        let (initiator, _c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap();
+        let (mut initiator, _c_r, id_cred_r, ead_2) =
+            initiator.parse_message_2(&message_2).unwrap();
         let valid_cred_r = credential_check_or_fetch(None, id_cred_r).unwrap();
         if let Some(ead_2) = ead_2 {
             let result = device.process_ead_2(&mut default_crypto(), ead_2, CRED_R);
             assert!(result.is_ok());
         }
-        let initiator = initiator.verify_message_2(I, cred_i, valid_cred_r).unwrap();
+        initiator
+            .set_identity(
+                I.try_into().expect("Wrong length of initiator private key"),
+                cred_i.clone(),
+            )
+            .unwrap();
+        let initiator = initiator.verify_message_2(valid_cred_r).unwrap();
 
         let (mut _initiator, message_3, i_prk_out) = initiator
             .prepare_message_3(CredentialTransfer::ByReference, &None)
@@ -736,7 +801,7 @@ mod test_authz {
         let valid_cred_i = if id_cred_i.reference_only() {
             mock_fetch_cred_i(id_cred_i).unwrap()
         } else {
-            id_cred_i
+            id_cred_i.get_ccs().unwrap()
         };
         let (mut _responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap();
 
diff --git a/shared/Cargo.toml b/shared/Cargo.toml
index 1221837a..68d86502 100644
--- a/shared/Cargo.toml
+++ b/shared/Cargo.toml
@@ -20,6 +20,7 @@ defmt = { version = "0.3", optional = true }
 
 [dev-dependencies]
 hexlit = "0.5.3"
+rstest = "0.21.0"
 
 [features]
 default = [  ]
diff --git a/shared/cbindgen.toml b/shared/cbindgen.toml
index e3392a2a..ff11b562 100644
--- a/shared/cbindgen.toml
+++ b/shared/cbindgen.toml
@@ -17,7 +17,10 @@ cpp_compat = true
 [export]
 include = [
     "EdhocMessageBuffer", "BytesMac", "BytesMac2",
-    "EADItemC", "CredentialRPK", "CredentialTransfer",
+    "EADItemC",
+    "EdhocBuffer", "BufferKid", "BufferCred", "BufferIdCred",
+    "CredentialKey", "CredentialType", "IdCred",
+    "CredentialTransfer",
     "InitiatorStart", "WaitM2", "Completed", "ProcessedM2",
     "EdhocInitiatorC", "EdhocInitiatorWaitM2C", "EdhocInitiatorProcessingM2C", "EdhocInitiatorProcessedM2C", "EdhocInitiatorDoneC",
 ]
diff --git a/shared/src/buffer.rs b/shared/src/buffer.rs
new file mode 100644
index 00000000..b60078b8
--- /dev/null
+++ b/shared/src/buffer.rs
@@ -0,0 +1,163 @@
+use core::ops::Index;
+
+// NOTE: This constant is only here for now because it is only ever used in instances of EdhocBuffer.
+// TODO: move to lib.rs, once EdhocMessageBuffer is replaced by EdhocBuffer.
+pub const MAX_SUITES_LEN: usize = 9;
+
+#[derive(PartialEq, Debug)]
+#[repr(C)]
+pub enum EdhocBufferError {
+    BufferAlreadyFull,
+    SliceTooLong,
+}
+
+/// A fixed-size (but parameterized) buffer for EDHOC messages.
+///
+/// Trying to have an API as similar as possible to `heapless::Vec`,
+/// so that in the future it can be hot-swappable by the application.
+// TODO: replace EdhocMessageBuffer with EdhocBuffer all over the library
+// NOTE: how would this const generic thing work across the C and Python bindings?
+#[derive(PartialEq, Debug, Copy, Clone)]
+#[repr(C)]
+pub struct EdhocBuffer<const N: usize> {
+    pub content: [u8; N],
+    pub len: usize,
+}
+
+impl<const N: usize> Default for EdhocBuffer<N> {
+    fn default() -> Self {
+        EdhocBuffer {
+            content: [0; N],
+            len: 0,
+        }
+    }
+}
+
+impl<const N: usize> EdhocBuffer<N> {
+    pub const fn new() -> Self {
+        EdhocBuffer {
+            content: [0u8; N],
+            len: 0,
+        }
+    }
+
+    pub fn len(&self) -> usize {
+        self.len
+    }
+
+    pub fn capacity(&self) -> usize {
+        N
+    }
+
+    pub fn new_from_slice(slice: &[u8]) -> Result<Self, EdhocBufferError> {
+        let mut buffer = Self::new();
+        if buffer.fill_with_slice(slice).is_ok() {
+            Ok(buffer)
+        } else {
+            Err(EdhocBufferError::SliceTooLong)
+        }
+    }
+
+    pub fn get(self, index: usize) -> Option<u8> {
+        self.content.get(index).copied()
+    }
+
+    pub fn contains(&self, item: &u8) -> bool {
+        self.content.contains(item)
+    }
+
+    pub fn push(&mut self, item: u8) -> Result<(), EdhocBufferError> {
+        if self.len < self.content.len() {
+            self.content[self.len] = item;
+            self.len += 1;
+            Ok(())
+        } else {
+            Err(EdhocBufferError::BufferAlreadyFull)
+        }
+    }
+
+    pub fn get_slice(&self, start: usize, len: usize) -> Option<&[u8]> {
+        self.content.get(start..start + len)
+    }
+
+    pub fn as_slice(&self) -> &[u8] {
+        &self.content[0..self.len]
+    }
+
+    pub fn fill_with_slice(&mut self, slice: &[u8]) -> Result<(), EdhocBufferError> {
+        if slice.len() <= self.content.len() {
+            self.len = slice.len();
+            self.content[..self.len].copy_from_slice(slice);
+            Ok(())
+        } else {
+            Err(EdhocBufferError::SliceTooLong)
+        }
+    }
+
+    pub fn extend_from_slice(&mut self, slice: &[u8]) -> Result<(), EdhocBufferError> {
+        if self.len + slice.len() <= self.content.len() {
+            self.content[self.len..self.len + slice.len()].copy_from_slice(slice);
+            self.len += slice.len();
+            Ok(())
+        } else {
+            Err(EdhocBufferError::SliceTooLong)
+        }
+    }
+
+    // so far only used in test contexts
+    pub fn from_hex(hex: &str) -> Self {
+        let mut buffer = EdhocBuffer::new();
+        buffer.len = hex.len() / 2;
+        for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
+            let chunk_str = core::str::from_utf8(chunk).unwrap();
+            buffer.content[i] = u8::from_str_radix(chunk_str, 16).unwrap();
+        }
+        buffer
+    }
+}
+
+impl<const N: usize> Index<usize> for EdhocBuffer<N> {
+    type Output = u8;
+    fn index(&self, item: usize) -> &Self::Output {
+        &self.content[item]
+    }
+}
+
+impl<const N: usize> TryInto<EdhocBuffer<N>> for &[u8] {
+    type Error = ();
+
+    fn try_into(self) -> Result<EdhocBuffer<N>, Self::Error> {
+        let mut buffer = [0u8; N];
+        if self.len() <= buffer.len() {
+            buffer[..self.len()].copy_from_slice(self);
+
+            Ok(EdhocBuffer {
+                content: buffer,
+                len: self.len(),
+            })
+        } else {
+            Err(())
+        }
+    }
+}
+
+mod test {
+
+    #[test]
+    fn test_edhoc_buffer() {
+        let mut buffer = crate::EdhocBuffer::<5>::new();
+        assert_eq!(buffer.len, 0);
+        assert_eq!(buffer.content, [0; 5]);
+
+        buffer.push(1).unwrap();
+        assert_eq!(buffer.len, 1);
+        assert_eq!(buffer.content, [1, 0, 0, 0, 0]);
+    }
+
+    #[test]
+    fn test_new_from_slice() {
+        let buffer = crate::EdhocBuffer::<5>::new_from_slice(&[1, 2, 3]).unwrap();
+        assert_eq!(buffer.len, 3);
+        assert_eq!(buffer.content, [1, 2, 3, 0, 0]);
+    }
+}
diff --git a/shared/src/cred.rs b/shared/src/cred.rs
index b82d279e..393816c9 100644
--- a/shared/src/cred.rs
+++ b/shared/src/cred.rs
@@ -1,39 +1,205 @@
 use super::*;
 
-#[derive(Clone, Copy, Debug)]
-#[cfg_attr(feature = "python-bindings", pyclass)]
+pub type BufferCred = EdhocBuffer<192>; // arbitrary size
+pub type BufferKid = EdhocBuffer<16>; // variable size, up to 16 bytes
+pub type BufferIdCred = EdhocBuffer<192>; // variable size, can contain either the contents of a BufferCred or a BufferKid
+pub type BytesKeyAES128 = [u8; 16];
+pub type BytesKeyEC2 = [u8; 32];
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[repr(C)]
+pub enum CredentialKey {
+    Symmetric(BytesKeyAES128),
+    EC2Compact(BytesKeyEC2),
+    // Add other key types as needed
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[repr(C)]
+pub enum CredentialType {
+    CCS,
+    #[allow(non_camel_case_types)]
+    CCS_PSK,
+    // Add other credential types as needed
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum IdCredType {
+    KID = 4,
+    KCCS = 14,
+}
+
+impl From<u8> for IdCredType {
+    fn from(value: u8) -> Self {
+        match value {
+            4 => IdCredType::KID,
+            14 => IdCredType::KCCS,
+            _ => panic!("Invalid IdCredType"),
+        }
+    }
+}
+
+/// A value of ID_CRED_x: a credential identifier
+///
+/// Possible values include key IDs, credentials by value and others.
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
 #[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
-    pub kid: u8,
+pub struct IdCred {
+    /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary;
+    /// while this technically wastes two bytes, it has the convenient property of having the full
+    /// value available as a slice.
+    pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid
 }
 
-impl CredentialRPK {
-    pub fn new(value: EdhocMessageBuffer) -> Result<Self, EDHOCError> {
-        let (public_key, kid) = Self::parse(value.as_slice())?;
+impl IdCred {
+    pub fn new() -> Self {
+        Self {
+            bytes: BufferIdCred::new(),
+        }
+    }
+
+    pub fn from_full_value(value: &[u8]) -> Result<Self, EDHOCError> {
         Ok(Self {
-            value,
-            public_key,
-            kid,
+            bytes: BufferIdCred::new_from_slice(value)
+                .map_err(|_| EDHOCError::CredentialTooLongError)?,
         })
     }
 
+    /// Instantiate an IdCred from an encoded value.
+    pub fn from_encoded_value(value: &[u8]) -> Result<Self, EDHOCError> {
+        let bytes = match value {
+            // kid that has been encoded as CBOR integer
+            &[x] if Self::bstr_representable_as_int(x) => {
+                BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x])
+                    .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: how to avoid map_err overuse?
+            }
+            // kid that has been encoded as CBOR byte string
+            &[0x41, x, ..] if !Self::bstr_representable_as_int(x) => {
+                let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL])
+                    .map_err(|_| EDHOCError::CredentialTooLongError)?;
+                bytes
+                    .extend_from_slice(value)
+                    .map_err(|_| EDHOCError::CredentialTooLongError)?;
+                bytes
+            }
+            // CCS by value
+            &[0xa1, KCSS_LABEL, ..] => BufferIdCred::new_from_slice(value)
+                .map_err(|_| EDHOCError::CredentialTooLongError)?,
+            _ => return Err(EDHOCError::ParsingError),
+        };
+
+        Ok(Self { bytes })
+    }
+
+    /// View the full value of the ID_CRED_x: the CBOR encoding of a 1-element CBOR map
+    ///
+    /// This is the value that is used when ID_CRED_x has no impact on message size, see RFC 9528 Section 3.5.3.2.
+    pub fn as_full_value(&self) -> &[u8] {
+        self.bytes.as_slice()
+    }
+
+    /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3.
+    ///
+    /// Note that this is NOT doing CBOR encoding, it is rather performing (when applicable)
+    /// the compact encoding of ID_CRED fields.
+    /// This style of encoding is used when ID_CRED_x has an impact on message size.
+    pub fn as_encoded_value(&self) -> &[u8] {
+        match self.bytes.as_slice() {
+            [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => {
+                &self.bytes.as_slice()[3..]
+            }
+            [0xa1, KID_LABEL, ..] => &self.bytes.as_slice()[2..],
+            _ => self.bytes.as_slice(),
+        }
+    }
+
     pub fn reference_only(&self) -> bool {
-        self.value.len == 0
+        [IdCredType::KID].contains(&self.item_type())
     }
 
-    pub fn get_id_cred(&self) -> BytesIdCred {
-        [0xa1, 0x04, 0x41, self.kid] // cbor map = {4: kid}
+    pub fn item_type(&self) -> IdCredType {
+        self.bytes.as_slice()[1].into()
     }
 
-    fn parse(cred: &[u8]) -> Result<(BytesP256ElemLen, u8), EDHOCError> {
-        // NOTE: this routine is only guaranteed to work with credentials from RFC9529
+    pub fn get_ccs(&self) -> Option<Credential> {
+        if self.item_type() == IdCredType::KCCS {
+            Credential::parse_ccs(&self.bytes.as_slice()[2..]).ok()
+        } else {
+            None
+        }
+    }
+
+    fn bstr_representable_as_int(value: u8) -> bool {
+        (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value)
+    }
+}
+
+/// A credential for use in EDHOC
+///
+/// For now supports CCS credentials only.
+/// Experimental support for CCS_PSK credentials is also available.
+// TODO: add back support for C and Python bindings
+#[cfg_attr(feature = "python-bindings", pyclass)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[repr(C)]
+pub struct Credential {
+    /// Original bytes of the credential, CBOR-encoded
+    ///
+    /// If the credential is a CCS, it contains an encoded CBOR map containnig
+    /// a COSE_Key in a cnf claim, see RFC 9528 Section 3.5.2.
+    pub bytes: BufferCred,
+    pub key: CredentialKey,
+    pub kid: Option<BufferKid>, // other types of identifiers can be added, such as `pub x5t: Option<BytesX5T>`
+    pub cred_type: CredentialType,
+}
+
+impl Credential {
+    /// Creates a new CCS credential with the given bytes and public key
+    pub fn new_ccs(bytes: BufferCred, public_key: BytesKeyEC2) -> Self {
+        Self {
+            bytes,
+            key: CredentialKey::EC2Compact(public_key),
+            kid: None,
+            cred_type: CredentialType::CCS,
+        }
+    }
+
+    /// Creates a new CCS credential with the given bytes and a pre-shared key
+    ///
+    /// NOTE: For now this is only useful for the experimental PSK method.
+    pub fn new_ccs_symmetric(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self {
+        Self {
+            bytes,
+            key: CredentialKey::Symmetric(symmetric_key),
+            kid: None,
+            cred_type: CredentialType::CCS_PSK,
+        }
+    }
+
+    pub fn with_kid(self, kid: BufferKid) -> Self {
+        Self {
+            kid: Some(kid),
+            ..self
+        }
+    }
+
+    pub fn public_key(&self) -> Option<BytesKeyEC2> {
+        match self.key {
+            CredentialKey::EC2Compact(key) => Some(key),
+            _ => None,
+        }
+    }
+
+    /// Parse a CCS style credential.
+    ///
+    /// If the given value matches the shape lakers expects of a CCS, i.e. credentials from RFC9529,
+    /// its public key and key ID are extracted into a full credential.
+    pub fn parse_ccs(value: &[u8]) -> Result<Self, EDHOCError> {
         const CCS_PREFIX_LEN: usize = 3;
         const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8;
         const COSE_KEY_FIRST_ITEMS_LEN: usize = 6;
 
-        if cred.len()
+        if value.len()
             < 3 + CCS_PREFIX_LEN
                 + 1
                 + CNF_AND_COSE_KEY_PREFIX_LEN
@@ -42,7 +208,7 @@ impl CredentialRPK {
         {
             Err(EDHOCError::ParsingError)
         } else {
-            let subject_len = CBORDecoder::info_of(cred[2]) as usize;
+            let subject_len = CBORDecoder::info_of(value[2]) as usize;
 
             let id_cred_offset: usize = CCS_PREFIX_LEN
                 .checked_add(subject_len)
@@ -55,40 +221,214 @@ impl CredentialRPK {
 
             if g_a_x_offset
                 .checked_add(P256_ELEM_LEN)
-                .map_or(false, |end| end <= cred.len())
+                .map_or(false, |end| end <= value.len())
             {
-                Ok((
-                    cred[g_a_x_offset..g_a_x_offset + P256_ELEM_LEN]
-                        .try_into()
-                        .expect("Wrong key length"),
-                    cred[id_cred_offset],
-                ))
+                let public_key: BytesKeyEC2 = value[g_a_x_offset..g_a_x_offset + P256_ELEM_LEN]
+                    .try_into()
+                    .expect("Wrong key length");
+                let kid = value[id_cred_offset];
+
+                Ok(Self {
+                    bytes: BufferCred::new_from_slice(value)
+                        .map_err(|_| EDHOCError::ParsingError)?,
+                    key: CredentialKey::EC2Compact(public_key),
+                    kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()),
+                    cred_type: CredentialType::CCS,
+                })
             } else {
                 Err(EDHOCError::ParsingError)
             }
         }
     }
+
+    /// Parse a CCS style credential, but the key is a symmetric key.
+    ///
+    /// NOTE: For now this is only useful for the experimental PSK method.
+    pub fn parse_ccs_symmetric(value: &[u8]) -> Result<Self, EDHOCError> {
+        const CCS_PREFIX_LEN: usize = 3;
+        const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8;
+        const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; //COSE for symmetric key
+        const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key
+
+        if value.len()
+            < CCS_PREFIX_LEN
+                + 1
+                + CNF_AND_COSE_KEY_PREFIX_LEN
+                + COSE_KEY_FIRST_ITEMS_LEN
+                + SYMMETRIC_KEY_LEN
+        {
+            Err(EDHOCError::ParsingError)
+        } else {
+            let subject_len = CBORDecoder::info_of(value[2]) as usize;
+
+            let id_cred_offset: usize = CCS_PREFIX_LEN
+                .checked_add(subject_len)
+                .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN))
+                .ok_or(EDHOCError::ParsingError)?;
+
+            let symmetric_key_offset: usize = id_cred_offset
+                .checked_add(COSE_KEY_FIRST_ITEMS_LEN)
+                .ok_or(EDHOCError::ParsingError)?;
+
+            if symmetric_key_offset
+                .checked_add(SYMMETRIC_KEY_LEN)
+                .map_or(false, |end| end <= value.len())
+            {
+                let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value
+                    [symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN]
+                    .try_into()
+                    .map_err(|_| EDHOCError::ParsingError)?;
+
+                let kid = value[id_cred_offset];
+
+                Ok(Self {
+                    bytes: BufferCred::new_from_slice(value)
+                        .map_err(|_| EDHOCError::ParsingError)?,
+                    key: CredentialKey::Symmetric(symmetric_key),
+                    kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()),
+                    cred_type: CredentialType::CCS_PSK,
+                })
+            } else {
+                Err(EDHOCError::ParsingError)
+            }
+        }
+    }
+
+    /// Returns a COSE_Header map with a single entry representing a credential by value.
+    ///
+    /// For example, if the credential is a CCS:
+    ///   { /kccs/ 14: bytes }
+    pub fn by_value(&self) -> Result<IdCred, EDHOCError> {
+        match self.cred_type {
+            CredentialType::CCS => {
+                let mut id_cred = IdCred::new();
+                id_cred
+                    .bytes
+                    .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL])
+                    .map_err(|_| EDHOCError::CredentialTooLongError)?;
+                id_cred
+                    .bytes
+                    .extend_from_slice(self.bytes.as_slice())
+                    .unwrap();
+                Ok(id_cred)
+            }
+            // if we could encode a message along the error below,
+            // it would be this: "Symmetric keys cannot be sent by value"
+            CredentialType::CCS_PSK => Err(EDHOCError::UnexpectedCredential),
+        }
+    }
+
+    /// Returns a COSE_Header map with a single entry representing a credential by reference.
+    ///
+    /// For example, if the reference is a kid:
+    ///   { /kid/ 4: kid }
+    ///
+    /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc.
+    pub fn by_kid(&self) -> Result<IdCred, EDHOCError> {
+        let Some(kid) = self.kid.as_ref() else {
+            return Err(EDHOCError::MissingIdentity);
+        };
+        let mut id_cred = IdCred::new();
+        id_cred
+            .bytes
+            .extend_from_slice(&[
+                CBOR_MAJOR_MAP + 1,
+                KID_LABEL,
+                CBOR_MAJOR_BYTE_STRING | kid.len() as u8,
+            ])
+            .map_err(|_| EDHOCError::CredentialTooLongError)?;
+        id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap();
+        Ok(id_cred)
+    }
 }
 
 #[cfg(test)]
 mod test {
     use super::*;
     use hexlit::hex;
+    use rstest::rstest;
 
     const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072");
     const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0");
-    const ID_CRED_TV: &[u8] = &hex!("a1044132");
+    const ID_CRED_BY_REF_TV: &[u8] = &hex!("a1044132");
+    const ID_CRED_BY_VALUE_TV: &[u8] = &hex!("A10EA2026B6578616D706C652E65647508A101A501020241322001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072");
+    const KID_VALUE_TV: &[u8] = &hex!("32");
+
+    const CRED_PSK: &[u8] =
+        &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214");
+    const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214");
+    const KID_VALUE_PSK: &[u8] = &hex!("32");
+
+    #[test]
+    fn test_new_cred_ccs() {
+        let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap());
+        assert_eq!(cred.bytes.as_slice(), CRED_TV);
+    }
+
+    #[test]
+    fn test_cred_ccs_by_value_or_reference() {
+        let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap())
+            .with_kid(KID_VALUE_TV.try_into().unwrap());
+        let id_cred = cred.by_value().unwrap();
+        assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_VALUE_TV);
+        assert_eq!(id_cred.item_type(), IdCredType::KCCS);
+        let id_cred = cred.by_kid().unwrap();
+        assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_REF_TV);
+        assert_eq!(id_cred.item_type(), IdCredType::KID);
+    }
+
+    #[test]
+    fn test_parse_ccs() {
+        let cred = Credential::parse_ccs(CRED_TV).unwrap();
+        assert_eq!(cred.bytes.as_slice(), CRED_TV);
+        assert_eq!(
+            cred.key,
+            CredentialKey::EC2Compact(G_A_TV.try_into().unwrap())
+        );
+        assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_TV);
+        assert_eq!(cred.cred_type, CredentialType::CCS);
+    }
+
+    #[rstest]
+    #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer
+    #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label
+    #[case(ID_CRED_BY_VALUE_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value
+    fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) {
+        assert_eq!(
+            IdCred::from_encoded_value(input).unwrap().as_full_value(),
+            expected
+        );
+    }
+}
+
+#[cfg(test)]
+mod test_experimental {
+    use super::*;
+    use hexlit::hex;
+
+    const CRED_PSK: &[u8] =
+        &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214");
+    const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214");
+    const KID_VALUE_PSK: &[u8] = &hex!("32");
+
+    #[test]
+    fn test_cred_ccs_symmetric_by_value_or_reference() {
+        // TODO
+    }
+
+    #[test]
+    fn test_new_cred_ccs_symmetric() {
+        let cred =
+            Credential::new_ccs_symmetric(CRED_PSK.try_into().unwrap(), K.try_into().unwrap());
+        assert_eq!(cred.bytes.as_slice(), CRED_PSK);
+    }
 
     #[test]
-    fn test_new_cred() {
-        let cred_tv: EdhocMessageBuffer = CRED_TV.try_into().unwrap();
-
-        let res = CredentialRPK::new(CRED_TV.try_into().unwrap());
-        assert!(res.is_ok());
-        let cred = res.unwrap();
-        assert_eq!(cred.value, cred_tv);
-        assert_eq!(cred.public_key, G_A_TV);
-        assert_eq!(cred.kid, ID_CRED_TV[3]);
-        assert_eq!(cred.get_id_cred(), ID_CRED_TV);
+    fn test_parse_ccs_symmetric() {
+        let cred = Credential::parse_ccs_symmetric(CRED_PSK).unwrap();
+        assert_eq!(cred.bytes.as_slice(), CRED_PSK);
+        assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap()));
+        assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK);
+        assert_eq!(cred.cred_type, CredentialType::CCS_PSK);
     }
 }
diff --git a/shared/src/crypto.rs b/shared/src/crypto.rs
index 8de9b8bd..2de58232 100644
--- a/shared/src/crypto.rs
+++ b/shared/src/crypto.rs
@@ -2,6 +2,26 @@
 
 use super::*;
 
+/// Returns the SUITES_I array, or an error if selected_suite is not supported.
+///
+/// The SUITES_I list will contain:
+/// - the selected suite at the last position
+/// - an ordered list of preferred suites in the first positions
+pub fn prepare_suites_i(
+    supported_suites: &EdhocBuffer<MAX_SUITES_LEN>,
+    selected_suite: u8,
+) -> Result<EdhocBuffer<MAX_SUITES_LEN>, EDHOCError> {
+    // TODO: implement a re-positioning algorithm, considering preferred and selected suites (see Section 5.2.2 of RFC 9528)
+    //       for now, we only support a single suite so we just return it
+    // NOTE: should we assume that supported_suites == preferred_suites?
+    if supported_suites.contains(&(selected_suite)) {
+        EdhocBuffer::<MAX_SUITES_LEN>::new_from_slice(&[selected_suite.into()])
+            .map_err(|_| EDHOCError::UnsupportedCipherSuite)
+    } else {
+        Err(EDHOCError::UnsupportedCipherSuite)
+    }
+}
+
 /// Interface between the lakers crate and any implementations of the required crypto primitives.
 ///
 /// Sending cryptographic operations through a trait gives the library the flexibility to use
@@ -17,6 +37,11 @@ use super::*;
 /// platform's mutex, or to refactor the main initiator and responder objects into a form where the
 /// cryptography implementation can be taken out and stored separately.
 pub trait Crypto: core::fmt::Debug {
+    /// Returns the list of cryptographic suites supported by the backend implementation.
+    fn supported_suites(&self) -> EdhocBuffer<MAX_SUITES_LEN> {
+        EdhocBuffer::<MAX_SUITES_LEN>::new_from_slice(&[EDHOCSuite::CipherSuite2 as u8])
+            .expect("This should never fail, as the slice is of the correct length")
+    }
     fn sha256_digest(&mut self, message: &BytesMaxBuffer, message_len: usize) -> BytesHashLen;
     fn hkdf_expand(
         &mut self,
diff --git a/shared/src/lib.rs b/shared/src/lib.rs
index 35934534..17473a71 100644
--- a/shared/src/lib.rs
+++ b/shared/src/lib.rs
@@ -19,11 +19,14 @@ use core::num::NonZeroI16;
 use defmt_or_log::trace;
 
 mod crypto;
-pub use crypto::Crypto;
+pub use crypto::*;
 
 mod cred;
 pub use cred::*;
 
+mod buffer;
+pub use buffer::*;
+
 #[cfg(feature = "python-bindings")]
 use pyo3::prelude::*;
 #[cfg(feature = "python-bindings")]
@@ -58,9 +61,9 @@ pub const MAC_LENGTH_3: usize = MAC_LENGTH_2;
 pub const ENCODED_VOUCHER_LEN: usize = 1 + MAC_LENGTH; // 1 byte for the length of the bstr-encoded voucher
 
 // maximum supported length of connection identifier for R
-pub const MAX_KDF_CONTEXT_LEN: usize = SCALE_FACTOR * 150;
+pub const MAX_KDF_CONTEXT_LEN: usize = SCALE_FACTOR * 256;
 pub const MAX_KDF_LABEL_LEN: usize = 15; // for "KEYSTREAM_2"
-pub const MAX_BUFFER_LEN: usize = SCALE_FACTOR * 256;
+pub const MAX_BUFFER_LEN: usize = SCALE_FACTOR * 256 + 64;
 pub const CBOR_BYTE_STRING: u8 = 0x58u8;
 pub const CBOR_TEXT_STRING: u8 = 0x78u8;
 pub const CBOR_UINT_1BYTE: u8 = 0x18u8;
@@ -80,6 +83,7 @@ pub const MAX_INFO_LEN: usize = 2 + SHA256_DIGEST_LEN + // 32-byte digest as bst
 						    1; // length as u8
 
 pub const KCSS_LABEL: u8 = 14;
+pub const KID_LABEL: u8 = 4;
 
 pub const ENC_STRUCTURE_LEN: usize = 8 + 5 + SHA256_DIGEST_LEN; // 8 for ENCRYPT0
 
@@ -195,6 +199,33 @@ impl ConnId {
     }
 }
 
+#[derive(PartialEq, Debug)]
+pub enum EDHOCMethod {
+    StatStat = 3,
+    // add others, such as:
+    // PSK1 = ?,
+    // PSK2 = ?,
+}
+
+impl From<EDHOCMethod> for u8 {
+    fn from(method: EDHOCMethod) -> u8 {
+        method as u8
+    }
+}
+
+#[derive(PartialEq, Debug)]
+pub enum EDHOCSuite {
+    CipherSuite2 = 2,
+    // add others, such as:
+    // CiherSuite3 = 3,
+}
+
+impl From<EDHOCSuite> for u8 {
+    fn from(suite: EDHOCSuite) -> u8 {
+        suite as u8
+    }
+}
+
 #[derive(PartialEq, Debug)]
 #[non_exhaustive]
 pub enum EDHOCError {
@@ -203,6 +234,8 @@ pub enum EDHOCError {
     /// error: When the application sets the expected credential, that process should be informed
     /// by the known details.
     UnexpectedCredential,
+    MissingIdentity,
+    IdentityAlreadySet,
     MacVerificationFailed,
     UnsupportedMethod,
     UnsupportedCipherSuite,
@@ -240,6 +273,8 @@ impl EDHOCError {
         use EDHOCError::*;
         match self {
             UnexpectedCredential => ErrCode::UNSPECIFIED,
+            MissingIdentity => ErrCode::UNSPECIFIED,
+            IdentityAlreadySet => ErrCode::UNSPECIFIED,
             MacVerificationFailed => ErrCode::UNSPECIFIED,
             UnsupportedMethod => ErrCode::UNSPECIFIED,
             UnsupportedCipherSuite => ErrCode::WRONG_SELECTED_CIPHER_SUITE,
@@ -283,14 +318,15 @@ impl ErrCode {
 #[derive(Debug)]
 #[repr(C)]
 pub struct InitiatorStart {
-    pub suites_i: BytesSuites,
-    pub suites_i_len: usize,
+    pub suites_i: EdhocBuffer<MAX_SUITES_LEN>,
+    pub method: u8,
     pub x: BytesP256ElemLen,   // ephemeral private key of myself
     pub g_x: BytesP256ElemLen, // ephemeral public key of myself
 }
 
 #[derive(Debug)]
 pub struct ResponderStart {
+    pub method: u8,
     pub y: BytesP256ElemLen,   // ephemeral private key of myself
     pub g_y: BytesP256ElemLen, // ephemeral public key of myself
 }
@@ -328,6 +364,7 @@ pub struct ProcessingM2 {
     pub g_y: BytesP256ElemLen,
     pub plaintext_2: EdhocMessageBuffer,
     pub c_r: ConnId,
+    pub id_cred_r: IdCred,
     pub ead_2: Option<EADItem>,
 }
 
@@ -345,6 +382,7 @@ pub struct ProcessingM3 {
     pub y: BytesP256ElemLen, // ephemeral private key of the responder
     pub prk_3e2m: BytesHashLen,
     pub th_3: BytesHashLen,
+    pub id_cred_i: IdCred,
     pub plaintext_3: EdhocMessageBuffer,
     pub ead_3: Option<EADItem>,
 }
@@ -364,8 +402,8 @@ pub struct Completed {
     pub prk_exporter: BytesHashLen,
 }
 
-#[cfg_attr(feature = "python-bindings", pyclass)]
-#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
+#[derive(Copy, Clone, Debug, PartialEq)]
 #[repr(C)]
 pub enum CredentialTransfer {
     ByReference,
@@ -506,31 +544,6 @@ impl EADItem {
     }
 }
 
-// FIXME: homogenize the two structs below (likey keep only the owned version)
-#[derive(Debug, Clone, Copy)]
-pub enum IdCred<'a> {
-    CompactKid(u8),
-    /// Credential by value. It is required that the credential is a valid deterministic encoding
-    /// of a CCS.
-    FullCredential(&'a [u8]),
-}
-
-impl<'a> IdCred<'a> {
-    pub fn write_to_message(&self, message: &mut EdhocMessageBuffer) -> Result<(), EDHOCError> {
-        match self {
-            IdCred::CompactKid(kid) => message.extend_from_slice(&[*kid]),
-            IdCred::FullCredential(cred) => {
-                let kccs_map_len = 1;
-                message
-                    .extend_from_slice(&[CBOR_MAJOR_MAP + kccs_map_len, KCSS_LABEL])
-                    .map_err(|_| EDHOCError::CredentialTooLongError)?;
-                message.extend_from_slice(cred)
-            }
-        }
-        .map_err(|_| EDHOCError::CredentialTooLongError)
-    }
-}
-
 mod helpers {
     use super::*;
 
@@ -612,24 +625,27 @@ mod edhoc_parser {
 
     pub fn parse_suites_i(
         mut decoder: CBORDecoder,
-    ) -> Result<(BytesSuites, usize, CBORDecoder), EDHOCError> {
+    ) -> Result<(EdhocBuffer<MAX_SUITES_LEN>, CBORDecoder), EDHOCError> {
         trace!("Enter parse_suites_i");
-        let mut suites_i: BytesSuites = Default::default();
+        let mut suites_i: EdhocBuffer<MAX_SUITES_LEN> = Default::default();
         if let Ok(curr) = decoder.current() {
             if CBOR_UINT_1BYTE_START == CBORDecoder::type_of(curr) {
-                suites_i[0] = decoder.u8()?;
-                let suites_i_len = 1;
-                Ok((suites_i, suites_i_len, decoder))
+                let Ok(_) = suites_i.push(decoder.u8()?) else {
+                    return Err(EDHOCError::ParsingError);
+                };
+                Ok((suites_i, decoder))
             } else if CBOR_MAJOR_ARRAY == CBORDecoder::type_of(curr)
                 && CBORDecoder::info_of(curr) >= 2
             {
                 // NOTE: arrays must be at least 2 items long, otherwise the compact encoding (int) must be used
-                let suites_i_len = decoder.array()?;
-                if suites_i_len <= suites_i.len() {
-                    for i in 0..suites_i_len {
-                        suites_i[i] = decoder.u8()?;
+                let received_suites_i_len = decoder.array()?;
+                if received_suites_i_len <= suites_i.capacity() {
+                    for i in 0..received_suites_i_len {
+                        // NOTE: could use suites_i.push, but hax complains about mutable references in loops
+                        suites_i.content[i] = decoder.u8()?;
                     }
-                    Ok((suites_i, suites_i_len, decoder))
+                    suites_i.len = received_suites_i_len;
+                    Ok((suites_i, decoder))
                 } else {
                     Err(EDHOCError::ParsingError)
                 }
@@ -646,8 +662,7 @@ mod edhoc_parser {
     ) -> Result<
         (
             u8,
-            BytesSuites,
-            usize,
+            EdhocBuffer<MAX_SUITES_LEN>,
             BytesP256ElemLen,
             ConnId,
             Option<EADItem>,
@@ -658,7 +673,7 @@ mod edhoc_parser {
         let mut decoder = CBORDecoder::new(rcvd_message_1.as_slice());
         let method = decoder.u8()?;
 
-        if let Ok((suites_i, suites_i_len, mut decoder)) = parse_suites_i(decoder) {
+        if let Ok((suites_i, mut decoder)) = parse_suites_i(decoder) {
             let mut g_x: BytesP256ElemLen = [0x00; P256_ELEM_LEN];
             g_x.copy_from_slice(decoder.bytes_sized(P256_ELEM_LEN)?);
 
@@ -671,12 +686,12 @@ mod edhoc_parser {
                 // we assume only one EAD item
                 let ead_res = parse_ead(decoder.remaining_buffer()?);
                 if let Ok(ead_1) = ead_res {
-                    Ok((method, suites_i, suites_i_len, g_x, c_i, ead_1))
+                    Ok((method, suites_i, g_x, c_i, ead_1))
                 } else {
                     Err(ead_res.unwrap_err())
                 }
             } else if decoder.finished() {
-                Ok((method, suites_i, suites_i_len, g_x, c_i, None))
+                Ok((method, suites_i, g_x, c_i, None))
             } else {
                 Err(EDHOCError::ParsingError)
             }
@@ -727,16 +742,8 @@ mod edhoc_parser {
 
         let c_r = ConnId::from_int_raw(decoder.int_raw()?);
 
-        // NOTE: if len of bstr is 1, it is a compact kid and therefore should have been encoded as int
-        let id_cred_r = if CBOR_MAJOR_MAP == CBORDecoder::type_of(decoder.current()?) {
-            if decoder.map()? == 1 && decoder.u8()? == KCSS_LABEL {
-                IdCred::FullCredential(decoder.any_as_encoded()?)
-            } else {
-                return Err(EDHOCError::ParsingError);
-            }
-        } else {
-            IdCred::CompactKid(decoder.int_raw()?)
-        };
+        // the id_cred may have been encoded as a single int, a byte string, or a map
+        let id_cred_r = IdCred::from_encoded_value(decoder.any_as_encoded()?)?;
 
         mac_2[..].copy_from_slice(decoder.bytes_sized(MAC_LENGTH_2)?);
 
@@ -764,16 +771,8 @@ mod edhoc_parser {
 
         let mut decoder = CBORDecoder::new(plaintext_3.as_slice());
 
-        // NOTE: if len of bstr is 1, it is a compact kid and therefore should have been encoded as int
-        let id_cred_i = if CBOR_MAJOR_MAP == CBORDecoder::type_of(decoder.current()?) {
-            if decoder.map()? == 1 && decoder.u8()? == KCSS_LABEL {
-                IdCred::FullCredential(decoder.any_as_encoded()?)
-            } else {
-                return Err(EDHOCError::ParsingError);
-            }
-        } else {
-            IdCred::CompactKid(decoder.int_raw()?)
-        };
+        // the id_cred may have been encoded as a single int, a byte string, or a map
+        let id_cred_i = IdCred::from_encoded_value(decoder.any_as_encoded()?)?;
 
         mac_3[..].copy_from_slice(decoder.bytes_sized(MAC_LENGTH_3)?);
 
diff --git a/shared/src/python_bindings.rs b/shared/src/python_bindings.rs
index f2464d3b..557553a9 100644
--- a/shared/src/python_bindings.rs
+++ b/shared/src/python_bindings.rs
@@ -34,6 +34,18 @@ impl From<MessageBufferError> for PyErr {
     }
 }
 
+impl fmt::Display for EdhocBufferError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "EdhocBufferError::{:?}", self)
+    }
+}
+
+impl From<EdhocBufferError> for PyErr {
+    fn from(error: EdhocBufferError) -> Self {
+        PyValueError::new_err(error.to_string())
+    }
+}
+
 #[pymethods]
 impl EADItem {
     #[new]
@@ -60,9 +72,10 @@ impl EADItem {
     }
 }
 
+// FIXME: adjust for new Credential struct
 #[pymethods]
-impl CredentialRPK {
-    /// Construct a new CredentialRPK
+impl Credential {
+    /// Construct a new Credential
     ///
     /// This has two variations:
     /// * Pass only the value. Lakers will try to parse the value as a CCS, and populate all fields
@@ -72,24 +85,22 @@ impl CredentialRPK {
     ///   application can decrypt based on its association with an ACE Authorization Server (AS),
     ///   or of which it knows the corresponding details from when it requested that token.
     ///
-    /// Note that other forms of a CredentialRPK can be around (eg. only carrying a kid). Those can
+    /// Note that other forms of a Credential can be around (eg. only carrying a kid). Those can
     /// not directly be constructed, but may be produced by Lakers when parsing a message that
     /// contains a credential by reference.
     #[new]
     #[pyo3(signature = (value, *, kid = None, public_key = None))]
-    fn new_py(value: Vec<u8>, kid: Option<u8>, public_key: Option<Vec<u8>>) -> PyResult<Self> {
-        let value = EdhocMessageBuffer::new_from_slice(&value)?;
+    fn new_py(value: Vec<u8>, kid: Option<Vec<u8>>, public_key: Option<Vec<u8>>) -> PyResult<Self> {
         match (kid, public_key) {
-            (None, None) => Ok(Self::new(value)?),
+            (None, None) => Ok(Self::parse_ccs(&value)?),
             (Some(kid), Some(public_key)) => {
                 let public_key = public_key
                     .try_into()
                     .map_err(|_| PyTypeError::new_err("Public key length mismatch"))?;
-                Ok(Self {
-                    value,
-                    kid,
-                    public_key,
-                })
+                Ok(
+                    Self::new_ccs(BufferCred::new_from_slice(&value)?, public_key)
+                        .with_kid(BufferKid::new_from_slice(&kid)?),
+                )
             }
             _ => Err(PyTypeError::new_err(
                 "To bypass credential parsing, all optional arguments must be given.",
@@ -99,22 +110,23 @@ impl CredentialRPK {
 
     fn __repr__(&self) -> String {
         format!(
-            "CredentialRpk(bytes.fromhex('{}'), public_key=bytes.fromhex('{}'), kid={})",
-            hex::encode(self.value.as_slice()),
-            hex::encode(self.public_key),
-            self.kid,
+            "Credential(bytes.fromhex('{}'), public_key=bytes.fromhex('{}'), kid={:?})",
+            hex::encode(self.bytes.as_slice()),
+            hex::encode(self.public_key().unwrap().as_slice()),
+            self.kid.as_ref().unwrap(),
         )
     }
 
     fn value<'a>(&self, py: Python<'a>) -> Bound<'a, PyBytes> {
-        PyBytes::new_bound(py, self.value.as_slice())
+        PyBytes::new_bound(py, self.bytes.as_slice())
     }
 
-    fn public_key<'a>(&self, py: Python<'a>) -> Bound<'a, PyBytes> {
-        PyBytes::new_bound(py, self.public_key.as_slice())
+    #[pyo3(name = "public_key")]
+    fn py_public_key<'a>(&self, py: Python<'a>) -> Bound<'a, PyBytes> {
+        PyBytes::new_bound(py, &self.public_key().unwrap())
     }
 
-    fn kid(&self) -> u8 {
-        self.kid
+    fn kid<'a>(&self, py: Python<'a>) -> Bound<'a, PyBytes> {
+        PyBytes::new_bound(py, self.kid.as_ref().unwrap().as_slice())
     }
 }


Test patch:
diff --git a/lakers-python/test/test_lakers.py b/lakers-python/test/test_lakers.py
index 409f630f..a532d996 100644
--- a/lakers-python/test/test_lakers.py
+++ b/lakers-python/test/test_lakers.py
@@ -22,15 +22,15 @@ def test_initiator():
 def test_responder():
     responder = lakers.EdhocResponder(R, CRED_R)
 
-def test_rpk_consruction():
+def test_ccs_consruction():
     # The main crednetials we use can be parsed as they are:
-    cred_r = lakers.CredentialRPK(CRED_R)
+    cred_r = lakers.Credential(CRED_R)
 
     # We can also parse them on our own and construct an equivalent credential:
     parsed_cred_r = cbor2.loads(CRED_R)
     public_key = parsed_cred_r[8][1][-2]
-    kid = ord(parsed_cred_r[8][1][2])
-    cred_r_manual = lakers.CredentialRPK(CRED_R, public_key=public_key, kid=kid)
+    kid = [ord(parsed_cred_r[8][1][2])]
+    cred_r_manual = lakers.Credential(CRED_R, public_key=public_key, kid=kid)
 
     # No equality is useful, but the reprs are comprehensive
     assert repr(cred_r_manual) == repr(cred_r)
diff --git a/lakers-python/test_requirements.txt b/lakers-python/test_requirements.txt
index 55b033e9..330a2ab7 100644
--- a/lakers-python/test_requirements.txt
+++ b/lakers-python/test_requirements.txt
@@ -1,1 +1,2 @@
-pytest
\ No newline at end of file
+pytest
+cbor2
\ No newline at end of file