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

Issue:
Refactor EADs to not use global state
This is a step in the direction of the separate message processing, initially discussed in #99, and further discussed in the meeting this week.

Edit: this includes:
- no more global mutable state
- no more worrying about having the same API on every EAD handler (in fact I don't think it is possible without `dyn` or some crazy generic stuff)
- better code organization, including typestated device (U) and authenticator (V)
Role struct constructors
Attempting to use edhoc-rs in a demo application, I found a few properties of the role constructors (eg. `EdhocInitiator`) odd:

* <del>The g_i / g_r is fed in explicitly -- from an application point of view I'd expect the library to either use the platform's random number generator (or, when that is not possible, to provide a random number callback that'll pull as many bytes as needed).</del> [edit: see <https://github.com/openwsn-berkeley/edhoc-rs/issues/99#issuecomment-1740520737>, these are the long-term keys]
* The constructors expect to be fed both sides' credentials -- the responder needs to know in advance which id_cred_i and cred_i the initiator will send, whereas I'd expect EDHOC operations to work with arbitrary initiators. It'd still be up to the application to provide the right CRED_R for an ID_CRED_R, but that could be a map in the simplest case (esp. when KIDs are used), or network based in more complex ones.
* <del>All those binary arguments are passed in hex encoded strings -- a conversion I'd prefer never to do on an embedded system.</del> [edit:fixed in <https://github.com/openwsn-berkeley/edhoc-rs/pull/101>]

I think I could provide patches for most of those, but these might be here for a reason. Could you explain why those are as they are, and whether those are boundary conditions set up by hacspec, or maybe just remnants of earlier development phases? If hacspec tightens things down here, do you have a roadmap for how to provide additional interfaces for constrained devices when hacspec limitations do not apply?
Ability to prepare and process more than one EAD field
Currently one either 0 or 1 EAD fields can be added to a message. This is not complete according to the EDHOC RFC, which specifies 0 or N EAD fields.

From the API perspective, here is one possible way to implement it (see [this review comment](https://github.com/openwsn-berkeley/lakers/pull/174#discussion_r1425394633)):

```rust
let (state, msg1) = start.prepare_message1(x, g_x, c_i)
    .add_ead1(id, Some(bytes))?
    .add_ead1(more_id, None)?
    .finish(&mut crypto);
```


Discussion / hints:
The new `lakers-ead-authz` crate is ready by itself, but now it depends on `lakers` to be updated to support multi-step message processing. ~~Thus for now I'll keep it as _draft_.~~
@chrysn I tried to use the typestate pattern here, could you please take a look? (see the 2 commits with "typestatify" in the name")
Thanks for the feedback. See below.

> The g_i / g_r is fed in explicitly -- from an application point of view I'd expect the library to either use the platform's random number generator (or, when that is not possible, to provide a random number callback that'll pull as many bytes as needed).

Note that these are not the ephemeral keys. These are the public keys present in the corresponding CRED_I/CRED_R. For the moment, we do not parse the credentials for the sake of simplicity but rather use these values in the corresponding ECDH calculations.

> The constructors expect to be fed both sides' credentials -- the responder needs to know in advance which id_cred_i and cred_i the initiator will send, whereas I'd expect EDHOC operations to work with arbitrary initiators. It'd still be up to the application to provide the right CRED_R for an ID_CRED_R, but that could be a map in the simplest case (esp. when KIDs are used), or network based in more complex ones.

This is indeed a current limitation of the API. We would be happy to accept a PR on this.

> All those binary arguments are passed in hex encoded strings -- a conversion I'd prefer never to do on an embedded system.

True. I went through `lib/src/lib.rs` quickly and it seems byte strings would work equally well. I do no recall why the particular choice to use hex-encoded string was made. Again, happy to accept a PR on this.

Note that with the evolution of the hacspec framework, we can now use it on the vanilla Rust branch of the code. That means that we will be getting rid of the hacspec version of the code quite soon, so if you are going to work on the code base, I would suggest doing so on `lib/src/lib.rs` and `lib/src/edhoc.rs`.
That's good to hear!

> Note that these are not the ephemeral keys.

Right, I mixed those up. (So then, providing the g_peer will be up to whatever provides the cred_peer for the provided id_cred_peer).

> would suggest doing so on lib/src/lib.rs and lib/src/edhoc.rs.

Those two have large `mod hacspec` / `mod rust` blocks; will these be unified, and is there a branch I can look at to see what of those survives? (In particular, will the `array!()` thingies still be around?)

For testing of upcoming PRs, is there anything I should do locally, or can I trust that CI will complain if I leave the hacspec parts behind somehow?
> Those two have large mod hacspec / mod rust blocks; will these be unified, and is there a branch I can look at to see what of those survives? (In particular, will the array!() thingies still be around?)

Note that only `lib/src/lib.rs` should have `rust` and `hacspec` blocks. Code in `lib/src/edhoc.rs` is vanilla Rust code. @geonnave is working on a branch where hacspec parts are removed and will let you know. The idea is to completely remove the "hacspec" blocks so `array!()` macros will go away!

>For testing of upcoming PRs, is there anything I should do locally, or can I trust that CI will complain if I leave the hacspec parts behind somehow?

The CI does all the necessary steps, but the hacspec checks will run only on the hacspec part of the code. So, for now, work on the Rust code and we can test later the hacspec aspects. (Please do not use early returns as these won't work when hacspec-checked).
Looking at this with some evenings into the library, and looking at all the data passed in at the constructor, it seems to me that the end game of a `EdhocResponder::new()` might look quite different from now, in that it would create its default state because
* r, cred_r and id_cred_r may be chosen as a result of processing message 1 (there's no draft on that yet, but if we ever did something like encrypted client hello, that's where it'd land)
* id_cred_i will be learned in message 3
* cred_i and g_i will be provided by the application after message 3 processing

This can all be expressed as extra arguments and return values in the right functions (and implementing it is one of my next steps). However, I'm wondering whether given the strict sequence (out-of-sequence calls make error go WrongState anyway), and given that the `.state` is taken and replaced anyway (also owing to hacspec's functional nature), may it not make sense to embrace typestate in the next iteration after that? (After learning some more of how this library works, it seems to me that the lib/src/lib.rs part will not be verified, so we'd be rather free in creating an idiomatic API there.)

So a mid-term proposal could look like

```rust
let a: EdhocResponder = Default::default();
let (a, ead1) = a.process_message_1(); // now a is of type EdhocResponderPhase1
let (a, m2) = a.prepare_message_2(...); // now a is of type EdhocResponderPhase2
```

and any error in sequence would be spotted by the type checker, instead of returning WrongState at runtime. There'd probably still be checks for the state (because the inner State object would stay the same, and we can't easily express its invariant to Rust), but some good const propagation could still make them go away completely.
Trying around, this is going to affect edhoc.rs more, because we'll have to split up `r_process_message_3` (and others if we want to decide the identity of R based on message 1's EAD). That is tricky because
1. it'll need to process the same message twice (OK, maybe not b/c decode_plaintext_3 returns owned data, but that may not be that way forever),
2. it's about processing processing encrypted data before they are authenticated (which is generally something to be very careful about towards the application), and
3. we're touching part of what makes verification happen.

However, the alternative is to decide CRED_I / G_I in a callback from within r_process_message_3, which I can't imagine makes anything any better, especially because that callback may block / may require async. (For example if looking up the credential takes time b/c it happens over a network and we have a cache miss).

Let's ignore 1 for the time being -- it may be a lot of data but it's bounded as long as we have limits on the EAD size we can process. I'd like to sketch a proposal first -- this would replace what was previously a `let (state, prk_out) = r_process_message_3(state, message_3, id_cred_i_expected, cred_i_expected, g_i)?` call:

```rust
let (state, mac_3, id_cred_i, ead_3_untrusted) = r_process_message_3a(state, message_3)?;
let (cred_i, g_i) = look_up_credentials(&id_cred_i); // may also use ead_3_untrusted
let (state, prk_out) = r_process_message_3b(state, mac_3, id_cred_i, ead_3_untrusted, cred_i, g_i)?;
// As that was successful, we may now also rename ead_3_untrusted to ead_3
```

Assembling the check from a mac_3 and the ead_3_untrusted is the alternative to passing in the message_3 once more -- none of which I'm really happy with, given that all things might go wrong if the user sends in the wrong mac_3 / ead_3_untrusted. The alternative is lugging them around with the state (which gets moved around anyway), but I'm not sure how well this would fly with hax:

```rust
let state_and_more = r_process_message_3a(state, message_3)?; // different type from `state`
let (cred_i, g_i) = look_up_credentials(state_and_more.id_cred_i()); // may also use accessor for ead_3_untrusted
let (state, prk_out) = r_process_message_3b(state_and_more, cred_i, g_i)?;
// ead_3 is now lost, but had we cloned it out, we could now trust it
// (If we were willing to carry it around, the latest state could also keep it; the compiler should
// throw it away anyway if unused if LTO is used)
```

I have a soft preference for approach 2 (which also moves the State even of the `edhoc` module into a typestate direction), but both should work, especially given that the lib module will be wrapping it anyway. So the larger question for me is: Which of those works for the verification?

(One way of addressing the question is: Where does the verifier even know what `r_process_message_3` is supposed to do? I only found two mentions in the code, in lib's `lib.rs` and `edhoc.rs`.)
>Looking at this with some evenings into the library, and looking at all the data passed in at the constructor, it seems to me that the end game of a EdhocResponder::new() might look quite different from now, in that it would create its default state because

> * r, cred_r and id_cred_r may be chosen as a result of processing message 1 (there's no draft on that yet, but if we ever did something like encrypted client hello, that's where it'd land)
> * id_cred_i will be learned in message 3
> * cred_i and g_i will be provided by the application after message 3 processing
> This can all be expressed as extra arguments and return values in the right functions (and implementing it is one of my next steps).

Agreed.

> However, I'm wondering whether given the strict sequence (out-of-sequence calls make error go WrongState anyway), and given that the .state is taken and replaced anyway (also owing to hacspec's functional nature), may it not make sense to embrace typestate in the next iteration after that? (After learning some more of how this library works, it seems to me that the lib/src/lib.rs part will not be verified, so we'd be rather free in creating an idiomatic API there.)

Indeed, we organized the library in this manner for exactly the reason you outline. We can and should provide an idiomatic API in `lib/src/lib.rs` that won't get hascpec-verified, but everything inside of `lib/src/edhoc.rs` goes under the hacspec scrutiny. Using typestate indeed seems to make sense, as long as it can all be contained in `lib/src/lib.rs`.

> So a mid-term proposal could look like

>  ```
> let a: EdhocResponder = Default::default();
> let (a, ead1) = a.process_message_1(); // now a is of type EdhocResponderPhase1
> let (a, m2) = a.prepare_message_2(...); // now a is of type EdhocResponderPhase2
> ```

This looks great!

> There'd probably still be checks for the state (because the inner State object would stay the same, and we can't easily express its invariant to Rust), but some good const propagation could still make them go away completely.

Just to make sure we are on the same page here, when you refer to the "inner" State object, do you mean the value of state passed to the hacspec-verified functions? Don't we still need those checks to verify the correct progress of hte state machine? e.g. how will typeset make sure that we do not process the same message twice if such a message was received over the wire?
> Don't we still need those checks to verify the correct progress of hte state machine?

They will, but may use results from the outside in const propagation. Ideally, we could inform the compiler that an invariant needs to be upheld -- say, `PublicStateAfterMessage2 { state_for_edhocrs: State }` might declare that this type has an invariant that `self.state_for_edhocrs == EdhocState::WaitMessage3`. We don't have that in Rust now, but tools emulating this may place the assert already at the accessor level (so by the time r_process_message_3 is called, the compiler knows that branch can't occur b/c of the earlier assert) or may even use assume versions (with due care: check at store time, `if state !=WaitMessage3 { unsafe { unsafe_unreachable() } }`) that moves all the checks to one point in time.
> Trying around, this is going to affect edhoc.rs more, because we'll have to split up `r_process_message_3` (and others if we want to decide the identity of R based on message 1's EAD). That is tricky because
> 
> 1. it'll need to process the same message twice (OK, maybe not b/c decode_plaintext_3 returns owned data, but that may not be that way forever),
> 2. it's about processing processing encrypted data before they are authenticated (which is generally something to be very careful about towards the application), and
> 3. we're touching part of what makes verification happen.
> 
> However, the alternative is to decide CRED_I / G_I in a callback from within r_process_message_3, which I can't imagine makes anything any better, especially because that callback may block / may require async. (For example if looking up the credential takes time b/c it happens over a network and we have a cache miss).
> 
> Let's ignore 1 for the time being -- it may be a lot of data but it's bounded as long as we have limits on the EAD size we can process. I'd like to sketch a proposal first -- this would replace what was previously a `let (state, prk_out) = r_process_message_3(state, message_3, id_cred_i_expected, cred_i_expected, g_i)?` call:
> 
> ```rust
> let (state, mac_3, id_cred_i, ead_3_untrusted) = r_process_message_3a(state, message_3)?;
> let (cred_i, g_i) = look_up_credentials(&id_cred_i); // may also use ead_3_untrusted
> let (state, prk_out) = r_process_message_3b(state, mac_3, id_cred_i, ead_3_untrusted, cred_i, g_i)?;
> // As that was successful, we may now also rename ead_3_untrusted to ead_3
> ```
> 
> Assembling the check from a mac_3 and the ead_3_untrusted is the alternative to passing in the message_3 once more -- none of which I'm really happy with, given that all things might go wrong if the user sends in the wrong mac_3 / ead_3_untrusted. The alternative is lugging them around with the state (which gets moved around anyway), but I'm not sure how well this would fly with hax:
> 
> ```rust
> let state_and_more = r_process_message_3a(state, message_3)?; // different type from `state`
> let (cred_i, g_i) = look_up_credentials(state_and_more.id_cred_i()); // may also use accessor for ead_3_untrusted
> let (state, prk_out) = r_process_message_3b(state_and_more, cred_i, g_i)?;
> // ead_3 is now lost, but had we cloned it out, we could now trust it
> // (If we were willing to carry it around, the latest state could also keep it; the compiler should
> // throw it away anyway if unused if LTO is used)
> ```
> 
> I have a soft preference for approach 2 (which also moves the State even of the `edhoc` module into a typestate direction), but both should work, especially given that the lib module will be wrapping it anyway. So the larger question for me is: Which of those works for the verification?
> 
> (One way of addressing the question is: Where does the verifier even know what `r_process_message_3` is supposed to do? I only found two mentions in the code, in lib's `lib.rs` and `edhoc.rs`.)

Before we discard the callback option, let me understand the pitfalls a bit better. I understand that retrieving `CRED_I` over the network may take time, but can we not just block until that is resolved? What would be an issue with that?

Other than that, on splitting the message 3 processing: I think that approach 1 is dangerous as it lets the application handle internal state of the protocol. I think it is out of the question.

As per approach 2, IIUC, you would essentially be adding `mac_3` and `ead_3` to state, and adding an additional state in the state machine to expect this (implemented with typestate). That seems fine as an approach to me, although I would ideally prefer to keep message_3 processing in one piece.

It would be great if @karthikbhargavan could chime in here.
> just block until that is resolved

A lot of embedded Rust environments don't block, but run on async instead. While it's certainly possible to have versions of the r_process_message_3 function around for blocking and async evaluation, I doubt that'd fly well with the verification, and I also don't think it's pretty (which is really an odd term to say that I think it's not good practice but can't express the arguments well yet).

> adding mac_3 and ead_3 to state

To understand better the impact of this I'd like to add to the open questions: Why is the state struct that's passed around between the phases of the protocol of the same type all the time? AFAICT, every function takes it only in one state and on exit it is in one state (unless there's an error), so why not typestate in the first place? (That'd make carrying mac_3 and ead_3 less hard).
> To understand better the impact of this I'd like to add to the open questions: Why is the state struct that's passed around between the phases of the protocol of the same type all the time? AFAICT, every function takes it only in one state and on exit it is in one state (unless there's an error), so why not typestate in the first place? (That'd make carrying mac_3 and ead_3 less hard).

There is no good explanation for this. We discussed previously splitting it but have not implemented it yet.


Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 55edda54..37f549b5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,6 +46,7 @@ lakers-shared = { package = "lakers-shared", path = "shared/", version = "^0.4.1
 lakers-ead = { package = "lakers-ead-dispatch", path = "ead/", version = "^0.4.1", default-features = false }
 lakers-ead-none = { package = "lakers-ead-none", path = "ead/lakers-ead-none/", version = "^0.4.1" }
 lakers-ead-zeroconf = { package = "lakers-ead-zeroconf", path = "ead/lakers-ead-zeroconf/", version = "^0.4.1" }
+lakers-ead-authz = { package = "lakers-ead-authz", path = "ead/lakers-ead-authz/", version = "^0.4.1" }
 lakers-crypto = { path = "crypto/" }
 
 lakers-crypto-cc2538 = { path = "crypto/lakers-crypto-cc2538/" }
diff --git a/ead/Cargo.toml b/ead/Cargo.toml
index 30100d50..f9b319ef 100644
--- a/ead/Cargo.toml
+++ b/ead/Cargo.toml
@@ -13,8 +13,10 @@ lakers-shared.workspace = true
 
 lakers-ead-none = { workspace = true, optional = true }
 lakers-ead-zeroconf = { workspace = true, optional = true }
+lakers-ead-authz = { workspace = true, optional = true }
 
 [features]
 default = [ "ead-none" ]
 ead-none = [ "lakers-ead-none" ]
 ead-zeroconf = [ "lakers-ead-zeroconf" ]
+ead-authz = [ "lakers-ead-authz" ]
diff --git a/ead/lakers-ead-authz/src/authenticator.rs b/ead/lakers-ead-authz/src/authenticator.rs
index fe42d755..efdb8a06 100644
--- a/ead/lakers-ead-authz/src/authenticator.rs
+++ b/ead/lakers-ead-authz/src/authenticator.rs
@@ -1,5 +1,5 @@
 use super::shared::*;
-use lakers_shared::{Crypto as CryptoTrait, *};
+use lakers_shared::*;
 
 #[derive(Debug, Default)]
 pub struct ZeroTouchAuthenticator;
@@ -113,7 +113,6 @@ fn parse_voucher_response(
 mod test_authenticator {
     use super::*;
     use crate::test_vectors::*;
-    use lakers_crypto::default_crypto;
 
     #[test]
     fn test_parse_ead_1_value() {
@@ -184,7 +183,6 @@ mod test_authenticator {
 mod test_responder_stateless_operation {
     use super::*;
     use crate::test_vectors::*;
-    use lakers_crypto::default_crypto;
 
     #[test]
     fn test_slo_encode_voucher_request() {
diff --git a/ead/lakers-ead-authz/src/device.rs b/ead/lakers-ead-authz/src/device.rs
index c3d8e99a..b59da79c 100644
--- a/ead/lakers-ead-authz/src/device.rs
+++ b/ead/lakers-ead-authz/src/device.rs
@@ -11,6 +11,7 @@ pub struct ZeroTouchDevice {
 #[derive(Debug)]
 pub struct ZeroTouchDeviceWaitEAD2 {
     prk: BytesHashLen,
+    pub h_message_1: BytesHashLen,
 }
 
 #[derive(Debug)]
@@ -43,17 +44,26 @@ impl ZeroTouchDevice {
             value,
         };
 
-        (ead_1, ZeroTouchDeviceWaitEAD2 { prk })
+        (
+            ead_1,
+            ZeroTouchDeviceWaitEAD2 {
+                prk,
+                h_message_1: [0; SHA256_DIGEST_LEN],
+            },
+        )
     }
 }
 
 impl ZeroTouchDeviceWaitEAD2 {
+    pub fn set_h_message_1(&mut self, h_message_1: BytesHashLen) {
+        self.h_message_1 = h_message_1;
+    }
+
     pub fn process_ead_2<Crypto: CryptoTrait>(
         &self,
         crypto: &mut Crypto,
         ead_2: EADItem,
         cred_v: &[u8],
-        h_message_1: &BytesHashLen,
     ) -> Result<ZeroTouchDeviceDone, ()> {
         if ead_2.label != EAD_ZEROCONF_LABEL || ead_2.value.is_none() {
             return Err(());
@@ -61,7 +71,7 @@ impl ZeroTouchDeviceWaitEAD2 {
         let mut ead_2_value: BytesEncodedVoucher = Default::default();
         ead_2_value[..].copy_from_slice(&ead_2.value.unwrap().content[..ENCODED_VOUCHER_LEN]);
 
-        match verify_voucher(crypto, &ead_2_value, h_message_1, cred_v, &self.prk) {
+        match verify_voucher(crypto, &ead_2_value, &self.h_message_1, cred_v, &self.prk) {
             Ok(voucher) => Ok(ZeroTouchDeviceDone { voucher }),
             Err(_) => Err(()),
         }
@@ -179,13 +189,13 @@ mod test_device {
 
         let ead_device = ZeroTouchDeviceWaitEAD2 {
             prk: PRK_TV.try_into().unwrap(),
+            h_message_1: H_MESSAGE_1_TV.try_into().unwrap(),
         };
 
         let res = ead_device.process_ead_2(
             &mut default_crypto(),
             ead_2_tv,
             CRED_V_TV.try_into().unwrap(),
-            &H_MESSAGE_1_TV.try_into().unwrap(),
         );
         assert!(res.is_ok());
         let ead_device = res.unwrap();
diff --git a/ead/lakers-ead-authz/src/lib.rs b/ead/lakers-ead-authz/src/lib.rs
index 9e8d7f8e..c58dbca0 100644
--- a/ead/lakers-ead-authz/src/lib.rs
+++ b/ead/lakers-ead-authz/src/lib.rs
@@ -18,6 +18,7 @@ mod test_authz {
         test_vectors::*,
     };
     use lakers_crypto::default_crypto;
+    use lakers_shared::EDHOCError;
 
     #[test]
     fn test_complete_flow() {
@@ -35,8 +36,9 @@ mod test_authz {
 
         // using .unwrap below since detailed errors are tested in each entity's tests
 
-        let (ead_1, device) =
+        let (ead_1, mut device) =
             device.prepare_ead_1(&mut default_crypto(), &X_TV.try_into().unwrap(), SS_TV);
+        device.set_h_message_1(H_MESSAGE_1_TV.try_into().unwrap());
 
         // ead_1 will be transported within message_1
 
@@ -54,12 +56,34 @@ mod test_authz {
 
         // ead_2 will be transported within message_2
 
-        let result = device.process_ead_2(
-            &mut default_crypto(),
-            ead_2,
-            CRED_V_TV,
-            H_MESSAGE_1_TV.try_into().unwrap(),
-        );
+        let result = device.process_ead_2(&mut default_crypto(), ead_2, CRED_V_TV);
         assert!(result.is_ok());
     }
+
+    #[test]
+    fn test_complete_flow_unauthorized() {
+        let device = ZeroTouchDevice::new(
+            ID_U_TV.try_into().unwrap(),
+            G_W_TV.try_into().unwrap(),
+            LOC_W_TV.try_into().unwrap(),
+        );
+        let authenticator = ZeroTouchAuthenticator::default();
+        let server = ZeroTouchServer::new(
+            W_TV.try_into().unwrap(),
+            CRED_V_TV.try_into().unwrap(),
+            Some(ACL_INVALID_TV.try_into().unwrap()),
+        );
+
+        let (ead_1, mut device) =
+            device.prepare_ead_1(&mut default_crypto(), &X_TV.try_into().unwrap(), SS_TV);
+        device.set_h_message_1(H_MESSAGE_1_TV.try_into().unwrap());
+
+        let (_loc_w, voucher_request, _authenticator) = authenticator
+            .process_ead_1(&ead_1, &MESSAGE_1_WITH_EAD_TV.try_into().unwrap())
+            .unwrap();
+
+        let voucher_response =
+            server.handle_voucher_request(&mut default_crypto(), &voucher_request);
+        assert_eq!(voucher_response.unwrap_err(), EDHOCError::EADError);
+    }
 }
diff --git a/ead/src/lib.rs b/ead/src/lib.rs
index 4c1b1148..5c717db3 100644
--- a/ead/src/lib.rs
+++ b/ead/src/lib.rs
@@ -5,3 +5,6 @@ pub use lakers_ead_none::*;
 
 #[cfg(feature = "ead-zeroconf")]
 pub use lakers_ead_zeroconf::*;
+
+#[cfg(feature = "ead-authz")]
+pub use lakers_ead_authz::*;
diff --git a/examples/coap/src/bin/coapclient.rs b/examples/coap/src/bin/coapclient.rs
index be464b64..c40af705 100644
--- a/examples/coap/src/bin/coapclient.rs
+++ b/examples/coap/src/bin/coapclient.rs
@@ -16,23 +16,24 @@ const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32
 const _G_R: &[u8] = &hex!("bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f0");
 
 fn main() {
+    match client_handshake() {
+        Ok(_) => println!("Handshake completed"),
+        Err(e) => panic!("Handshake failed with error: {:?}", e),
+    }
+}
+
+fn client_handshake() -> Result<(), EDHOCError> {
     let url = "coap://127.0.0.1:5683/.well-known/edhoc";
     let timeout = Duration::new(5, 0);
     println!("Client request: {}", url);
 
-    let state = Default::default();
-    let initiator = EdhocInitiator::new(
-        state,
-        lakers_crypto::default_crypto(),
-        &I,
-        &CRED_I,
-        Some(&CRED_R),
-    );
+    let initiator =
+        EdhocInitiator::new(lakers_crypto::default_crypto(), &I, &CRED_I, Some(&CRED_R));
 
     // 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
     let c_i = generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto());
-    let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap();
+    let (initiator, message_1) = initiator.prepare_message_1(Some(c_i), &None)?;
     msg_1_buf.extend_from_slice(message_1.as_slice());
     println!("message_1 len = {}", msg_1_buf.len());
 
@@ -43,45 +44,43 @@ fn main() {
     println!("response_vec = {:02x?}", response.message.payload);
     println!("message_2 len = {}", response.message.payload.len());
 
-    let m2result = initiator.process_message_2(
-        &response.message.payload[..]
-            .try_into()
-            .expect("wrong length"),
-    );
+    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 (valid_cred_r, _g_r) =
+        credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap();
+    let initiator = initiator.verify_message_2(valid_cred_r.as_slice())?;
 
-    if let Ok((initiator, c_r)) = m2result {
-        let mut msg_3 = Vec::from([c_r]);
-        let (mut initiator, message_3, prk_out) = initiator.prepare_message_3().unwrap();
-        msg_3.extend_from_slice(message_3.as_slice());
-        println!("message_3 len = {}", msg_3.len());
+    let mut msg_3 = Vec::from([c_r]);
+    let (mut initiator, message_3, prk_out) = initiator.prepare_message_3(&None)?;
+    msg_3.extend_from_slice(message_3.as_slice());
+    println!("message_3 len = {}", msg_3.len());
 
-        let _response = CoAPClient::post_with_timeout(url, msg_3, timeout).unwrap();
-        // we don't care about the response to message_3 for now
+    let _response = CoAPClient::post_with_timeout(url, msg_3, timeout).unwrap();
+    // we don't care about the response to message_3 for now
 
-        println!("EDHOC exchange successfully completed");
-        println!("PRK_out: {:02x?}", prk_out);
+    println!("EDHOC exchange successfully completed");
+    println!("PRK_out: {:02x?}", prk_out);
 
-        let mut _oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0
-        let mut _oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1
+    let mut oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0
+    let mut oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1
 
-        println!("OSCORE secret: {:02x?}", _oscore_secret);
-        println!("OSCORE salt: {:02x?}", _oscore_salt);
+    println!("OSCORE secret: {:02x?}", oscore_secret);
+    println!("OSCORE salt: {:02x?}", oscore_salt);
 
-        // context of key update is a test vector from draft-ietf-lake-traces
-        let prk_out_new = initiator.edhoc_key_update(&[
-            0xa0, 0x11, 0x58, 0xfd, 0xb8, 0x20, 0x89, 0x0c, 0xd6, 0xbe, 0x16, 0x96, 0x02, 0xb8,
-            0xbc, 0xea,
-        ]);
+    // context of key update is a test vector from draft-ietf-lake-traces
+    let prk_out_new = initiator.edhoc_key_update(&[
+        0xa0, 0x11, 0x58, 0xfd, 0xb8, 0x20, 0x89, 0x0c, 0xd6, 0xbe, 0x16, 0x96, 0x02, 0xb8, 0xbc,
+        0xea,
+    ]);
 
-        println!("PRK_out after key update: {:02x?}?", prk_out_new);
+    println!("PRK_out after key update: {:02x?}?", prk_out_new);
 
-        // compute OSCORE secret and salt after key update
-        _oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0
-        _oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1
+    // compute OSCORE secret and salt after key update
+    oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0
+    oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1
 
-        println!("OSCORE secret after key update: {:02x?}", _oscore_secret);
-        println!("OSCORE salt after key update: {:02x?}", _oscore_salt);
-    } else {
-        panic!("Message 2 processing error: {:#?}", m2result);
-    }
+    println!("OSCORE secret after key update: {:02x?}", oscore_secret);
+    println!("OSCORE salt after key update: {:02x?}", oscore_salt);
+
+    Ok(())
 }
diff --git a/examples/coap/src/bin/coapserver-coaphandler.rs b/examples/coap/src/bin/coapserver-coaphandler.rs
index 148dfcf1..31291ee6 100644
--- a/examples/coap/src/bin/coapserver-coaphandler.rs
+++ b/examples/coap/src/bin/coapserver-coaphandler.rs
@@ -5,7 +5,7 @@ use lakers_crypto::Crypto;
 use embedded_nal::UdpFullStack;
 
 const _ID_CRED_I: &[u8] = &hex!("a104412b");
-const _ID_CRED_R: &[u8] = &hex!("a104410a");
+const ID_CRED_R: &[u8] = &hex!("a104410a");
 const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
 const _G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6");
 const _G_I_Y_COORD: &[u8] =
@@ -46,7 +46,7 @@ enum EdhocResponse {
     // take up a slot there anyway) if we make it an enum.
     OkSend2 {
         c_r: u8,
-        responder: EdhocResponderBuildM2<'static, Crypto>,
+        responder: EdhocResponderProcessedM1<'static, Crypto>,
     },
     Message3Processed,
 }
@@ -60,20 +60,13 @@ impl coap_handler::Handler for EdhocHandler {
         let starts_with_true = request.payload().get(0) == Some(&0xf5);
 
         if starts_with_true {
-            let state = EdhocState::default();
-
-            let responder = EdhocResponder::new(
-                state,
-                lakers_crypto::default_crypto(),
-                &R,
-                &CRED_R,
-                Some(&CRED_I),
-            );
+            let responder =
+                EdhocResponder::new(lakers_crypto::default_crypto(), &R, &CRED_R, Some(&CRED_I));
 
             let response = responder
                 .process_message_1(&request.payload()[1..].try_into().expect("wrong length"));
 
-            if let Ok(responder) = response {
+            if let Ok((responder, _ead_1)) = response {
                 let c_r = self.new_c_r();
                 EdhocResponse::OkSend2 { c_r, responder }
             } else {
@@ -87,9 +80,17 @@ impl coap_handler::Handler for EdhocHandler {
                 .expect("No such C_R found");
 
             println!("Found state with connection identifier {:?}", c_r_rcvd);
-            let result = responder
-                .process_message_3(&request.payload()[1..].try_into().expect("wrong length"));
 
+            let message_3 = EdhocMessageBuffer::new_from_slice(&request.payload()[1..]).unwrap();
+            let result = responder.parse_message_3(&message_3);
+            let Ok((responder, id_cred_i, _ead_3)) = result else {
+                println!("EDHOC processing error: {:?}", result);
+                // FIXME remove state from edhoc_connections
+                panic!("Handler can't just not respond");
+            };
+            let (valid_cred_i, _g_i) =
+                credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap();
+            let result = responder.verify_message_3(valid_cred_i.as_slice());
             let Ok((mut responder, prk_out)) = result else {
                 println!("EDHOC processing error: {:?}", result);
                 // FIXME remove state from edhoc_connections
@@ -130,7 +131,9 @@ impl coap_handler::Handler for EdhocHandler {
         response.set_code(coap_numbers::code::CHANGED.try_into().ok().unwrap());
         match req {
             EdhocResponse::OkSend2 { c_r, responder } => {
-                let (responder, message_2) = responder.prepare_message_2(c_r).unwrap();
+                let kid = IdCred::CompactKid(ID_CRED_R[3]);
+                let (responder, message_2) =
+                    responder.prepare_message_2(&kid, Some(c_r), &None).unwrap();
                 self.connections.push((c_r, responder));
                 response.set_payload(message_2.as_slice());
             }
diff --git a/examples/coap/src/bin/coapserver.rs b/examples/coap/src/bin/coapserver.rs
index 9125a780..efd7591a 100644
--- a/examples/coap/src/bin/coapserver.rs
+++ b/examples/coap/src/bin/coapserver.rs
@@ -4,7 +4,7 @@ use hexlit::hex;
 use std::net::UdpSocket;
 
 const _ID_CRED_I: &[u8] = &hex!("a104412b");
-const _ID_CRED_R: &[u8] = &hex!("a104410a");
+const ID_CRED_R: &[u8] = &hex!("a104410a");
 const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
 const _G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6");
 const _G_I_Y_COORD: &[u8] =
@@ -31,9 +31,7 @@ fn main() {
             println!("Received message from {}", src);
             // This is an EDHOC message
             if request.message.payload[0] == 0xf5 {
-                let state = EdhocState::default();
                 let responder = EdhocResponder::new(
-                    state,
                     lakers_crypto::default_crypto(),
                     &R,
                     &CRED_R,
@@ -46,10 +44,12 @@ fn main() {
                         .expect("wrong length"),
                 );
 
-                if let Ok(responder) = result {
+                if let Ok((responder, _ead_1)) = result {
                     let c_r =
                         generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto());
-                    let (responder, message_2) = responder.prepare_message_2(c_r).unwrap();
+                    let kid = IdCred::CompactKid(ID_CRED_R[3]);
+                    let (responder, message_2) =
+                        responder.prepare_message_2(&kid, Some(c_r), &None).unwrap();
                     response.message.payload = Vec::from(message_2.as_slice());
                     // save edhoc connection
                     edhoc_connections.push((c_r, responder));
@@ -62,17 +62,23 @@ fn main() {
                 let responder = take_state(c_r_rcvd, &mut edhoc_connections).unwrap();
 
                 println!("Found state with connection identifier {:?}", c_r_rcvd);
-                let result = responder.process_message_3(
-                    &request.message.payload[1..]
-                        .try_into()
-                        .expect("wrong length"),
-                );
-                let Ok((mut responder, prk_out)) = result else {
-                    println!("EDHOC processing error: {:?}", response);
+                let message_3 =
+                    EdhocMessageBuffer::new_from_slice(&request.message.payload[1..]).unwrap();
+                let Ok((responder, id_cred_i, _ead_3)) = responder.parse_message_3(&message_3)
+                else {
+                    println!("EDHOC processing error: {:?}", message_3);
                     // We don't get another chance, it's popped and can't be used any further
                     // anyway legally
                     continue;
                 };
+                let (valid_cred_i, _g_i) =
+                    credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap();
+                let Ok((mut responder, prk_out)) =
+                    responder.verify_message_3(valid_cred_i.as_slice())
+                else {
+                    println!("EDHOC processing error: {:?}", valid_cred_i);
+                    continue;
+                };
 
                 // send empty ack back
                 response.message.payload = b"".to_vec();
diff --git a/examples/edhoc-rs-no_std/src/main.rs b/examples/edhoc-rs-no_std/src/main.rs
index 6328f03f..1e07a531 100644
--- a/examples/edhoc-rs-no_std/src/main.rs
+++ b/examples/edhoc-rs-no_std/src/main.rs
@@ -61,7 +61,7 @@ fn main() -> ! {
     use hexlit::hex;
 
     const _ID_CRED_I: &[u8] = &hex!("a104412b");
-    const _ID_CRED_R: &[u8] = &hex!("a104410a");
+    const ID_CRED_R: &[u8] = &hex!("a104410a");
     const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
     const I: &[u8] = &hex!("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b");
     const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac");
@@ -73,14 +73,8 @@ fn main() -> ! {
     const _C_R_TV: [u8; 1] = hex!("27");
 
     fn test_new_initiator() {
-        let state = Default::default();
-        let _initiator = EdhocInitiator::new(
-            state,
-            lakers_crypto::default_crypto(),
-            I,
-            CRED_I,
-            Some(CRED_R),
-        );
+        let _initiator =
+            EdhocInitiator::new(lakers_crypto::default_crypto(), I, CRED_I, Some(CRED_R));
     }
 
     test_new_initiator();
@@ -99,18 +93,12 @@ fn main() -> ! {
     println!("Test test_p256_keys passed.");
 
     fn test_prepare_message_1() {
-        let state = Default::default();
-        let mut initiator = EdhocInitiator::new(
-            state,
-            lakers_crypto::default_crypto(),
-            I,
-            CRED_I,
-            Some(CRED_R),
-        );
+        let mut initiator =
+            EdhocInitiator::new(lakers_crypto::default_crypto(), I, CRED_I, Some(CRED_R));
 
         let c_i: u8 =
             generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into();
-        let message_1 = initiator.prepare_message_1(c_i);
+        let message_1 = initiator.prepare_message_1(None, &None);
         assert!(message_1.is_ok());
     }
 
@@ -118,39 +106,29 @@ fn main() -> ! {
     println!("Test test_prepare_message_1 passed.");
 
     fn test_handshake() {
-        let state_initiator = Default::default();
-        let mut initiator = EdhocInitiator::new(
-            state_initiator,
-            lakers_crypto::default_crypto(),
-            I,
-            CRED_I,
-            Some(CRED_R),
-        );
-        let state_responder = Default::default();
-        let responder = EdhocResponder::new(
-            state_responder,
-            lakers_crypto::default_crypto(),
-            R,
-            CRED_R,
-            Some(CRED_I),
-        );
+        let mut initiator =
+            EdhocInitiator::new(lakers_crypto::default_crypto(), I, CRED_I, Some(CRED_R));
+        let responder =
+            EdhocResponder::new(lakers_crypto::default_crypto(), R, CRED_R, Some(CRED_I));
 
-        let c_i: u8 =
-            generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into();
-        let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap(); // to update the state
+        let (initiator, message_1) = initiator.prepare_message_1(None, &None).unwrap();
 
-        let responder = responder.process_message_1(&message_1).unwrap();
-
-        let c_r: u8 =
-            generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into();
-        let (responder, message_2) = responder.prepare_message_2(c_r).unwrap();
-        assert!(c_r != 0xff);
+        let (responder, _ead_1) = responder.process_message_1(&message_1).unwrap();
+        let kid = IdCred::CompactKid(ID_CRED_R[3]);
+        let (responder, message_2) = responder.prepare_message_2(&kid, None, &None).unwrap();
 
-        let (initiator, _c_r) = initiator.process_message_2(&message_2).unwrap();
+        let (initiator, c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap();
+        let (valid_cred_r, g_r) =
+            credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap();
+        let initiator = initiator.verify_message_2(valid_cred_r.as_slice()).unwrap();
 
-        let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3().unwrap();
+        let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3(&None).unwrap();
 
-        let (mut responder, r_prk_out) = responder.process_message_3(&message_3).unwrap();
+        let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap();
+        let (valid_cred_i, g_i) =
+            credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap();
+        let (mut responder, r_prk_out) =
+            responder.verify_message_3(valid_cred_i.as_slice()).unwrap();
 
         // check that prk_out is equal at initiator and responder side
         assert_eq!(i_prk_out, r_prk_out);
diff --git a/lib/Cargo.toml b/lib/Cargo.toml
index b0ec956b..f7262bff 100644
--- a/lib/Cargo.toml
+++ b/lib/Cargo.toml
@@ -20,6 +20,7 @@ hexlit = "0.5.3"
 default = [ "lakers-ead/ead-none" ]
 ead-none = [ "lakers-ead/ead-none" ]
 ead-zeroconf = [ "lakers-ead/ead-zeroconf" ]
+ead-authz = [ "lakers-ead/ead-authz" ]
 
 [lib]
 crate-type = ["rlib"]
diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs
index 94418299..3e6fe60e 100644
--- a/lib/src/edhoc.rs
+++ b/lib/src/edhoc.rs
@@ -1,9 +1,7 @@
-use core::marker::PhantomData;
-use lakers_ead::*;
 use lakers_shared::{Crypto as CryptoTrait, *};
 
 pub fn edhoc_exporter(
-    state: &State<Completed>,
+    state: &Completed,
     crypto: &mut impl CryptoTrait,
     label: u8,
     context: &BytesMaxContextBuffer,
@@ -21,7 +19,7 @@ pub fn edhoc_exporter(
 }
 
 pub fn edhoc_key_update(
-    state: &mut State<Completed>,
+    state: &mut Completed,
     crypto: &mut impl CryptoTrait,
     context: &BytesMaxContextBuffer,
     context_len: usize,
@@ -56,47 +54,32 @@ pub fn edhoc_key_update(
 }
 
 pub fn r_process_message_1(
-    state: State<Start>,
+    state: ResponderStart,
     crypto: &mut impl CryptoTrait,
     message_1: &BufferMessage1,
-) -> Result<State<ProcessedMessage1>, EDHOCError> {
+) -> Result<(ProcessingM1, Option<EADItem>), EDHOCError> {
     // Step 1: decode message_1
     // g_x will be saved to the state
-    let res = parse_message_1(message_1);
-
-    if let Ok((method, suites_i, suites_i_len, g_x, c_i, ead_1)) = res {
+    if let Ok((method, suites_i, suites_i_len, 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] {
-                // Step 3: If EAD is present make it available to the application
-                let ead_success = if let Some(ead_1) = ead_1 {
-                    r_process_ead_1(crypto, &ead_1, message_1).is_ok()
-                } else {
-                    true
-                };
-                if ead_success {
-                    // 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());
-                    let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len);
-
-                    let state = State {
-                        current_state: PhantomData,
-                        x_or_y: state.x_or_y,
+                // 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());
+                let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len);
+
+                Ok((
+                    ProcessingM1 {
+                        y: state.y,
+                        g_y: state.g_y,
                         c_i,
-                        gy_or_gx: g_x,
-                        prk_3e2m: state.prk_3e2m,
-                        prk_4e3m: state.prk_4e3m,
-                        prk_out: state.prk_out,
-                        prk_exporter: state.prk_exporter,
+                        g_x,
                         h_message_1,
-                        th_3: state.th_3,
-                    };
-                    Ok(state)
-                } else {
-                    Err(EDHOCError::EADError)
-                }
+                    },
+                    ead_1,
+                ))
             } else {
                 Err(EDHOCError::UnsupportedCipherSuite)
             }
@@ -109,35 +92,25 @@ pub fn r_process_message_1(
 }
 
 pub fn r_prepare_message_2(
-    state: State<ProcessedMessage1>,
+    state: ProcessingM1,
     crypto: &mut impl CryptoTrait,
     cred_r: &[u8],
     r: &BytesP256ElemLen, // R's static private DH key
-    y: BytesP256ElemLen,
-    g_y: BytesP256ElemLen,
     c_r: u8,
-) -> Result<(State<WaitMessage3>, BufferMessage2), EDHOCError> {
+    id_cred_r: &IdCred,
+    ead_2: &Option<EADItem>,
+) -> Result<(WaitM3, BufferMessage2), EDHOCError> {
     // compute TH_2
-    let th_2 = compute_th_2(crypto, &g_y, &state.h_message_1);
+    let th_2 = compute_th_2(crypto, &state.g_y, &state.h_message_1);
 
     // compute prk_3e2m
-    let prk_2e = compute_prk_2e(crypto, &y, &state.gy_or_gx, &th_2);
+    let prk_2e = compute_prk_2e(crypto, &state.y, &state.g_x, &th_2);
     let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2);
-    let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.gy_or_gx);
+    let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.g_x);
 
     // compute MAC_2
     let mac_2 = compute_mac_2(crypto, &prk_3e2m, &get_id_cred(cred_r)?, cred_r, &th_2);
 
-    let ead_2 = r_prepare_ead_2();
-
-    let id_cred_r = if ead_2.is_some() {
-        // NOTE: assume EAD_2 is for zeroconf
-        IdCred::FullCredential(cred_r)
-    } else {
-        let (_g_r, kid) = parse_cred(cred_r)?;
-        IdCred::CompactKid(kid)
-    };
-
     // compute ciphertext_2
     let plaintext_2 = encode_plaintext_2(c_r, id_cred_r, &mac_2, &ead_2)?;
 
@@ -152,133 +125,51 @@ pub fn r_prepare_message_2(
 
     ct.fill_with_slice(ciphertext_2.as_slice()).unwrap(); // TODO(hax): same as just above.
 
-    let message_2 = encode_message_2(&g_y, &ct);
-
-    let state = State {
-        current_state: PhantomData,
-        x_or_y: y,
-        c_i: state.c_i,
-        gy_or_gx: state.gy_or_gx,
-        prk_3e2m: prk_3e2m,
-        prk_4e3m: state.prk_4e3m,
-        prk_out: state.prk_out,
-        prk_exporter: state.prk_exporter,
-        h_message_1: state.h_message_1,
-        th_3: th_3,
-    };
+    let message_2 = encode_message_2(&state.g_y, &ct);
 
-    Ok((state, message_2))
+    Ok((
+        WaitM3 {
+            y: state.y,
+            prk_3e2m: prk_3e2m,
+            th_3: th_3,
+        },
+        message_2,
+    ))
 }
 
 // FIXME fetch ID_CRED_I and CRED_I based on kid
-pub fn r_process_message_3(
-    state: &mut State<WaitMessage3>,
+pub fn r_parse_message_3(
+    state: &mut WaitM3,
     crypto: &mut impl CryptoTrait,
     message_3: &BufferMessage3,
-    cred_i_expected: Option<&[u8]>,
-) -> Result<(State<Completed>, BytesHashLen), EDHOCError> {
+) -> Result<(ProcessingM3, IdCredOwned, 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 {
-            // The implementation currently supports the following two cases on handling the credentials:
-            // 1. R receives a kid and has a corresponding CRED_x passed in as cred_i_expected
-            // 2. R receives CRED_x by value in the message and uses it
-            // TODO: add support for fetching CRED_x based on kid received in the message
-
-            let cred_i = credential_check_or_fetch(cred_i_expected, id_cred_i);
-            // IMPL: stop if credential_check_or_fetch returns Error
-
-            if let Ok((valid_cred_i, g_i)) = cred_i {
-                // Phase 2:
-                // - Process EAD_X items that have not been processed yet, and that can be processed before message verification
-                // IMPL: we are sure valid_cred_i is a full credential
-
-                // Step 3: If EAD is present make it available to the application
-                let ead_res = if let Some(ead_3) = ead_3 {
-                    // IMPL: if EAD-zeroconf is present, then id_cred must contain a full credential
-                    // at this point, in case of EAD = zeroconf, if it works it means that:
-                    // - the Voucher has been verified
-                    // - the received valid_cred_i (aka cred_i) has been authenticated
-                    r_process_ead_3(ead_3)
-                } else {
-                    Ok(())
-                };
-
-                if ead_res.is_ok() {
-                    // verify mac_3
-
-                    // 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.x_or_y, &g_i);
-
-                    // compute mac_3
-                    let expected_mac_3 = compute_mac_3(
-                        crypto,
-                        &prk_4e3m,
-                        &state.th_3,
-                        &get_id_cred(valid_cred_i)?,
-                        valid_cred_i,
-                    );
-
-                    // verify mac_3
-                    if mac_3 == expected_mac_3 {
-                        let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, valid_cred_i);
-
-                        let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN];
-                        th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]);
-                        // compute prk_out
-                        // PRK_out = EDHOC-KDF( PRK_4e3m, 7, TH_4, hash_length )
-                        let prk_out_buf = edhoc_kdf(
-                            crypto,
-                            &prk_4e3m,
-                            7u8,
-                            &th_4_buf,
-                            th_4.len(),
-                            SHA256_DIGEST_LEN,
-                        );
-                        let mut prk_out: BytesHashLen = Default::default();
-                        prk_out[..SHA256_DIGEST_LEN]
-                            .copy_from_slice(&prk_out_buf[..SHA256_DIGEST_LEN]);
-
-                        // compute prk_exporter from prk_out
-                        // PRK_exporter  = EDHOC-KDF( PRK_out, 10, h'', hash_length )
-                        let prk_exporter_buf = edhoc_kdf(
-                            crypto,
-                            &prk_out,
-                            10u8,
-                            &[0x00u8; MAX_KDF_CONTEXT_LEN],
-                            0,
-                            SHA256_DIGEST_LEN,
-                        );
-                        state.prk_exporter[..SHA256_DIGEST_LEN]
-                            .copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]);
-
-                        let state = State {
-                            current_state: PhantomData,
-                            x_or_y: state.x_or_y,
-                            c_i: state.c_i,
-                            gy_or_gx: state.gy_or_gx,
-                            prk_3e2m: state.prk_3e2m,
-                            prk_4e3m: state.prk_4e3m,
-                            prk_out: prk_out,
-                            prk_exporter: state.prk_exporter,
-                            h_message_1: state.h_message_1,
-                            th_3: state.th_3,
-                        };
-                        Ok((state, prk_out))
-                    } else {
-                        Err(EDHOCError::MacVerificationFailed)
-                    }
-                } else {
-                    Err(EDHOCError::EADError)
+            let id_cred_i = match id_cred_i {
+                IdCred::CompactKid(kid) => IdCredOwned::CompactKid(kid),
+                IdCred::FullCredential(cred) => {
+                    let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else {
+                        return Err(EDHOCError::ParsingError);
+                    };
+                    IdCredOwned::FullCredential(buffer)
                 }
-            } else {
-                Err(cred_i.unwrap_err())
-            }
+            };
+
+            Ok((
+                ProcessingM3 {
+                    mac_3,
+                    y: state.y,
+                    prk_3e2m: state.prk_3e2m,
+                    th_3: state.th_3,
+                    plaintext_3, // NOTE: this is needed for th_4, which needs valid_cred_i, which is only available at step 'b'
+                },
+                id_cred_i,
+                ead_3,
+            ))
         } else {
             Err(decoded_p3_res.unwrap_err())
         }
@@ -288,22 +179,86 @@ pub fn r_process_message_3(
     }
 }
 
-pub fn i_prepare_message_1(
-    state: State<Start>,
+pub fn r_verify_message_3(
+    state: &mut ProcessingM3,
     crypto: &mut impl CryptoTrait,
-    x: BytesP256ElemLen,
-    g_x: BytesP256ElemLen,
-    c_i: u8,
-) -> Result<(State<WaitMessage2>, BufferMessage1), EDHOCError> {
-    // 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[..]);
+    valid_cred_i: &[u8],
+) -> Result<(Completed, BytesHashLen), EDHOCError> {
+    let (g_i, _kid_i) = parse_cred(valid_cred_i)?;
 
-    let ead_1 = i_prepare_ead_1(crypto, &x, suites_i[suites_i_len - 1]);
+    // 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, &g_i);
 
+    // compute mac_3
+    let expected_mac_3 = compute_mac_3(
+        crypto,
+        &prk_4e3m,
+        &state.th_3,
+        &get_id_cred(valid_cred_i)?,
+        valid_cred_i,
+    );
+
+    // verify mac_3
+    if state.mac_3 == expected_mac_3 {
+        let th_4 = compute_th_4(crypto, &state.th_3, &state.plaintext_3, valid_cred_i);
+
+        let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN];
+        th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]);
+        // compute prk_out
+        // PRK_out = EDHOC-KDF( PRK_4e3m, 7, TH_4, hash_length )
+        let prk_out_buf = edhoc_kdf(
+            crypto,
+            &prk_4e3m,
+            7u8,
+            &th_4_buf,
+            th_4.len(),
+            SHA256_DIGEST_LEN,
+        );
+        let mut prk_out: BytesHashLen = Default::default();
+        prk_out[..SHA256_DIGEST_LEN].copy_from_slice(&prk_out_buf[..SHA256_DIGEST_LEN]);
+
+        // compute prk_exporter from prk_out
+        // PRK_exporter  = EDHOC-KDF( PRK_out, 10, h'', hash_length )
+        let prk_exporter_buf = edhoc_kdf(
+            crypto,
+            &prk_out,
+            10u8,
+            &[0x00u8; MAX_KDF_CONTEXT_LEN],
+            0,
+            SHA256_DIGEST_LEN,
+        );
+        let mut prk_exporter = BytesHashLen::default();
+        prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]);
+
+        Ok((
+            Completed {
+                prk_out,
+                prk_exporter,
+            },
+            prk_out,
+        ))
+    } else {
+        Err(EDHOCError::MacVerificationFailed)
+    }
+}
+
+pub fn i_prepare_message_1(
+    state: InitiatorStart,
+    crypto: &mut impl CryptoTrait,
+    c_i: u8,
+    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, &suites_i, suites_i_len, &g_x, c_i, &ead_1)?;
+    let message_1 = encode_message_1(
+        EDHOC_METHOD,
+        &state.suites_i,
+        state.suites_i_len,
+        &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());
@@ -311,38 +266,27 @@ pub fn i_prepare_message_1(
     // hash message_1 here to avoid saving the whole message in the state
     let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len);
 
-    let state = State {
-        current_state: PhantomData,
-        x_or_y: x,
-        c_i: c_i,
-        gy_or_gx: state.gy_or_gx,
-        prk_3e2m: state.prk_3e2m,
-        prk_4e3m: state.prk_4e3m,
-        prk_out: state.prk_out,
-        prk_exporter: state.prk_exporter,
-        h_message_1: h_message_1,
-        th_3: state.th_3,
-    };
-
-    Ok((state, message_1))
+    Ok((
+        WaitM2 {
+            x: state.x,
+            h_message_1,
+        },
+        message_1,
+    ))
 }
 
 // returns c_r
-pub fn i_process_message_2(
-    state: State<WaitMessage2>,
+pub fn i_parse_message_2<'a>(
+    state: WaitM2,
     crypto: &mut impl CryptoTrait,
     message_2: &BufferMessage2,
-    cred_r_expected: Option<&[u8]>,
-    i: &BytesP256ElemLen, // I's static private DH key
-) -> Result<(State<ProcessedMessage2>, u8, u8), EDHOCError> {
-    let mut kid = 0xffu8; // invalidate kid
-
+) -> Result<(ProcessingM2, u8, IdCredOwned, 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);
 
         // compute prk_2e
-        let prk_2e = compute_prk_2e(crypto, &state.x_or_y, &g_y, &th_2);
+        let prk_2e = compute_prk_2e(crypto, &state.x, &g_y, &th_2);
 
         let plaintext_2 = encrypt_decrypt_ciphertext_2(crypto, &prk_2e, &th_2, ciphertext_2);
 
@@ -350,73 +294,26 @@ pub fn i_process_message_2(
         let plaintext_2_decoded = decode_plaintext_2(&plaintext_2);
 
         if let Ok((c_r_2, id_cred_r, mac_2, ead_2)) = plaintext_2_decoded {
-            let c_r = c_r_2;
-
-            let cred_r = credential_check_or_fetch(cred_r_expected, id_cred_r);
-            // IMPL: stop if credential_check_or_fetch returns Error
-            if let Ok((valid_cred_r, g_r)) = cred_r {
-                // Phase 2:
-                // - Process EAD_X items that have not been processed yet, and that can be processed before message verification
-                // IMPL: we are sure valid_cred_r is a full credential
-
-                // Step 3: If EAD is present make it available to the application
-                let ead_res = if let Some(ead_2) = ead_2 {
-                    // IMPL: if EAD-zeroconf is present, then id_cred must contain a full credential
-                    // at this point, in case of EAD = zeroconf, if it works it means that:
-                    // - the Voucher has been verified
-                    // - the received valid_cred_r (aka cred_v) has been authenticated
-                    i_process_ead_2(crypto, ead_2, valid_cred_r, &state.h_message_1)
-                } else {
-                    Ok(())
-                };
-
-                if ead_res.is_ok() {
-                    // verify mac_2
-                    let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2);
-
-                    let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, &state.x_or_y, &g_r);
-
-                    let expected_mac_2 = compute_mac_2(
-                        crypto,
-                        &prk_3e2m,
-                        &get_id_cred(valid_cred_r)?,
-                        valid_cred_r,
-                        &th_2,
-                    );
-
-                    if mac_2 == expected_mac_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, valid_cred_r);
-                        // message 3 processing
-
-                        let salt_4e3m = compute_salt_4e3m(crypto, &prk_3e2m, &th_3);
-
-                        let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, i, &g_y);
-
-                        let state = State {
-                            current_state: PhantomData,
-                            x_or_y: state.x_or_y,
-                            c_i: state.c_i,
-                            gy_or_gx: state.gy_or_gx,
-                            prk_3e2m: prk_3e2m,
-                            prk_4e3m: prk_4e3m,
-                            prk_out: state.prk_out,
-                            prk_exporter: state.prk_exporter,
-                            h_message_1: state.h_message_1,
-                            th_3: th_3,
-                        };
-
-                        Ok((state, c_r, kid))
-                    } else {
-                        Err(EDHOCError::MacVerificationFailed)
-                    }
-                } else {
-                    Err(EDHOCError::EADError)
+            let state = ProcessingM2 {
+                mac_2,
+                prk_2e,
+                th_2,
+                g_y,
+                plaintext_2: plaintext_2,
+                x: state.x,
+            };
+
+            let id_cred_r = match id_cred_r {
+                IdCred::CompactKid(kid) => IdCredOwned::CompactKid(kid),
+                IdCred::FullCredential(cred) => {
+                    let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else {
+                        return Err(EDHOCError::ParsingError);
+                    };
+                    IdCredOwned::FullCredential(buffer)
                 }
-            } else {
-                Err(cred_r.unwrap_err())
-            }
+            };
+
+            Ok((state, c_r_2, id_cred_r, ead_2))
         } else {
             Err(EDHOCError::ParsingError)
         }
@@ -425,17 +322,63 @@ pub fn i_process_message_2(
     }
 }
 
+pub fn i_verify_message_2(
+    state: ProcessingM2,
+    crypto: &mut impl CryptoTrait,
+    valid_cred_r: &[u8], // TODO: have a struct to hold credentials to avoid re-computing
+    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 (g_r, _) = parse_cred(valid_cred_r)?;
+    let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, &state.x, &g_r);
+
+    let expected_mac_2 = compute_mac_2(
+        crypto,
+        &prk_3e2m,
+        &get_id_cred(valid_cred_r)?,
+        valid_cred_r,
+        &state.th_2,
+    );
+
+    if state.mac_2 == expected_mac_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, &state.th_2, &state.plaintext_2, valid_cred_r);
+        // message 3 processing
+
+        let salt_4e3m = compute_salt_4e3m(crypto, &prk_3e2m, &th_3);
+
+        let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, i, &state.g_y);
+
+        let state = ProcessedM2 {
+            prk_3e2m: prk_3e2m,
+            prk_4e3m: prk_4e3m,
+            th_3: th_3,
+        };
+
+        Ok(state)
+    } else {
+        Err(EDHOCError::MacVerificationFailed)
+    }
+}
+
 pub fn i_prepare_message_3(
-    state: &mut State<ProcessedMessage2>,
+    state: &mut ProcessedM2,
     crypto: &mut impl CryptoTrait,
-    id_cred_i: &BytesIdCred,
     cred_i: &[u8],
-) -> Result<(State<Completed>, BufferMessage3, BytesHashLen), EDHOCError> {
-    let mac_3 = compute_mac_3(crypto, &state.prk_4e3m, &state.th_3, id_cred_i, cred_i);
-
-    let ead_3 = i_prepare_ead_3();
+    ead_3: &Option<EADItem>, // FIXME: make it a list of EADItem
+) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> {
+    let mac_3 = compute_mac_3(
+        crypto,
+        &state.prk_4e3m,
+        &state.th_3,
+        &get_id_cred(cred_i)?,
+        cred_i,
+    );
 
-    let plaintext_3 = encode_plaintext_3(id_cred_i, &mac_3, &ead_3)?;
+    let plaintext_3 = encode_plaintext_3(&get_id_cred(cred_i)?, &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);
@@ -466,86 +409,17 @@ pub fn i_prepare_message_3(
         0,
         SHA256_DIGEST_LEN,
     );
-    state.prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]);
-
-    let state = State {
-        current_state: PhantomData,
-        x_or_y: state.x_or_y,
-        c_i: state.c_i,
-        gy_or_gx: state.gy_or_gx,
-        prk_3e2m: state.prk_3e2m,
-        prk_4e3m: state.prk_4e3m,
-        prk_out: prk_out,
-        prk_exporter: state.prk_exporter,
-        h_message_1: state.h_message_1,
-        th_3: state.th_3,
-    };
-
-    Ok((state, message_3, prk_out))
-}
-
-// Implements auth credential checking according to draft-tiloca-lake-implem-cons
-fn credential_check_or_fetch<'a>(
-    cred_expected: Option<&'a [u8]>,
-    id_cred_received: IdCred<'a>,
-) -> Result<(&'a [u8], BytesP256ElemLen), EDHOCError> {
-    // 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
-    if let Some(cred_expected) = cred_expected {
-        // 1. Does ID_CRED_X point to a stored authentication credential? YES
-        // IMPL: compare cred_i_expected with id_cred
-        //   IMPL: assume cred_i_expected is well formed
-        let (public_key_expected, kid_expected) = parse_cred(cred_expected)?;
-        let public_key = public_key_expected;
-        let credentials_match = match id_cred_received {
-            IdCred::CompactKid(kid_received) => kid_received == kid_expected,
-            IdCred::FullCredential(cred_received) => cred_expected == cred_received,
-        };
-
-        // 2. Is this authentication credential still valid?
-        // IMPL,TODO: check cred_r_expected is still valid
-
-        // Continue by considering CRED_X as the authentication credential of the other peer.
-        // IMPL: ready to proceed, including process ead_2
-
-        if credentials_match {
-            Ok((cred_expected, public_key))
-        } else {
-            Err(EDHOCError::UnknownPeer)
-        }
-    } else {
-        // 1. Does ID_CRED_X point to a stored authentication credential? NO
-        // IMPL: cred_i_expected provided by application is None
-        //       id_cred must be a full credential
-        if let IdCred::FullCredential(cred_received) = id_cred_received {
-            // 3. Is the trust model Pre-knowledge-only? NO (hardcoded to NO for now)
-
-            // 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: parse_cred(cred_r) and check it is valid
-            match parse_cred(cred_received) {
-                Ok((public_key_received, _kid_received)) => {
-                    // 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.
-                    // IMPL: cred_r = id_cred
-                    let public_key = public_key_received;
-                    Ok((cred_received, public_key))
-                }
-                Err(_) => Err(EDHOCError::UnknownPeer),
-            }
-        } else {
-            // IMPL: should have gotten a full credential
-            Err(EDHOCError::UnknownPeer)
-        }
-    }
-
-    // 8. Is this authentication credential good to use in the context of this EDHOC session?
-    // IMPL,TODO: we just skip this step for now
+    let mut prk_exporter: BytesHashLen = Default::default();
+    prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]);
+
+    Ok((
+        Completed {
+            prk_out,
+            prk_exporter,
+        },
+        message_3,
+        prk_out,
+    ))
 }
 
 fn encode_ead_item(ead_1: &EADItem) -> Result<EdhocMessageBuffer, EDHOCError> {
@@ -911,7 +785,7 @@ fn compute_mac_2(
 
 fn encode_plaintext_2(
     c_r: u8,
-    id_cred_r: IdCred,
+    id_cred_r: &IdCred,
     mac_2: &BytesMac2,
     ead_2: &Option<EADItem>,
 ) -> Result<BufferPlaintext2, EDHOCError> {
@@ -920,7 +794,7 @@ fn encode_plaintext_2(
 
     let offset_cred = match id_cred_r {
         IdCred::CompactKid(kid) => {
-            plaintext_2.content[1] = kid;
+            plaintext_2.content[1] = *kid;
             2
         }
         IdCred::FullCredential(cred) => {
@@ -1102,8 +976,6 @@ mod tests {
         "0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b63701cccccc";
     const MESSAGE_1_WITH_DUMMY_CRITICAL_EAD_TV: &str =
         "0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b63720cccccc";
-    const PLAINTEXT_2_WITH_DUMMY_CRITICAL_EAD_TV: &str = "3248d0d1a594797d0aaf20cccccc";
-    const PLAINTEXT_3_WITH_DUMMY_CRITICAL_EAD_TV: &str = "2b48ddf106b86fd22fe420cccccc";
     const G_Y_TV: BytesP256ElemLen =
         hex!("419701d7f00a26c2dc587a36dd752549f33763c893422c8ea0f955a13a4ff5d5");
     const C_R_TV: u8 = 0x27;
@@ -1115,7 +987,6 @@ mod tests {
         hex!("356efd53771425e008f3fe3a86c83ff4c6b16e57028ff39d5236c182b202084b");
     const TH_3_TV: BytesHashLen =
         hex!("dfe5b065e64c72d226d500c12d49bee6dc4881ded0965e9bdf89d24a54f2e59a");
-    const CIPHERTEXT_3_TV: &str = "473dd16077dd71d65b56e6bd71e7a49d6012";
     const TH_4_TV: BytesHashLen =
         hex!("baf60adbc500fce789af25b108ada2275575056c52c1c2036a2da4a643891cb4");
     const PRK_2E_TV: BytesP256ElemLen =
@@ -1201,7 +1072,7 @@ 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();
+        let (suites_i, _suites_i_len, _decoder) = res.unwrap();
         assert_eq!(suites_i, SUITES_I_TV);
 
         let message_1_tv = BufferMessage1::from_hex(MESSAGE_1_TV_SUITE_ONLY_A);
@@ -1209,7 +1080,7 @@ 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();
+        let (suites_i, _suites_i_len, _decoder) = res.unwrap();
         assert_eq!(suites_i[0], 0x18);
 
         // let (suites_i, suites_i_len, raw_suites_len) =
@@ -1250,7 +1121,7 @@ mod tests {
         // 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, _suites_i_len, g_x, c_i, ead_1) = res.unwrap();
 
         assert_eq!(method, METHOD_TV_FIRST_TIME);
         assert_eq!(suites_i, SUITES_I_TV_FIRST_TIME);
@@ -1261,7 +1132,7 @@ 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, _suites_i_len, g_x, c_i, ead_1) = res.unwrap();
 
         assert_eq!(method, METHOD_TV);
         assert_eq!(suites_i, SUITES_I_TV);
@@ -1438,7 +1309,7 @@ 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::CompactKid(ID_CRED_R_TV[ID_CRED_R_TV.len() - 1]),
             &MAC_2_TV,
             &None::<EADItem>,
         )
@@ -1461,7 +1332,6 @@ mod tests {
     #[test]
     fn test_decode_plaintext_2() {
         let plaintext_2_tv = BufferPlaintext2::from_hex(PLAINTEXT_2_TV);
-        let ead_2_tv = [0x00u8; 0];
 
         let plaintext_2 = decode_plaintext_2(&plaintext_2_tv);
         assert!(plaintext_2.is_ok());
@@ -1662,6 +1532,7 @@ mod tests {
         assert_eq!(ead_1.value.unwrap().content, ead_value_tv.content);
     }
 
+    #[test]
     fn test_compute_prk_out() {
         let mut prk_out: BytesHashLen = [0x00; SHA256_DIGEST_LEN];
         let mut th_4_context: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN];
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index 4be01b36..4be6c62a 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -15,20 +15,19 @@
 //! [EDHOC]: https://datatracker.ietf.org/doc/draft-ietf-lake-edhoc/
 #![cfg_attr(not(test), no_std)]
 
-pub use {
-    lakers_shared::Crypto as CryptoTrait, lakers_shared::State as EdhocState, lakers_shared::*,
-};
+pub use {lakers_shared::Crypto as CryptoTrait, lakers_shared::*};
 
-#[cfg(any(feature = "ead-none", feature = "ead-zeroconf"))]
+#[cfg(any(feature = "ead-none", feature = "ead-zeroconf", feature = "ead-authz"))]
 pub use lakers_ead::*;
 
 mod edhoc;
 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<'a, Crypto: CryptoTrait> {
-    state: EdhocState<Start>, // opaque state
+    state: InitiatorStart,    // opaque state
     i: &'a [u8],              // private authentication key of I
     cred_i: &'a [u8],         // I's full credential
     cred_r: Option<&'a [u8]>, // R's full credential (if provided)
@@ -37,30 +36,39 @@ pub struct EdhocInitiator<'a, Crypto: CryptoTrait> {
 
 #[derive(Debug)]
 pub struct EdhocInitiatorWaitM2<'a, Crypto: CryptoTrait> {
-    state: EdhocState<WaitMessage2>, // opaque state
-    i: &'a [u8],                     // private authentication key of I
-    cred_i: &'a [u8],                // I's full credential
-    cred_r: Option<&'a [u8]>,        // R's full credential (if provided)
+    state: WaitM2,            // opaque state
+    i: &'a [u8],              // private authentication key of I
+    cred_i: &'a [u8],         // I's full credential
+    cred_r: Option<&'a [u8]>, // R's full credential (if provided)
+    crypto: Crypto,
+}
+
+#[derive(Debug)]
+pub struct EdhocInitiatorProcessingM2<'a, Crypto: CryptoTrait> {
+    state: ProcessingM2,      // opaque state
+    i: &'a [u8],              // private authentication key of I
+    cred_i: &'a [u8],         // I's full credential
+    cred_r: Option<&'a [u8]>, // R's full credential (if provided)
     crypto: Crypto,
 }
 
 #[derive(Debug)]
-pub struct EdhocInitiatorBuildM3<'a, Crypto: CryptoTrait> {
-    state: EdhocState<ProcessedMessage2>, // opaque state
-    cred_i: &'a [u8],                     // I's full credential
+pub struct EdhocInitiatorProcessedM2<'a, Crypto: CryptoTrait> {
+    state: ProcessedM2, // opaque state
+    cred_i: &'a [u8],   // I's full credential
     crypto: Crypto,
 }
 
 #[derive(Debug)]
 pub struct EdhocInitiatorDone<Crypto: CryptoTrait> {
-    state: EdhocState<Completed>,
+    state: Completed,
     crypto: Crypto,
 }
 
 /// Starting point for performing EDHOC in the role of the Responder.
 #[derive(Debug)]
 pub struct EdhocResponder<'a, Crypto: CryptoTrait> {
-    state: EdhocState<Start>, // opaque state
+    state: ResponderStart,    // opaque state
     r: &'a [u8],              // private authentication key of R
     cred_r: &'a [u8],         // R's full credential
     cred_i: Option<&'a [u8]>, // I's full credential (if provided)
@@ -68,39 +76,46 @@ pub struct EdhocResponder<'a, Crypto: CryptoTrait> {
 }
 
 #[derive(Debug)]
-pub struct EdhocResponderBuildM2<'a, Crypto: CryptoTrait> {
-    state: EdhocState<ProcessedMessage1>, // opaque state
-    r: &'a [u8],                          // private authentication key of R
-    cred_r: &'a [u8],                     // R's full credential
-    cred_i: Option<&'a [u8]>,             // I's full credential (if provided)
+pub struct EdhocResponderProcessedM1<'a, Crypto: CryptoTrait> {
+    state: ProcessingM1,      // opaque state
+    r: &'a [u8],              // private authentication key of R
+    cred_r: &'a [u8],         // R's full credential
+    cred_i: Option<&'a [u8]>, // I's full credential (if provided)
     crypto: Crypto,
 }
 
 #[derive(Debug)]
 pub struct EdhocResponderWaitM3<'a, Crypto: CryptoTrait> {
-    state: EdhocState<WaitMessage3>, // opaque state
-    cred_i: Option<&'a [u8]>,        // I's full credential (if provided)
+    state: WaitM3,            // opaque state
+    cred_i: Option<&'a [u8]>, // I's full credential (if provided)
+    crypto: Crypto,
+}
+
+#[derive(Debug)]
+pub struct EdhocResponderProcessingM3<'a, Crypto: CryptoTrait> {
+    state: ProcessingM3,      // opaque state
+    cred_i: Option<&'a [u8]>, // I's full credential (if provided)
     crypto: Crypto,
 }
 
 #[derive(Debug)]
 pub struct EdhocResponderDone<Crypto: CryptoTrait> {
-    state: EdhocState<Completed>,
+    state: Completed,
     crypto: Crypto,
 }
 
 impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> {
     pub fn new(
-        state: EdhocState<Start>,
-        crypto: Crypto,
+        mut crypto: Crypto,
         r: &'a [u8],
         cred_r: &'a [u8],
         cred_i: Option<&'a [u8]>,
     ) -> Self {
         assert!(r.len() == P256_ELEM_LEN);
+        let (y, g_y) = crypto.p256_generate_key_pair();
 
         EdhocResponder {
-            state,
+            state: ResponderStart { y, g_y },
             r,
             cred_r,
             cred_i,
@@ -111,34 +126,42 @@ impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> {
     pub fn process_message_1(
         mut self,
         message_1: &BufferMessage1,
-    ) -> Result<EdhocResponderBuildM2<'a, Crypto>, EDHOCError> {
-        let state = r_process_message_1(self.state, &mut self.crypto, message_1)?;
-
-        Ok(EdhocResponderBuildM2 {
-            state,
-            r: self.r,
-            cred_r: self.cred_r,
-            cred_i: self.cred_i,
-            crypto: self.crypto,
-        })
+    ) -> Result<(EdhocResponderProcessedM1<'a, Crypto>, Option<EADItem>), EDHOCError> {
+        let (state, ead_1) = r_process_message_1(self.state, &mut self.crypto, message_1)?;
+
+        Ok((
+            EdhocResponderProcessedM1 {
+                state,
+                r: self.r,
+                cred_r: self.cred_r,
+                cred_i: self.cred_i,
+                crypto: self.crypto,
+            },
+            ead_1,
+        ))
     }
 }
 
-impl<'a, Crypto: CryptoTrait> EdhocResponderBuildM2<'a, Crypto> {
+impl<'a, Crypto: CryptoTrait> EdhocResponderProcessedM1<'a, Crypto> {
     pub fn prepare_message_2(
         mut self,
-        c_r: u8,
+        id_cred_r: &IdCred,
+        c_r: Option<u8>,
+        ead_2: &Option<EADItem>,
     ) -> Result<(EdhocResponderWaitM3<'a, Crypto>, BufferMessage2), EDHOCError> {
-        let (y, g_y) = self.crypto.p256_generate_key_pair();
+        let c_r = match c_r {
+            Some(c_r) => c_r,
+            None => generate_connection_identifier_cbor(&mut self.crypto),
+        };
 
         match r_prepare_message_2(
             self.state,
             &mut self.crypto,
             self.cred_r,
             self.r.try_into().expect("Wrong length of private key"),
-            y,
-            g_y,
             c_r,
+            id_cred_r,
+            ead_2,
         ) {
             Ok((state, message_2)) => Ok((
                 EdhocResponderWaitM3 {
@@ -154,11 +177,38 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderBuildM2<'a, Crypto> {
 }
 
 impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3<'a, Crypto> {
-    pub fn process_message_3(
+    pub fn parse_message_3(
         mut self,
-        message_3: &BufferMessage3,
+        message_3: &'a BufferMessage3,
+    ) -> Result<
+        (
+            EdhocResponderProcessingM3<Crypto>,
+            IdCredOwned,
+            Option<EADItem>,
+        ),
+        EDHOCError,
+    > {
+        match r_parse_message_3(&mut self.state, &mut self.crypto, message_3) {
+            Ok((state, id_cred_i, ead_3)) => Ok((
+                EdhocResponderProcessingM3 {
+                    state,
+                    crypto: self.crypto,
+                    cred_i: self.cred_i,
+                },
+                id_cred_i,
+                ead_3,
+            )),
+            Err(error) => Err(error),
+        }
+    }
+}
+
+impl<'a, Crypto: CryptoTrait> EdhocResponderProcessingM3<'a, Crypto> {
+    pub fn verify_message_3(
+        mut self,
+        cred_i: &[u8],
     ) -> Result<(EdhocResponderDone<Crypto>, [u8; SHA256_DIGEST_LEN]), EDHOCError> {
-        match r_process_message_3(&mut self.state, &mut self.crypto, message_3, self.cred_i) {
+        match r_verify_message_3(&mut self.state, &mut self.crypto, cred_i) {
             Ok((state, prk_out)) => Ok((
                 EdhocResponderDone {
                     state,
@@ -206,16 +256,25 @@ impl<Crypto: CryptoTrait> EdhocResponderDone<Crypto> {
 
 impl<'a, Crypto: CryptoTrait> EdhocInitiator<'a, Crypto> {
     pub fn new(
-        state: EdhocState<Start>,
-        crypto: Crypto,
+        mut crypto: Crypto,
         i: &'a [u8],
         cred_i: &'a [u8],
         cred_r: Option<&'a [u8]>,
     ) -> Self {
         assert!(i.len() == P256_ELEM_LEN);
+        // 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) = crypto.p256_generate_key_pair();
 
         EdhocInitiator {
-            state,
+            state: InitiatorStart {
+                x,
+                g_x,
+                suites_i,
+                suites_i_len,
+            },
             i,
             cred_i,
             cred_r,
@@ -225,11 +284,15 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator<'a, Crypto> {
 
     pub fn prepare_message_1(
         mut self,
-        c_i: u8,
-    ) -> Result<(EdhocInitiatorWaitM2<'a, Crypto>, BufferMessage1), EDHOCError> {
-        let (x, g_x) = self.crypto.p256_generate_key_pair();
-
-        match i_prepare_message_1(self.state, &mut self.crypto, x, g_x, c_i) {
+        c_i: Option<u8>,
+        ead_1: &Option<EADItem>,
+    ) -> Result<(EdhocInitiatorWaitM2<'a, Crypto>, EdhocMessageBuffer), EDHOCError> {
+        let c_i = match c_i {
+            Some(c_i) => c_i,
+            None => generate_connection_identifier_cbor(&mut self.crypto),
+        };
+
+        match i_prepare_message_1(self.state, &mut self.crypto, c_i, ead_1) {
             Ok((state, message_1)) => Ok((
                 EdhocInitiatorWaitM2 {
                     state,
@@ -246,35 +309,63 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator<'a, Crypto> {
 }
 
 impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2<'a, Crypto> {
-    pub fn process_message_2(
+    pub fn parse_message_2(
         mut self,
-        message_2: &BufferMessage2,
-    ) -> Result<(EdhocInitiatorBuildM3<'a, Crypto>, u8), EDHOCError> {
-        match i_process_message_2(
-            self.state,
-            &mut self.crypto,
-            message_2,
-            self.cred_r,
-            self.i
-                .try_into()
-                .expect("Wrong length of initiator private key"),
-        ) {
-            Ok((state, c_r, _kid)) => Ok((
-                EdhocInitiatorBuildM3 {
+        message_2: &'a BufferMessage2,
+    ) -> Result<
+        (
+            EdhocInitiatorProcessingM2<'a, Crypto>,
+            u8,
+            IdCredOwned,
+            Option<EADItem>,
+        ),
+        EDHOCError,
+    > {
+        match i_parse_message_2(self.state, &mut self.crypto, message_2) {
+            Ok((state, c_r, id_cred_r, ead_2)) => Ok((
+                EdhocInitiatorProcessingM2 {
                     state,
+                    i: self.i,
                     cred_i: self.cred_i,
+                    cred_r: self.cred_r,
                     crypto: self.crypto,
                 },
                 c_r,
+                id_cred_r,
+                ead_2,
             )),
             Err(error) => Err(error),
         }
     }
 }
 
-impl<'a, Crypto: CryptoTrait> EdhocInitiatorBuildM3<'a, Crypto> {
+impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessingM2<'a, Crypto> {
+    pub fn verify_message_2(
+        mut self,
+        valid_cred_r: &[u8],
+    ) -> Result<EdhocInitiatorProcessedM2<'a, Crypto>, EDHOCError> {
+        match i_verify_message_2(
+            self.state,
+            &mut self.crypto,
+            valid_cred_r,
+            self.i
+                .try_into()
+                .expect("Wrong length of initiator private key"),
+        ) {
+            Ok(state) => Ok(EdhocInitiatorProcessedM2 {
+                state,
+                cred_i: self.cred_i,
+                crypto: self.crypto,
+            }),
+            Err(error) => Err(error),
+        }
+    }
+}
+
+impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessedM2<'a, Crypto> {
     pub fn prepare_message_3(
         mut self,
+        ead_3: &Option<EADItem>,
     ) -> Result<
         (
             EdhocInitiatorDone<Crypto>,
@@ -283,12 +374,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorBuildM3<'a, Crypto> {
         ),
         EDHOCError,
     > {
-        match i_prepare_message_3(
-            &mut self.state,
-            &mut self.crypto,
-            &get_id_cred(self.cred_i)?,
-            self.cred_i,
-        ) {
+        match i_prepare_message_3(&mut self.state, &mut self.crypto, self.cred_i, ead_3) {
             Ok((state, message_3, prk_out)) => Ok((
                 EdhocInitiatorDone {
                     state,
@@ -356,6 +442,70 @@ pub fn generate_connection_identifier<Crypto: CryptoTrait>(crypto: &mut Crypto)
     conn_id
 }
 
+// Implements auth credential checking according to draft-tiloca-lake-implem-cons
+pub fn credential_check_or_fetch<'a>(
+    cred_expected: Option<EdhocMessageBuffer>,
+    id_cred_received: IdCredOwned,
+) -> Result<(EdhocMessageBuffer, BytesP256ElemLen), EDHOCError> {
+    // 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
+    if let Some(cred_expected) = cred_expected {
+        // 1. Does ID_CRED_X point to a stored authentication credential? YES
+        // IMPL: compare cred_i_expected with id_cred
+        //   IMPL: assume cred_i_expected is well formed
+        let (public_key_expected, kid_expected) = parse_cred(cred_expected.as_slice())?;
+        let public_key = public_key_expected;
+        let credentials_match = match id_cred_received {
+            IdCredOwned::CompactKid(kid_received) => kid_received == kid_expected,
+            IdCredOwned::FullCredential(cred_received) => cred_expected == cred_received,
+        };
+
+        // 2. Is this authentication credential still valid?
+        // IMPL,TODO: check cred_r_expected is still valid
+
+        // Continue by considering CRED_X as the authentication credential of the other peer.
+        // IMPL: ready to proceed, including process ead_2
+
+        if credentials_match {
+            Ok((cred_expected, public_key))
+        } else {
+            Err(EDHOCError::UnknownPeer)
+        }
+    } else {
+        // 1. Does ID_CRED_X point to a stored authentication credential? NO
+        // IMPL: cred_i_expected provided by application is None
+        //       id_cred must be a full credential
+        if let IdCredOwned::FullCredential(cred_received) = id_cred_received {
+            // 3. Is the trust model Pre-knowledge-only? NO (hardcoded to NO for now)
+
+            // 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: parse_cred(cred_r) and check it is valid
+            match parse_cred(cred_received.as_slice()) {
+                Ok((public_key_received, _kid_received)) => {
+                    // 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.
+                    // IMPL: cred_r = id_cred
+                    let public_key = public_key_received;
+                    Ok((cred_received, public_key))
+                }
+                Err(_) => Err(EDHOCError::UnknownPeer),
+            }
+        } else {
+            // IMPL: should have gotten a full credential
+            Err(EDHOCError::UnknownPeer)
+        }
+    }
+
+    // 8. Is this authentication credential good to use in the context of this EDHOC session?
+    // IMPL,TODO: we just skip this step for now
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -369,12 +519,9 @@ mod test {
     const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
     const I: &[u8] = &hex!("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b");
     const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac");
-    const G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6"); // used
     const _G_I_Y_COORD: &[u8] =
         &hex!("6e5de611388a4b8a8211334ac7d37ecb52a387d257e6db3c2a93df21ff3affc8"); // not used
     const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072");
-    const G_R: &[u8] = &hex!("bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f0");
-    const C_R_TV: [u8; 1] = hex!("27");
 
     const MESSAGE_1_TV_FIRST_TIME: &str =
         "03065820741a13d7ba048fbb615e94386aa3b61bea5b3d8f65f32620b749bee8d278efa90e";
@@ -383,36 +530,30 @@ mod test {
 
     #[test]
     fn test_new_initiator() {
-        let state = Default::default();
-        let _initiator = EdhocInitiator::new(state, default_crypto(), I, CRED_I, Some(CRED_R));
-        let state = Default::default();
-        let _initiator = EdhocInitiator::new(state, default_crypto(), I, CRED_I, None);
+        let _initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R));
+        let _initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, None);
     }
 
     #[test]
     fn test_new_responder() {
-        let state = Default::default();
-        let _responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, Some(CRED_I));
-        let state = Default::default();
-        let _responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, None);
+        let _responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I));
+        let _responder = EdhocResponder::new(default_crypto(), R, CRED_R, None);
     }
 
     #[test]
     fn test_prepare_message_1() {
-        let state = Default::default();
-        let initiator = EdhocInitiator::new(state, default_crypto(), I, CRED_I, Some(CRED_R));
+        let initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R));
 
         let c_i = generate_connection_identifier_cbor(&mut default_crypto());
-        let message_1 = initiator.prepare_message_1(c_i);
-        assert!(message_1.is_ok());
+        let result = initiator.prepare_message_1(Some(c_i), &None);
+        assert!(result.is_ok());
     }
 
     #[test]
     fn test_process_message_1() {
         let message_1_tv_first_time = EdhocMessageBuffer::from_hex(MESSAGE_1_TV_FIRST_TIME);
         let message_1_tv = EdhocMessageBuffer::from_hex(MESSAGE_1_TV);
-        let state = Default::default();
-        let responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, Some(CRED_I));
+        let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I));
 
         // process message_1 first time, when unsupported suite is selected
         let error = responder.process_message_1(&message_1_tv_first_time);
@@ -421,8 +562,7 @@ mod test {
 
         // We need to create a new responder -- no message is supposed to be processed twice by a
         // responder or initiator
-        let state = Default::default();
-        let responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, Some(CRED_I));
+        let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I));
 
         // process message_1 second time
         let error = responder.process_message_1(&message_1_tv);
@@ -438,27 +578,40 @@ mod test {
     #[cfg(feature = "ead-none")]
     #[test]
     fn test_handshake() {
-        let state_initiator = Default::default();
-        let initiator =
-            EdhocInitiator::new(state_initiator, default_crypto(), I, CRED_I, Some(CRED_R));
-        let state_responder = Default::default();
-        let responder =
-            EdhocResponder::new(state_responder, default_crypto(), R, CRED_R, Some(CRED_I));
-
-        let c_i: u8 = generate_connection_identifier_cbor(&mut default_crypto());
-        let (initiator, result) = initiator.prepare_message_1(c_i).unwrap(); // to update the state
-
-        let responder = responder.process_message_1(&result).unwrap();
-
-        let c_r = generate_connection_identifier_cbor(&mut default_crypto());
-        let (responder, message_2) = responder.prepare_message_2(c_r).unwrap();
-
-        assert!(c_r != 0xff);
-        let (initiator, _) = initiator.process_message_2(&message_2).unwrap();
-
-        let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3().unwrap();
-
-        let (mut responder, r_prk_out) = responder.process_message_3(&message_3).unwrap();
+        let initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R));
+        let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I));
+
+        // ---- begin initiator handling
+        // if needed: prepare ead_1
+        let (initiator, message_1) = initiator.prepare_message_1(None, &None).unwrap();
+        // ---- end initiator handling
+
+        // ---- begin responder handling
+        let (responder, _ead_1) = responder.process_message_1(&message_1).unwrap();
+        // if ead_1: process ead_1
+        // if needed: prepare ead_2
+        let kid = IdCred::CompactKid(ID_CRED_R[3]);
+        let (responder, message_2) = responder.prepare_message_2(&kid, None, &None).unwrap();
+        // ---- end responder handling
+
+        // ---- being initiator handling
+        let (initiator, _c_r, id_cred_r, _ead_2) = initiator.parse_message_2(&message_2).unwrap();
+        let (valid_cred_r, _g_r) =
+            credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap();
+        let initiator = initiator.verify_message_2(valid_cred_r.as_slice()).unwrap();
+
+        // if needed: prepare ead_3
+        let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3(&None).unwrap();
+        // ---- end initiator handling
+
+        // ---- begin responder handling
+        let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap();
+        let (valid_cred_i, _g_i) =
+            credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap();
+        // if ead_3: process ead_3
+        let (mut responder, r_prk_out) =
+            responder.verify_message_3(valid_cred_i.as_slice()).unwrap();
+        // ---- end responder handling
 
         // check that prk_out is equal at initiator and responder side
         assert_eq!(i_prk_out, r_prk_out);
@@ -498,138 +651,75 @@ mod test {
     const LOC_W_TV: &[u8] = &hex!("636F61703A2F2F656E726F6C6C6D656E742E736572766572");
 
     // TODO: have a setup_test function that prepares the common objects for the ead tests
-    #[cfg(feature = "ead-zeroconf")]
+    #[cfg(feature = "ead-authz")]
     #[test]
-    fn test_ead_zeroconf() {
+    fn test_ead_authz() {
         // ==== initialize edhoc ====
-        let initiator = EdhocInitiator::new(Default::default(), default_crypto(), I, CRED_I, None);
-        let responder = EdhocResponder::new(
-            Default::default(),
-            default_crypto(),
-            R,
-            CRED_R,
-            Some(CRED_I),
-        );
-
-        // ==== initialize ead-zeroconf ====
-        let id_u: EdhocMessageBuffer = ID_U_TV.try_into().unwrap();
-        let g_w: BytesP256ElemLen = G_W_TV.try_into().unwrap();
-        let loc_w: EdhocMessageBuffer = LOC_W_TV.try_into().unwrap();
-
-        ead_initiator_set_global_state(EADInitiatorState::new(id_u, g_w, loc_w));
-        let ead_initiator_state = ead_initiator_get_global_state();
-        assert_eq!(
-            ead_initiator_state.protocol_state,
-            EADInitiatorProtocolState::Start
-        );
-
-        ead_responder_set_global_state(EADResponderState::new());
-        let ead_responder_state = ead_responder_get_global_state();
-        assert_eq!(
-            ead_responder_state.protocol_state,
-            EADResponderProtocolState::Start
+        let initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R));
+        let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I));
+
+        // ==== initialize ead-authz ====
+        let device = ZeroTouchDevice::new(
+            ID_U_TV.try_into().unwrap(),
+            G_W_TV.try_into().unwrap(),
+            LOC_W_TV.try_into().unwrap(),
         );
+        let authenticator = ZeroTouchAuthenticator::default();
 
-        let mut acl = EdhocMessageBuffer::new();
-        let (_g, kid_i) = parse_cred(CRED_I).unwrap();
-        acl.push(kid_i).unwrap();
-        mock_ead_server_set_global_state(MockEADServerState::new(
-            CRED_R,
+        let acl = EdhocMessageBuffer::new_from_slice(&[ID_CRED_I[3]]).unwrap();
+        let server = ZeroTouchServer::new(
             W_TV.try_into().unwrap(),
+            CRED_R.try_into().unwrap(),
             Some(acl),
-        ));
-
-        let c_i = generate_connection_identifier_cbor(&mut default_crypto());
-        let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap();
-        assert_eq!(
-            ead_initiator_state.protocol_state,
-            EADInitiatorProtocolState::WaitEAD2
         );
 
-        // ==== begin edhoc with ead-zeroconf ====
-        let responder = responder.process_message_1(&message_1).unwrap();
-        assert_eq!(
-            ead_responder_state.protocol_state,
-            EADResponderProtocolState::ProcessedEAD1
-        );
+        // ==== begin edhoc with ead-authz ====
 
-        let c_r = generate_connection_identifier_cbor(&mut default_crypto());
-        let (responder, message_2) = responder.prepare_message_2(c_r).unwrap();
-        assert_eq!(
-            ead_responder_state.protocol_state,
-            EADResponderProtocolState::Completed
+        let (ead_1, mut device) = device.prepare_ead_1(
+            &mut default_crypto(),
+            &initiator.state.x, // FIXME: avoid accessing private ephemeral key from application code
+            initiator.state.suites_i[initiator.state.suites_i_len - 1],
         );
+        let (initiator, message_1) = initiator.prepare_message_1(None, &Some(ead_1)).unwrap();
+        device.set_h_message_1(initiator.state.h_message_1.clone());
+
+        let (responder, ead_1) = responder.process_message_1(&message_1).unwrap();
+        let ead_2 = if let Some(ead_1) = ead_1 {
+            let (_loc_w, voucher_request, authenticator) =
+                authenticator.process_ead_1(&ead_1, &message_1).unwrap();
+
+            // the line below mocks a request to the server: let voucher_response = auth_client.post(loc_w, voucher_request)?
+            let voucher_response = server
+                .handle_voucher_request(&mut default_crypto(), &voucher_request)
+                .unwrap();
+
+            let res = authenticator.prepare_ead_2(&voucher_response);
+            assert!(res.is_ok());
+            authenticator.prepare_ead_2(&voucher_response).ok()
+        } else {
+            None
+        };
+        let kid = IdCred::CompactKid(ID_CRED_R[3]);
+        let (responder, message_2) = responder.prepare_message_2(&kid, None, &ead_2).unwrap();
+
+        let (initiator, _c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap();
+        let (valid_cred_r, _g_r) =
+            credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), 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(valid_cred_r.as_slice()).unwrap();
 
-        let (initiator, _) = initiator.process_message_2(&message_2).unwrap();
-
-        assert_eq!(
-            ead_initiator_state.protocol_state,
-            EADInitiatorProtocolState::Completed
-        );
+        let (mut _initiator, message_3, i_prk_out) = initiator.prepare_message_3(&None).unwrap();
 
-        let (initiator, message_3, i_prk_out) = initiator.prepare_message_3().unwrap();
+        let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap();
+        let (valid_cred_i, _g_i) =
+            credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap();
+        let (mut _responder, r_prk_out) =
+            responder.verify_message_3(valid_cred_i.as_slice()).unwrap();
 
-        let (mut responder, r_prk_out) = responder.process_message_3(&message_3).unwrap();
+        // check that prk_out is equal at initiator and responder side
         assert_eq!(i_prk_out, r_prk_out);
-        assert_eq!(
-            ead_responder_state.protocol_state,
-            EADResponderProtocolState::Completed
-        );
-    }
-
-    #[cfg(feature = "ead-zeroconf")]
-    #[test]
-    fn test_ead_zeroconf_not_authorized() {
-        // ==== initialize edhoc ====
-        let initiator = EdhocInitiator::new(Default::default(), default_crypto(), I, CRED_I, None);
-        let responder = EdhocResponder::new(
-            Default::default(),
-            default_crypto(),
-            R,
-            CRED_R,
-            Some(CRED_I),
-        );
-
-        // ==== initialize ead-zeroconf ====
-        let id_u: EdhocMessageBuffer = ID_U_TV.try_into().unwrap();
-        let g_w: BytesP256ElemLen = G_W_TV.try_into().unwrap();
-        let loc_w: EdhocMessageBuffer = LOC_W_TV.try_into().unwrap();
-
-        ead_initiator_set_global_state(EADInitiatorState::new(id_u, g_w, loc_w));
-        let ead_initiator_state = ead_initiator_get_global_state();
-        assert_eq!(
-            ead_initiator_state.protocol_state,
-            EADInitiatorProtocolState::Start
-        );
-
-        ead_responder_set_global_state(EADResponderState::new());
-        let ead_responder_state = ead_responder_get_global_state();
-        assert_eq!(
-            ead_responder_state.protocol_state,
-            EADResponderProtocolState::Start
-        );
-
-        let mut acl = EdhocMessageBuffer::new();
-        let (_g, kid_i) = parse_cred(CRED_I).unwrap();
-        let invalid_kid = kid_i + 1;
-        acl.push(invalid_kid).unwrap();
-        mock_ead_server_set_global_state(MockEADServerState::new(
-            CRED_R,
-            W_TV.try_into().unwrap(),
-            Some(acl),
-        ));
-
-        let c_i = generate_connection_identifier_cbor(&mut default_crypto());
-        let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap();
-        assert_eq!(
-            ead_initiator_state.protocol_state,
-            EADInitiatorProtocolState::WaitEAD2
-        );
-
-        // ==== begin edhoc with ead-zeroconf ====
-        assert_eq!(
-            responder.process_message_1(&message_1).unwrap_err(),
-            EDHOCError::EADError
-        );
     }
 }
diff --git a/shared/src/lib.rs b/shared/src/lib.rs
index 5b5b5417..53fb15d0 100644
--- a/shared/src/lib.rs
+++ b/shared/src/lib.rs
@@ -9,8 +9,6 @@
 //! [lakers-ead-dispatch]: https://docs.rs/lakers-ead-dispatch/latest/lakers_ead_dispatch/
 #![no_std]
 
-use core::marker::PhantomData;
-
 pub use cbor::*;
 pub use cbor_decoder::*;
 pub use edhoc_parser::*;
@@ -97,31 +95,6 @@ pub type BytesMac = [u8; MAC_LENGTH];
 pub type BytesEncodedVoucher = [u8; ENCODED_VOUCHER_LEN];
 pub type EADMessageBuffer = EdhocMessageBuffer; // TODO: make it of size MAX_EAD_SIZE_LEN
 
-// This is sealed
-pub trait EDHOCState: core::fmt::Debug {}
-// For both initiator and responder
-#[derive(Debug)]
-pub struct Start;
-impl EDHOCState for Start {}
-// For the initiator
-#[derive(Debug)]
-pub struct WaitMessage2;
-impl EDHOCState for WaitMessage2 {}
-#[derive(Debug)]
-pub struct ProcessedMessage2;
-impl EDHOCState for ProcessedMessage2 {}
-// For the responder
-#[derive(Debug)]
-pub struct ProcessedMessage1;
-impl EDHOCState for ProcessedMessage1 {}
-#[derive(Debug)]
-pub struct WaitMessage3;
-impl EDHOCState for WaitMessage3 {}
-// For both again
-#[derive(Debug)]
-pub struct Completed;
-impl EDHOCState for Completed {}
-
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum EDHOCError {
@@ -136,37 +109,80 @@ pub enum EDHOCError {
     UnknownError = 9,
 }
 
-#[repr(C)]
 #[derive(Debug)]
-pub struct State<Phase: EDHOCState> {
-    pub current_state: PhantomData<Phase>,
-    pub x_or_y: BytesP256ElemLen,   // ephemeral private key of myself
-    pub c_i: u8,                    // connection identifier chosen by the initiator
-    pub gy_or_gx: BytesP256ElemLen, // g_y or g_x, ephemeral public key of the peer
+pub struct InitiatorStart {
+    pub suites_i: BytesSuites,
+    pub suites_i_len: usize,
+    pub x: BytesP256ElemLen,   // ephemeral private key of myself
+    pub g_x: BytesP256ElemLen, // ephemeral public key of myself
+}
+
+#[derive(Debug)]
+pub struct ResponderStart {
+    pub y: BytesP256ElemLen,   // ephemeral private key of myself
+    pub g_y: BytesP256ElemLen, // ephemeral public key of myself
+}
+
+#[derive(Debug)]
+pub struct ProcessingM1 {
+    pub y: BytesP256ElemLen,
+    pub g_y: BytesP256ElemLen,
+    pub c_i: u8,
+    pub g_x: BytesP256ElemLen, // ephemeral public key of the initiator
+    pub h_message_1: BytesHashLen,
+}
+
+#[derive(Debug)]
+pub struct WaitM2 {
+    pub x: BytesP256ElemLen, // ephemeral private key of the initiator
+    pub h_message_1: BytesHashLen,
+}
+
+#[derive(Debug)]
+pub struct WaitM3 {
+    pub y: BytesP256ElemLen, // ephemeral private key of the responder
+    pub prk_3e2m: BytesHashLen,
+    pub th_3: BytesHashLen,
+}
+
+#[derive(Debug)]
+pub struct ProcessingM2 {
+    pub mac_2: BytesMac2,
+    pub prk_2e: BytesHashLen,
+    pub th_2: BytesHashLen,
+    pub x: BytesP256ElemLen,
+    pub g_y: BytesP256ElemLen,
+    pub plaintext_2: EdhocMessageBuffer,
+}
+
+#[derive(Debug)]
+pub struct ProcessedM2 {
     pub prk_3e2m: BytesHashLen,
     pub prk_4e3m: BytesHashLen,
-    pub prk_out: BytesHashLen,
-    pub prk_exporter: BytesHashLen,
-    pub h_message_1: BytesHashLen,
     pub th_3: BytesHashLen,
 }
 
-impl Default for State<Start> {
-    fn default() -> Self {
-        // This is also what `#[derive(Default)]` would do, but we can't limit that to Start.
-        State {
-            current_state: Default::default(),
-            x_or_y: Default::default(),
-            c_i: Default::default(),
-            gy_or_gx: Default::default(),
-            prk_3e2m: Default::default(),
-            prk_4e3m: Default::default(),
-            prk_out: Default::default(),
-            prk_exporter: Default::default(),
-            h_message_1: Default::default(),
-            th_3: Default::default(),
-        }
-    }
+#[derive(Debug)]
+pub struct ProcessingM3 {
+    pub mac_3: BytesMac3,
+    pub y: BytesP256ElemLen, // ephemeral private key of the responder
+    pub prk_3e2m: BytesHashLen,
+    pub th_3: BytesHashLen,
+    pub plaintext_3: EdhocMessageBuffer,
+}
+
+#[derive(Debug)]
+pub struct PreparingM3 {
+    pub prk_3e2m: BytesHashLen,
+    pub prk_4e3m: BytesHashLen,
+    pub th_3: BytesHashLen,
+    pub mac_3: BytesMac3,
+}
+
+#[derive(Debug)]
+pub struct Completed {
+    pub prk_out: BytesHashLen,
+    pub prk_exporter: BytesHashLen,
 }
 
 /// An owned u8 vector of a limited length
@@ -197,6 +213,15 @@ impl EdhocMessageBuffer {
         }
     }
 
+    pub fn new_from_slice(slice: &[u8]) -> Result<Self, ()> {
+        let mut buffer = Self::new();
+        if buffer.fill_with_slice(slice).is_ok() {
+            Ok(buffer)
+        } else {
+            Err(())
+        }
+    }
+
     pub fn get(self, index: usize) -> Option<u8> {
         self.content.get(index).copied()
     }
@@ -290,12 +315,19 @@ impl EADTrait for EADItem {
     }
 }
 
-#[derive(Debug)]
+// FIXME: homogenize the two structs below (likey keep only the owned version)
+#[derive(Debug, Clone, Copy)]
 pub enum IdCred<'a> {
     CompactKid(u8),
     FullCredential(&'a [u8]),
 }
 
+#[derive(Debug, Clone, Copy)]
+pub enum IdCredOwned {
+    CompactKid(u8),
+    FullCredential(EdhocMessageBuffer),
+}
+
 // TODO: remove this, once ead-zeroconf is adjusted to use cbor_decoder
 mod cbor {
     use super::*;


Test patch:
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index dc8d74dc..74601c8c 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -21,15 +21,12 @@ jobs:
     - name: Check if code is well formatted
       run: cargo fmt --check
 
-  unit-tests: # run before build because it is faster
+  unit-tests-default:
     needs: check-style
     runs-on: ubuntu-latest
 
     strategy:
       fail-fast: false
-      matrix:
-        crypto_backend: [lakers-crypto/hacspec, lakers-crypto/psa, lakers-crypto/rustcrypto]
-        ead: [ead-none, ead-zeroconf]
 
     steps:
     - name: Checkout repo
@@ -38,6 +35,21 @@ jobs:
     - name: Run unit tests with default features
       run: RUST_BACKTRACE=1 cargo test
 
+
+  unit-tests:
+    needs: check-style
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        crypto_backend: [lakers-crypto/hacspec, lakers-crypto/psa, lakers-crypto/rustcrypto]
+        ead: [ead-none, ead-zeroconf]
+
+    steps:
+    - name: Checkout repo
+      uses: actions/checkout@v3
+
     - name: Run unit tests with feature matrix # note that we only add `--package lakers-ead-zeroconf` when testing with that config
       run: RUST_BACKTRACE=1 cargo test -p lakers -p lakers-crypto -p lakers-shared ${{ matrix.ead == 'ead-zeroconf' && '-p lakers-ead-zeroconf' || '' }}  --no-default-features --features="${{ matrix.crypto_backend }}, ${{ matrix.ead }}" --no-fail-fast -- --test-threads 1
 
@@ -66,7 +78,6 @@ jobs:
 
 
   generate-fstar:
-    needs: unit-tests
     runs-on: ubuntu-latest
 
     steps: