The repository consists of the following main components:
- Basic DIDComm v2 support in Rust.
- Wasm - based DIDComm JavaScript/TypeScript, see wasm.
- uniffi-rs - based wrappers
- uniffi - callback-based Rust wrapper with uniffi-rs support
- wrappers/swift - Swift wrapper generated via uniffi-rs
The docs below are provided for the main DIDComm Rust.
See wasm/README.md for DIDComm JavaScript/TypeScript docs.
See wrappers/swift/README.md for DIDComm Swift docs.
To use didcomm
, add this to your Cargo.toml
:
[dependencies]
didcomm = "0.4"
Use cargo run --example {example-name}
for example cargo run --example basic
.
- Rust 2018 edition is required.
- In order to use the library,
SecretsResolver
andDIDResolver
traits must be implemented on the application level. Implementation of that traits is out of DIDComm library scope, but we provide 2 simple implementationExampleDIDResolver
andExampleSecretsResolver
that allows resolve locally known DID docs and secrets for tests/demo purposes.- Verification materials are expected in JWK, Base58 and Multibase (internally Base58 only) formats.
- In Base58 and Multibase formats, keys using only X25519 and Ed25519 curves are supported.
- For private keys in Base58 and Multibase formats, the verification material value contains both private and public parts (concatenated bytes).
- In Multibase format, bytes of the verification material value is prefixed with the corresponding Multicodec code.
- Key IDs (kids) used in
SecretsResolver
must match the corresponding key IDs from DID Doc verification methods. - Key IDs (kids) in DID Doc verification methods and secrets must be a full DID Fragment, that is
did#key-id
. - Verification methods referencing another DID Document are not supported (see Referring to Verification Methods).
- Verification materials are expected in JWK, Base58 and Multibase (internally Base58 only) formats.
- The following curves and algorithms are supported:
- Encryption:
- Curves: X25519, P-256
- Content encryption algorithms:
- XC20P (to be used with ECDH-ES only, default for anoncrypt),
- A256GCM (to be used with ECDH-ES only),
- A256CBC-HS512 (default for authcrypt)
- Key wrapping algorithms: ECDH-ES+A256KW, ECDH-1PU+A256KW
- Signing:
- Curves: Ed25519, Secp256k1, P-256
- Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K
- Encryption:
- Forward protocol is implemented and used by default.
- DID rotation (
fromPrior
field) is supported. - DIDComm has been implemented under the following Assumptions
See examples for details.
A general usage of the API is the following:
- Sender Side:
- Build a
Message
(plaintext, payload). - Convert a message to a DIDComm Message for further transporting by calling one of the following:
Message::pack_encrypted
to build an Encrypted DIDComm messageMessage::pack_signed
to build a Signed DIDComm messageMessage::pack_plaintext
to build a Plaintext DIDComm message
- Build a
- Receiver side:
- Call
Message::unpack
on receiver side that will decrypt the message, verify signature if needed and return aMessage
for further processing on the application level.
- Call
This is the most common DIDComm message to be used in most of the applications.
A DIDComm encrypted message is an encrypted JWM (JSON Web Messages) that
- hides its content from all but authorized recipients
- (optionally) discloses and proves the sender to only those recipients
- provides message integrity guarantees
It is important in privacy-preserving routing. It is what normally moves over network transports in DIDComm applications, and is the safest format for storing DIDComm data at rest.
See Message::pack_encrypted
documentation for more details.
Authentication encryption example (most common case):
// --- Build message from ALICE to BOB ---
let msg = Message::build(
"example-1".to_owned(),
"example/v1".to_owned(),
json!("example-body"),
)
.to(ALICE_DID.to_owned())
.from(BOB_DID.to_owned())
.finalize();
// --- Pack encrypted and authenticated message ---
let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]);
let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone());
let (msg, metadata) = msg
.pack_encrypted(
BOB_DID,
Some(ALICE_DID),
None,
&did_resolver,
&secrets_resolver,
&PackEncryptedOptions::default(),
)
.await
.expect("Unable pack_encrypted");
println!("Encryption metadata is\n{:?}\n", metadata);
// --- Send message ---
println!("Sending message \n{}\n", msg);
// --- Unpacking message ---
let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]);
let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone());
let (msg, metadata) = Message::unpack(
&msg,
&did_resolver,
&secrets_resolver,
&UnpackOptions::default(),
)
.await
.expect("Unable unpack");
println!("Receved message is \n{:?}\n", msg);
println!("Receved message unpack metadata is \n{:?}\n", metadata);
Anonymous encryption example:
let (msg, metadata) = msg
.pack_encrypted(
BOB_DID,
None, // Keep sender as None here
None,
&did_resolver,
&secrets_resolver,
&PackEncryptedOptions::default(),
)
.await
.expect("Unable pack_encrypted");
Encryption with non-repudiation example:
let (msg, metadata) = msg
.pack_encrypted(
BOB_DID,
Some(ALICE_DID),
Some(ALICE_DID), // Provide information about signer here
&did_resolver,
&secrets_resolver,
&PackEncryptedOptions::default(),
)
.await
.expect("Unable pack_encrypted");
Signed messages are only necessary when
- the origin of plaintext must be provable to third parties
- or the sender can’t be proven to the recipient by authenticated encryption because the recipient is not known in advance (e.g., in a broadcast scenario).
Adding a signature when one is not needed can degrade rather than enhance security because it relinquishes the sender’s ability to speak off the record.
See Message::pack_signed
documentation for more details.
// ALICE
let msg = Message::build(
"example-1".to_owned(),
"example/v1".to_owned(),
json!("example-body"),
)
.to(ALICE_DID.to_owned())
.from(BOB_DID.to_owned())
.finalize();
let (msg, metadata) = msg
.pack_signed(ALICE_DID, &did_resolver, &secrets_resolver)
.await
.expect("Unable pack_signed");
// BOB
let (msg, metadata) = Message::unpack(
&msg,
&did_resolver,
&secrets_resolver,
&UnpackOptions::default(),
)
.await
.expect("Unable unpack");
A DIDComm message in its plaintext form that
- is not packaged into any protective envelope
- lacks confidentiality and integrity guarantees
- repudiable
They are therefore not normally transported across security boundaries.
// ALICE
let msg = Message::build(
"example-1".to_owned(),
"example/v1".to_owned(),
json!("example-body"),
)
.to(ALICE_DID.to_owned())
.from(BOB_DID.to_owned())
.finalize();
let msg = msg
.pack_plaintext(&did_resolver)
.expect("Unable pack_plaintext");
// BOB
let (msg, metadata) = Message::unpack(
&msg,
&did_resolver,
&secrets_resolver,
&UnpackOptions::default(),
)
.await
.expect("Unable unpack");
PRs are welcome!
The following CI checks are run against every PR:
- No warnings from
cargo check --all-targets
- All tests must pass with
cargo tests
- Code must be formatted by
cargo fmt --all