Skip to main content

co_didcomm/
lib.rs

1//! Rust implementation of DIDComm v2 [spec](https://identity.foundation/didcomm-messaging/spec)
2//!
3//! ![tests](https://github.com/decentralized-identity/didcomm-rs/workflows/tests/badge.svg)
4//!
5//! ## License
6//!
7//! [Apache-2.0](LICENSE.md)
8//!
9//! ## Examples of usage
10//!
11//! ### 1. Prepare raw message for send and receive
12//!
13//! #### GoTo: [full test][send_receive_raw]
14//!
15//! ```rust
16//! # use co_didcomm::Message;
17//! # const TEST_DID: &'static str = r###"{
18//! #     "@context": "https://www.w3.org/ns/did/v1",
19//! #     "id": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
20//! #     "verificationMethod": [
21//! #         {
22//! #             "id": "#DDMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU",
23//! #             "type": "Ed25519VerificationKey2018",
24//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
25//! #             "publicKeyBase64": "DMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU="
26//! #         },
27//! #         {
28//! #             "id": "#CpPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA",
29//! #             "type": "X25519KeyAgreementKey2019",
30//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
31//! #             "publicKeyBase64": "pPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA="
32//! #         }
33//! #     ]
34//! # }"###;
35//! // Message construction
36//! let m = Message::new()
37//!     // setting `from` header (sender) - Optional
38//!     .from("did:xyz:ulapcuhsatnpuhza930hpu34n_")
39//!     // setting `to` header (recipients) - Optional
40//!     .to(&[
41//!         "did::xyz:34r3cu403hnth03r49g03",
42//!         "did:xyz:30489jnutnjqhiu0uh540u8hunoe",
43//!     ])
44//!     // populating body with some data - `Vec<bytes>`
45//!     .body(TEST_DID).unwrap();
46//!
47//! // Serialize message into JWM json (SENDER action)
48//! let ready_to_send = m.clone().as_raw_json().unwrap();
49//!
50//! // ... transport is happening here ...
51//!
52//! // On receival deserialize from json into Message (RECEIVER action)
53//! // Error handling recommended here
54//!
55//! let received = Message::receive(&ready_to_send, None, None, None);
56//! ```
57//!
58//! ### 2. Prepare JWE message for direct send
59//!
60//! #### GoTo: [full test][send_receive_encrypted_xc20p_json_test]
61//!
62//! ```rust
63//! # use co_didcomm::{crypto::CryptoAlgorithm, Message};
64//! # use utilities::{get_keypair_set, KeyPairSet};
65//! # const TEST_DID: &'static str = r###"{
66//! #     "@context": "https://www.w3.org/ns/did/v1",
67//! #     "id": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
68//! #     "verificationMethod": [
69//! #         {
70//! #             "id": "#DDMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU",
71//! #             "type": "Ed25519VerificationKey2018",
72//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
73//! #             "publicKeyBase64": "DMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU="
74//! #         },
75//! #         {
76//! #             "id": "#CpPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA",
77//! #             "type": "X25519KeyAgreementKey2019",
78//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
79//! #             "publicKeyBase64": "pPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA="
80//! #         }
81//! #     ]
82//! # }"###;
83//! # let KeyPairSet { bobs_public, mediators_public: carol_public, .. } = get_keypair_set();
84//! // sender key as bytes
85//! let ek = [130, 110, 93, 113, 105, 127, 4, 210, 65, 234, 112, 90, 150, 120, 189, 252, 212, 165, 30, 209, 194, 213, 81, 38, 250, 187, 216, 14, 246, 250, 166, 92];
86//!
87//! // Message construction
88//! let message = Message::new()
89//!     .from("did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp")
90//!     .to(&[
91//!         "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
92//!         "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
93//!     ])
94//!     // packing in some payload (can be anything really)
95//!     .body(TEST_DID).unwrap()
96//!     // decide which [Algorithm](crypto::encryptor::CryptoAlgorithm) is used (based on key)
97//!     .as_jwe(
98//!         &CryptoAlgorithm::XC20P,
99//!         Some(bobs_public.to_vec()),
100//!     )
101//!     // add some custom app/protocol related headers to didcomm header portion
102//!     // these are not included into JOSE header
103//!     .add_header_field("my_custom_key".into(), "my_custom_value".into())
104//!     .add_header_field("another_key".into(), "another_value".into())
105//!     // set `kid` property
106//!     .kid(r#"#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW"#);
107//!
108//! // recipient public key is automatically resolved
109//! let ready_to_send = message.seal(
110//!     &ek,
111//!     Some(vec![Some(bobs_public.to_vec()), Some(carol_public.to_vec())]),
112//! ).unwrap();
113//!
114//! //... transport is happening here ...
115//! ```
116//!
117//! ### 3. Prepare JWS message -> send -> receive
118//!
119//! * Here `Message` is signed but not encrypted.
120//! * In such scenarios explicit use of `.sign(...)` and `Message::verify(...)` required.
121//!
122//! ```rust
123//! # use co_didcomm::{crypto::{Signer, SignatureAlgorithm}, Message};
124//! # use rand_core::OsRng;
125//! # const TEST_DID: &'static str = r###"{
126//! #     "@context": "https://www.w3.org/ns/did/v1",
127//! #     "id": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
128//! #     "verificationMethod": [
129//! #         {
130//! #             "id": "#DDMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU",
131//! #             "type": "Ed25519VerificationKey2018",
132//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
133//! #             "publicKeyBase64": "DMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU="
134//! #         },
135//! #         {
136//! #             "id": "#CpPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA",
137//! #             "type": "X25519KeyAgreementKey2019",
138//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
139//! #             "publicKeyBase64": "pPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA="
140//! #         }
141//! #     ]
142//! # }"###;
143//! # let sign_keypair = ed25519_dalek::SigningKey::generate(&mut OsRng);
144//! // Message construction an JWS wrapping
145//! let message = Message::new() // creating message
146//!     .from("did:xyz:ulapcuhsatnpuhza930hpu34n_") // setting from
147//!     .to(&["did::xyz:34r3cu403hnth03r49g03", "did:xyz:30489jnutnjqhiu0uh540u8hunoe"]) // setting to
148//!     .body(TEST_DID).unwrap() // packing in some payload
149//!     .as_jws(&SignatureAlgorithm::EdDsa)
150//!     .sign(SignatureAlgorithm::EdDsa.signer(), &sign_keypair.to_bytes()).unwrap();
151//!
152//! //... transport is happening here ...
153//!
154//! // Receiving JWS
155//! let received = Message::verify(&message.as_bytes(), &sign_keypair.verifying_key().to_bytes());
156//! ```
157//!
158//! ### 4. Prepare JWE message to be mediated -> mediate -> receive
159//!
160//! * Message should be encrypted by destination key first in `.routed_by()` method call using key for the recipient.
161//! * Next it should be encrypted by mediator key in `.seal()` method call - this can be done multiple times - once for each mediator in chain but should be strictly sequential to match mediators sequence in the chain.
162//! * Method call `.seal()` **MUST** be preceded by  `.as_jwe(CryptoAlgorithm)` as mediators may use different algorithms and key types than destination and this is not automatically predicted or populated.
163//! * Keys used for encryption should be used in reverse order - final destination - last mediator - second to last mediator - etc. Onion style.
164//!
165//! #### GoTo: [full test][send_receive_mediated_encrypted_xc20p_json_test]
166//!
167//! ```rust
168//! # use co_didcomm::{crypto::CryptoAlgorithm, Jwe, Mediated, Message};
169//! # use utilities::{get_keypair_set, KeyPairSet};
170//! # let KeyPairSet {
171//! #     alice_private,
172//! #     alice_public,
173//! #     bobs_private,
174//! #     bobs_public,
175//! #     mediators_private,
176//! #     mediators_public,
177//! # } = get_keypair_set();
178//! let mediated = Message::new()
179//!     // setting from
180//!     .from("did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp")
181//!     // setting to
182//!     .to(&["did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"])
183//!     // packing in some payload
184//!     .body(r#"{"foo":"bar"}"#).unwrap()
185//!     // set JOSE header for XC20P algorithm
186//!     .as_jwe(&CryptoAlgorithm::XC20P, Some(bobs_public.to_vec()))
187//!     // custom header
188//!     .add_header_field("my_custom_key".into(), "my_custom_value".into())
189//!     // another custom header
190//!     .add_header_field("another_key".into(), "another_value".into())
191//!     // set kid header
192//!     .kid(&"Ef1sFuyOozYm3CEY4iCdwqxiSyXZ5Br-eUDdQXk6jaQ")
193//!     // here we use destination key to bob and `to` header of mediator -
194//!     //**THIS MUST BE LAST IN THE CHAIN** - after this call you'll get new instance of envelope `Message` destined to the mediator.
195//!     .routed_by(
196//!         &alice_private,
197//!         Some(vec![Some(bobs_public.to_vec())]),
198//!         "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
199//!         Some(mediators_public.to_vec()),
200//!     );
201//! assert!(mediated.is_ok());
202//!
203//! //... transport to mediator is happening here ...
204//!
205//! // Received by mediator
206//! let mediator_received = Message::receive(
207//!     &mediated.unwrap(),
208//!     Some(&mediators_private),
209//!     Some(alice_public.to_vec()),
210//!     None,
211//! );
212//! assert!(mediator_received.is_ok());
213//!
214//! // Get inner JWE as string from message
215//! let mediator_received_unwrapped = mediator_received.unwrap().get_body().unwrap();
216//! let pl_string = String::from_utf8_lossy(mediator_received_unwrapped.as_ref());
217//! let message_to_forward: Mediated = serde_json::from_str(&pl_string).unwrap();
218//! let attached_jwe = serde_json::from_slice::<Jwe>(&message_to_forward.payload);
219//! assert!(attached_jwe.is_ok());
220//! let str_jwe = serde_json::to_string(&attached_jwe.unwrap());
221//! assert!(str_jwe.is_ok());
222//!
223//! //... transport to destination is happening here ...
224//!
225//! // Received by Bob
226//! let bob_received = Message::receive(
227//!     &String::from_utf8_lossy(&message_to_forward.payload),
228//!     Some(&bobs_private),
229//!     Some(alice_public.to_vec()),
230//!     None,
231//! );
232//! assert!(bob_received.is_ok());
233//! ```
234//!
235//! ### 5. Prepare JWS envelope wrapped into JWE -> sign -> pack -> receive
236//!
237//! * JWS header is set automatically based on signing algorithm type.
238//! * Message forming and encryption happens in same way as in other JWE examples.
239//! * ED25519-dalek signature is used in this example with keypair for signing and public key for verification.
240//!
241//! #### GoTo: [full test][send_receive_direct_signed_and_encrypted_xc20p_test]
242//!
243//! ```rust
244//! # use co_didcomm::{crypto::{CryptoAlgorithm, Signer, SignatureAlgorithm}, Message};
245//! # use rand_core::OsRng;
246//! # use utilities::{get_keypair_set, KeyPairSet};
247//! # const TEST_DID: &'static str = r###"{
248//! #     "@context": "https://www.w3.org/ns/did/v1",
249//! #     "id": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
250//! #     "verificationMethod": [
251//! #         {
252//! #             "id": "#DDMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU",
253//! #             "type": "Ed25519VerificationKey2018",
254//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
255//! #             "publicKeyBase64": "DMt6ythjG9pyuKcyFYHPC62gw6KjL8EdouCuNRm0GbU="
256//! #         },
257//! #         {
258//! #             "id": "#CpPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA",
259//! #             "type": "X25519KeyAgreementKey2019",
260//! #             "controller": "did:jun:ErJ3ahRB-zmOvTp0QmcJQfLq3hCAHBpXmoYLeHK8fHhw",
261//! #             "publicKeyBase64": "pPUgqlFBYtyXuCe6t8tXqeKo_wisONT9OzX-tRWNuGA="
262//! #         }
263//! #     ]
264//! # }"###;
265//! let KeyPairSet {
266//!     alice_public,
267//!     alice_private,
268//!     bobs_private,
269//!     bobs_public,
270//!     ..
271//! } = get_keypair_set();
272//! # let sign_keypair = ed25519_dalek::SigningKey::generate(&mut OsRng);
273//! // Message construction
274//! let message = Message::new() // creating message
275//!     .from("did:xyz:ulapcuhsatnpuhza930hpu34n_") // setting from
276//!     .to(&["did::xyz:34r3cu403hnth03r49g03"]) // setting to
277//!     .body(TEST_DID).unwrap() // packing in some payload
278//!     .as_jwe(&CryptoAlgorithm::XC20P, Some(bobs_public.to_vec())) // set JOSE header for XC20P algorithm
279//!     .add_header_field("my_custom_key".into(), "my_custom_value".into()) // custom header
280//!     .add_header_field("another_key".into(), "another_value".into()) // another custom header
281//!     .kid(r#"Ef1sFuyOozYm3CEY4iCdwqxiSyXZ5Br-eUDdQXk6jaQ"#); // set kid header
282//!
283//! // Send as signed and encrypted JWS wrapped into JWE
284//! let ready_to_send = message.seal_signed(
285//!     &alice_private,
286//!     Some(vec![Some(bobs_public.to_vec())]),
287//!     SignatureAlgorithm::EdDsa,
288//!     &sign_keypair.to_bytes(),
289//! ).unwrap();
290//!
291//! //... transport to destination is happening here ...
292//!
293//! //Receive - same method to receive for JWE or JWS wrapped into JWE but with pub verifying key
294//! let received = Message::receive(
295//!     &ready_to_send,
296//!     Some(&bobs_private),
297//!     Some(alice_public.to_vec()),
298//!     None,
299//! ); // and now we parse received
300//! ```
301//!
302//! ### 6. Multiple recipients static key wrap per recipient with shared secret
303//!
304//! * ! Works with `resolve` feature only - requires resolution of public keys for each recipient for shared secret generation.
305//! * Static key generated randomly in the background (`to` field has >1 recipient).
306//!
307//! #### GoTo: [full test][send_receive_didkey_test]
308//!
309#![cfg_attr(
310    feature = "resolve",
311    doc = r##"
312```rust
313# use co_didcomm::{crypto::{CryptoAlgorithm}, Message};
314# use utilities::{get_keypair_set, KeyPairSet};
315# let KeyPairSet {
316#     alice_private,
317#     bobs_private,
318#     mediators_private: carol_private,
319#     ..
320# } = get_keypair_set();
321// Creating message with multiple recipients.
322let m = Message::new()
323    .from("did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp")
324    .to(&[
325        "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
326        "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
327    ])
328    .as_jwe(&CryptoAlgorithm::XC20P, None);
329
330let jwe = m.seal(&alice_private, None);
331// Packing was ok?
332assert!(jwe.is_ok());
333
334let jwe = jwe.unwrap();
335
336// Each of the recipients receive it in same way as before (direct with single recipient)
337let received_first = Message::receive(&jwe, Some(&bobs_private), None, None);
338let received_second = Message::receive(&jwe, Some(&carol_private), None, None);
339
340// All good without any extra inputs
341assert!(received_first.is_ok());
342assert!(received_second.is_ok());
343```"##
344)]
345//! ## Pluggable cryptography
346//!
347//! In order to use your own implementation(s) of message crypto and/or signature algorithms implement these trait(s):
348//!
349//! [`co_didcomm::crypto::Cypher`][crypter]
350//!
351//! [`co_didcomm::crypto::Signer`][signer]
352//!
353//! Don't use `default` feature - might change in future.
354//!
355//! When implemented - use them instead of `CryptoAlgorithm` and `SignatureAlgorithm` from examples above.
356//!
357//! ## Strongly typed Message payload (body)
358//!
359//! ### GoTo: [full test][shape_desired_test]
360//!
361//! In most cases application implementation would prefer to have strongly typed body of the message instead of raw `Vec<u8>`.
362//! For this scenario `Shape` trait should be implemented for target type.
363//!
364//! * First, let's define our target type. JSON in this example.
365//!
366//! ```rust
367//! # use serde::{Deserialize, Serialize};
368//! #[derive(Serialize, Deserialize, PartialEq, Debug)]
369//! struct DesiredShape {
370//!     num_field: usize,
371//!     string_field: String,
372//! }
373//! ```
374//!
375//! * Next, implement `Shape` trait for it
376//!
377//! ```rust
378//! # use co_didcomm::{Error, Message, Shape};
379//! # use serde::{Deserialize, Serialize};
380//! # #[derive(Serialize, Deserialize, PartialEq, Debug)]
381//! # struct DesiredShape {
382//! #     num_field: usize,
383//! #     string_field: String,
384//! # }
385//! impl Shape for DesiredShape {
386//!     type Err = Error;
387//!     fn shape(m: &Message) -> Result<DesiredShape, Error> {
388//!         serde_json::from_str(&m.get_body().unwrap())
389//!             .map_err(|e| Error::SerdeError(e))
390//!     }
391//! }
392//! ```
393//!
394//! * Now we can call `shape()` on our `Message` and shape in in.
395//! * In this example we expect JSON payload and use it's Deserializer to get our data, but your implementation can work with any serialization.
396//!
397//! ```rust
398//! # use co_didcomm::{Error, Message, Shape};
399//! # use serde::{Deserialize, Serialize};
400//! # #[derive(Serialize, Deserialize, PartialEq, Debug)]
401//! # struct DesiredShape {
402//! #     num_field: usize,
403//! #     string_field: String,
404//! # }
405//! # impl Shape for DesiredShape {
406//! #     type Err = Error;
407//! #     fn shape(m: &Message) -> Result<DesiredShape, Error> {
408//! #         serde_json::from_str(&m.get_body().unwrap())
409//! #             .map_err(|e| Error::SerdeError(e))
410//! #     }
411//! # }
412//! let body = r#"{"num_field":123,"string_field":"foobar"}"#.to_string();
413//! let message = Message::new() // creating message
414//!     .from("did:xyz:ulapcuhsatnpuhza930hpu34n_") // setting from
415//!     .to(&["did::xyz:34r3cu403hnth03r49g03"]) // setting to
416//!     .body(&body).unwrap(); // packing in some payload
417//! let received_typed_body = DesiredShape::shape(&message).unwrap(); // Where m = Message
418//! ```
419//!
420//! ## Disclaimer
421//!
422//! This is a sample implementation of the DIDComm V2 spec. The DIDComm V2 spec is still actively being developed by the DIDComm WG in the DIF and therefore subject to change.
423//!
424//! <!-- Collect links to line numbers here to be able to maintain them easier -->
425//! [crypter]: https://github.com/evannetwork/didcomm-rs/blob/master/src/crypto/mod.rs#L30
426//! [send_receive_raw]: https://github.com/evannetwork/didcomm-rs/blob/master/tests/send_receive.rs#L16
427//! [send_receive_encrypted_xc20p_json_test]: https://github.com/evannetwork/didcomm-rs/blob/master/tests/send_receive.rs#L42
428//! [send_receive_mediated_encrypted_xc20p_json_test]: https://github.com/evannetwork/didcomm-rs/blob/master/tests/send_receive.rs#L84
429//! [send_receive_direct_signed_and_encrypted_xc20p_test]: https://github.com/evannetwork/didcomm-rs/blob/master/tests/send_receive.rs#L164
430//! [send_receive_didkey_test]: https://github.com/evannetwork/didcomm-rs/blob/master/src/messages/message.rs#L482
431//! [shape_desired_test]: https://github.com/evannetwork/didcomm-rs/blob/main/tests/shape.rs#L21
432//! [signer]: https://github.com/evannetwork/didcomm-rs/blob/master/src/crypto/mod.rs#L39
433#[cfg(feature = "env_logger")]
434extern crate env_logger;
435#[cfg_attr(feature = "raw-crypto", macro_use)]
436extern crate log;
437
438#[macro_use]
439extern crate serde;
440extern crate base64_url;
441#[cfg(feature = "raw-crypto")]
442pub mod crypto;
443mod error;
444mod messages;
445mod result;
446
447pub use error::*;
448pub use messages::*;
449pub use result::Result;