didcomm 1.0.0-dev.1 copy "didcomm: ^1.0.0-dev.1" to clipboard
didcomm: ^1.0.0-dev.1 copied to clipboard

A Dart package for implementing secure and private communication on your app using DIDComm v2 Messaging protocol.

Affinidi DIDComm for Dart #

A Dart package for implementing secure and private communication on your app using DIDComm v2 Messaging protocol. DIDComm v2 Messaging is a decentralised communication protocol that uses a Decentralised Identifier (DID) to establish a secure communication channel and send a private and verifiable message.

The DIDComm for Dart package provides the tools and libraries to enable your app to send DIDComm messages. It supports various encryption algorithms and DID methods, such as did:peer, did:key, and did:web for signing and encrypting to ensure the secure and private transport of messages to the intended recipient, establishing verifiable and trusted communication.

Table of Contents #

Core Concepts #

The DIDComm for Dart package utilises existing open standards and cryptographic techniques to provide secure, private, and verifiable communication.

  • Decentralised Identifier (DID) - A globally unique identifier that enables secure interactions. The DID is the cornerstone of Self-Sovereign Identity (SSI), a concept that aims to put individuals or entities in control of their digital identities.

  • DID Document - A DID is a URI (Uniform Resource Identifier) that resolves into a DID Document that contains information such as cryptographic public keys, authentication methods, and service endpoints. It allows others to verify signatures, authenticate interactions, and validate messages cryptographically.

  • DIDComm Message - is a JSON Web Message (JWM), a lightweight, secure, and standardised format for structured communication using JSON. It represents headers, message types, routing metadata, and payloads designed to enable secure and interoperable communication across different systems.

  • Mediator - A service that handles and routes messages sent between participants (e.g., users, organisations, another mediator, or even AI agents).

  • DID Manager - Establishes the relationship between DID methods and key pairs from the Wallet, supporting different algorithms for signing and verifying messages.

Key Features #

  • Implements the DIDComm Message v2.0 protocol.

  • Support for multiple DID methods like did:peer and did:web to prove control of user's digital identity.

  • Support for digital wallets under Affinidi Dart SSI to manage cryptographic keys.

  • Support key types like P256, ED25519 and SECP256K1 to encrypt and sign messages.

  • Support for DIDComm Messaging Envelope types.

  • Connect and authenticate with different mediator services that follow the DIDComm Message v2.0 protocol.

DIDComm Message Envelopes #

DIDComm v2 messages can be sent in the following formats: plaintext, signed, and encrypted. Each format, called "envelope", provides different security and privacy guarantees and can be combined in various ways.

  • Plaintext message: A message that is neither signed nor encrypted. It is readable by anyone and provides no integrity or authenticity guarantees. Used for non-sensitive data, debugging, or as the inner content of other envelopes.

  • Signed message: A message that is digitally signed but not encrypted. Anyone can read it, but the recipient can prove who signed it (non-repudiation)—used when the message's origin must be provable to the recipient or third parties.

  • Encrypted message: An encrypted message for one or more recipients. Only the intended recipients can read the content of the message. Encryption can be:

    • Authenticated encryption (authcrypt, ECDH-1PU): Proves the sender's identity to the recipient (but not to intermediaries). Used when both confidentiality and sender authenticity are required.

      It uses the ECDH-1PU for authenticated encryption (authcrypt), where the sender's key is involved in the encryption process, allowing the recipient to verify the sender's identity.

    • Anonymous encryption (anoncrypt, ECDH-ES): Hides the sender's identity from the recipient and intermediaries. It is used when the sender's anonymity is required.

      It uses ECDH-ES for anonymous encryption (anoncrypt), where only the recipient's key is used, and the sender remains anonymous.

Combining Different Envelope Types #

You can combine the DIDComm Message Envelope types in the following ways:

Envelope Type Purpose Use case
plaintext Used as the building block of higher-level protocols, but rarely transmitted directly, since it lacks security guarantees. Public announcements, non-confidential data, debugging, or as the inner content of other envelopes.
signed(plaintext) Adds non-repudiation to a plaintext message; whoever receives a message wrapped in this way can prove its origin to any external party. Audit trails, legal or regulatory messages, or when recipients need to prove message origin to third parties.
anoncrypt(plaintext) Guarantees confidentiality and integrity without revealing the identity of the sender. Anonymous tips, whistleblowing, or when sender's identity must be hidden.
authcrypt(plaintext) It guarantees confidentiality and integrity. It also proves the sender's identity—but in a way that only the recipient can verify. This is the default wrapping choice that should be used unless a different goal is clearly identified. Most secure communications where both privacy and sender authenticity are required.
anoncrypt(sign(plaintext)) Guarantees confidentiality, integrity, and non-repudiation – but prevents an observer of the outer envelope from accessing the signature. Relative to authcrypt(plaintext), this increases guarantees to the recipient since non-repudiation is stronger than simple authentication. However, it also forces the sender to talk “on the record” and is thus not assumed to be desirable by default. Sensitive communications where sender wants to prove authorship to the recipient but remain anonymous to intermediaries.
authcrypt(sign(plaintext)) It adds no useful guarantees over the previous choice and is slightly more expensive. This wrapping combination should not be emitted by conforming implementations. However, implementations may accept it. If they choose to do so, they must emit an error if the signer of the plaintext is different from the sender identified by the authcrypt layer. Rarely used; only for compatibility or special cases.
anoncrypt(authcrypt(plaintext)) A specialized combination that hides the sender key ID (skid) header in the authcrypt envelope, so the hop immediately sourceward of a mediator cannot discover an identifier for the sender. Advanced scenarios requiring layered security and sender anonymity from intermediaries.

Security Features of Envelope Type Combinations #

Refer to the table below how security features are affected by combining each envelope type.

Envelope Type Confidentiality Sender Authenticity Non-repudiation Sender Anonymity
plaintext (no envelope)
signed(plaintext)
anoncrypt(plaintext)
authcrypt(plaintext)
anoncrypt(sign(plaintext))
authcrypt(sign(plaintext))
anoncrypt(authcrypt(plaintext))

In Summary

  • Use plaintext for non-sensitive data.
  • Use signed(plaintext) for integrity and non-repudiation.
  • Use anoncrypt(plaintext) for confidential, sender-anonymous messages.
  • Use authcrypt(plaintext) for confidential, authenticated messages.
  • Use anoncrypt(sign(plaintext)) for confidential, non-repudiable, sender-anonymous messages.
  • Use authcrypt(sign(plaintext)) for the highest level of security and auditability (rarely needed).
  • Use anoncrypt(authcrypt(plaintext)) for advanced layered security and sender anonymity from intermediaries.

For more details, see the DIDComm Messaging specification.

Requirements #

  • Dart SDK version ^3.6.0

Installation #

Run:

dart pub add didcomm

or manually, add the package into your pubspec.yaml file:

dependencies:
  didcomm: ^<version_number>

and then run the command below to install the package:

dart pub get

Visit the pub.dev install page of the Dart package for more information.

Usage #

Below is a step-by-step example of secure communication between Alice and Bob using the DIDComm Dart package. The example demonstrates how to construct, sign, encrypt, and unpack messages according to the DIDComm Messaging spec.

1. Set up DID Manager and DIDs #

final aliceKeyStore = InMemoryKeyStore();
final aliceWallet = PersistentWallet(aliceKeyStore);
final aliceDidManager = DidKeyManager(
  wallet: aliceWallet,
  store: InMemoryDidStore(),
);

final bobKeyStore = InMemoryKeyStore();
final bobWallet = PersistentWallet(bobKeyStore);
final bobDidManager = DidKeyManager(
  wallet: bobWallet,
  store: InMemoryDidStore(),
);

final aliceKeyId = 'alice-key-1';
await aliceWallet.generateKey(
  keyId: aliceKeyId,
  keyType: KeyType.p256,
);

await aliceDidManager.addVerificationMethod(aliceKeyId);
final aliceDidDocument = await aliceDidManager.getDidDocument();

final bobKeyId = 'bob-key-1';
await bobWallet.generateKey(
  keyId: bobKeyId,
  keyType: KeyType.p256,
);

await bobDidManager.addVerificationMethod(bobKeyId);
final bobDidDocument = await bobDidManager.getDidDocument();

2. Compose a Plain Text Message #

A plain text message is a simple JSON message with headers and a body.

final plainTextMessage = PlainTextMessage(
  id: '041b47d4-9c8f-4a24-ae85-b60ec91b025c', // Unique message ID
  from: aliceDidDocument.id,                   // Sender's DID
  to: [bobDidDocument.id],                     // Recipient's DID(s)
  type: Uri.parse('https://didcomm.org/example/1.0/message'), // Message type URI
  body: {'content': 'Hello, Bob!'},            // Message payload
);

plainTextMessage['custom-header'] = 'custom-value'; // Add custom headers if needed

3. Sign the Message #

Signing a message is optional in DIDComm. It is required when you need to provide non-repudiation—proof that the sender cannot deny authorship of the message. Signing a message is essential for scenarios where:

  • The recipient must prove to a third party that the sender authored the message (e.g., legal, regulatory, or audit requirements).
  • The message may be forwarded or relayed, and recipients must verify its origin independently of the transport channel.
  • You want to ensure message integrity and origin even if the message is not encrypted.
final aliceSigner = await aliceDidManager.getSigner(
  aliceDidDocument.assertionMethod.first.id,
);

final signedMessage = await SignedMessage.pack(
  plainTextMessage,
  signer: aliceSigner, // The signer instance
);

4. Encrypt the Message #

Although optional, encrypting DIDComm messages is highly recommended to protect their confidentiality. DIDComm supports two main types of encryption:

  • Authenticated Encryption (authcrypt, ECDH-1PU):
    • Proves the sender's identity to the recipient (but not to intermediaries).
    • Used when both confidentiality and sender authenticity are required.
    • Only the intended recipient can read the message and verify that the sender's key encrypts the message.
    • Protects the sender's identity from intermediaries and eavesdroppers.

Choose authcrypt when you want the recipient to know who sent the message (authenticated, private communication).

  • Anonymous Encryption (anoncrypt, ECDH-ES):
    • Hides the sender's identity from both the recipient and intermediaries.
    • Used when sender anonymity is required.
    • Only the intended recipient can read the message but cannot determine who sent it.

Choose anoncrypt when you want to keep the sender's identity hidden (anonymous tips, whistleblowing, or privacy-preserving scenarios).

Example: Authenticated Encryption (authcrypt)

final aliceMatchedKeyIds = aliceDidDocument.matchKeysInKeyAgreement(
  otherDidDocuments: [bobDidDocument],
);

final encryptedMessage = await EncryptedMessage.packWithAuthentication(
  message, // The signed or plain text message to encrypt
  keyPair: await aliceDidManager.getKeyPairByDidKeyId(aliceMatchedKeyIds.first),
  didKeyId: aliceMatchedKeyIds.first,
  recipientDidDocuments: [bobDidDocument],
  encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
);

  • keyPair: Alice's key pair used for authenticated encryption.
  • didKeyId: The key ID from Alice's DID Document for key agreement.
  • recipientDidDocuments: The recipient's DID Document(s).
  • encryptionAlgorithm: The encryption algorithm to use (e.g., a256cbc).

Example: Anonymous Encryption (anoncrypt)

If you want to encrypt a message without revealing the sender's identity, use packAnonymously:

final anonymousEncryptedMessage = await EncryptedMessage.packAnonymously(
  message, // The signed or plain text message to encrypt
  keyType: KeyType.p256, // Key type for recipient's key agreement (required)
  recipientDidDocuments: [bobDidDocument], // List of recipient DID Documents
  encryptionAlgorithm: EncryptionAlgorithm.a256cbc, // Encryption algorithm
);
  • message: The message to encrypt (can be plain or signed).
  • recipientDidDocuments: The recipient's DID Document(s).
  • encryptionAlgorithm: The encryption algorithm to use.
  • keyType: The key type for the recipient's key agreement key (e.g., KeyType.p256, KeyType.ed25519).

In this case, Bob can decrypt and read the message but cannot determine who sent it. This approach is helpful for scenarios where sender anonymity is required.

More details about the key type selection for authcrypt and anoncrypt.

Pack and Unpack DIDComm Message Helpers #

The DidcommMessage class provides high-level helper methods for common packing and unpacking workflows. These helpers simplify signing and encrypting messages according to your security and privacy requirements.

packIntoEncryptedMessage #

Packs a plain text message into an encrypted message. Use this for confidential messages (authcrypt or anoncrypt, depending on parameters).

// Authenticated encryption (authcrypt)
final aliceMatchedKeyIds = aliceDidDocument.matchKeysInKeyAgreement(
  otherDidDocuments: [bobDidDocument],
);

final encrypted = await DidcommMessage.packIntoEncryptedMessage(
  plainTextMessage,
  keyPair: await aliceDidManager.getKeyPairByDidKeyId(aliceMatchedKeyIds.first),
  didKeyId: aliceMatchedKeyIds.first,
  recipientDidDocuments: [bobDidDocument],
  keyWrappingAlgorithm: KeyWrappingAlgorithm.ecdh1Pu,
  encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
);

// Anonymous encryption (anoncrypt)
final anonEncrypted = await DidcommMessage.packIntoEncryptedMessage(
  plainTextMessage,
  keyType: KeyType.p256,
  recipientDidDocuments: [bobDidDocument],
  keyWrappingAlgorithm: KeyWrappingAlgorithm.ecdh1Es,
  encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
);

packIntoSignedMessage #

Packs a plain text message into a signed message. Use this for non-repudiation and message integrity.

final signed = await DidcommMessage.packIntoSignedMessage(
  plainTextMessage,
  signer: aliceSigner,
);

packIntoSignedAndEncryptedMessages #

Packs a plain text message into a signed message and then encrypts it. Use this for both non-repudiation and confidentiality in a single step. Encryption can be anonymous (anoncrypt) or authenticated (authcrypt), depending on the provided parameters. anoncrypt(singed(plaintext)) should be preferred over authcrypt(singed(plaintext)) (see here)

// Anonymous encryption - anoncrypt(singed(plaintext))
// this is a preferred way
final signedAndAnonEncrypted = await DidcommMessage.packIntoSignedAndEncryptedMessages(
  plainTextMessage,
  keyType: KeyType.p256,
  recipientDidDocuments: [bobDidDocument],
  keyWrappingAlgorithm: KeyWrappingAlgorithm.ecdh1Es,
  encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
  signer: aliceSigner,
);

// Authenticated encryption with signed message - authcrypt(signed(plaintext)))
final signedAndEncrypted = await DidcommMessage.packIntoSignedAndEncryptedMessages(
  plainTextMessage,
  keyPair: aliceKeyPair,
  didKeyId: aliceDidDocument.keyAgreement[0].id,
  recipientDidDocuments: [bobDidDocument],
  keyWrappingAlgorithm: KeyWrappingAlgorithm.ecdh1Pu,
  encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
  signer: aliceSigner,
);

unpackToPlainTextMessage #

Bob receives the encrypted message and unpacks it:

final unpackedMessage = await DidcommMessage.unpackToPlainTextMessage(
  message: jsonDecode(sentMessageByAlice) as Map<String, dynamic>, // The received message
  recipientDidManager: bobDidManager, // Bob's DID manager for decryption
  expectedMessageWrappingTypes: [MessageWrappingType.authcryptSignPlaintext], // Expected wrapping
  expectedSigners: [aliceSigner.didKeyId], // List of expected signers' key IDs
);
  • message: The received message as a decoded JSON map.

  • recipientDidManager: The DID manager instance used to decrypt the message.

  • expectedMessageWrappingTypes: List of expected message wrapping types. This argument ensures the unpacked message matches the expected security and privacy guarantees.

    The values are from the MessageWrappingType enum, which maps to the DIDComm IANA media types, such as authcryptPlaintext for authenticated encryption, signedPlaintext for signed messages, and plaintext for unprotected messages. It helps prevent downgrade attacks and ensures the message is processed as intended.

    Note: If expectedMessageWrappingTypes is omitted or set to null, the default expected wrapping type is [MessageWrappingType.authcryptPlaintext] according to the DIDComm specification. This means only authenticated encrypted messages will be accepted by default unless you explicitly specify otherwise.

  • expectedSigners: List of key IDs whose signatures are expected and will be verified.

More Usage Examples #

See example/didcomm_example.dart for a complete runnable example.

For more sample usage, including a mediator workflow, see the example folder.

Key Type Selection for Authcrypt and Anoncrypt #

When encrypting messages, you must select a key type for a key agreement that all parties support.

  • Authcrypt (authenticated encryption, ECDH-1PU):

    • For key agreement, both the sender and all recipients must use compatible key types (e.g., both must have P-256 or X25519 keys in their DID Documents).
    • You typically use the sender's DidManager to find a compatible key pair and key agreement method with each recipient.
    • Use the matchKeysInKeyAgreement extension method to find compatible key IDs from the sender's wallet for all recipients.
  • Anoncrypt (anonymous encryption, ECDH-ES):

    • Only uses the recipients' key agreement keys; does not involve the sender's key pair.
    • You must select a key type that all recipients for key agreement support.
    • Use the getCommonKeyTypesInKeyAgreements extension method on the list of recipient DID Documents to determine the set of key types common to all recipients.

Examples:

Authcrypt:

final compatibleKeyIds = aliceDidDocument.matchKeysInKeyAgreement(
  otherDidDocuments: [
    bobDidDocument
    // and other recipients
  ],
);

if (compatibleKeyIds.isEmpty) {
  throw Exception('No compatible key agreement method found between Alice and Bob');
}

final aliceDidKeyId = compatibleKeyIds.first; // Use this key ID for Alice

Anoncrypt:

final commonKeyTypes = [
  bobDidDocument,
  // and other recipients
].getCommonKeyTypesInKeyAgreements();

if (commonKeyTypes.isEmpty) {
  throw Exception('No common key type found for anoncrypt between recipients');
}

final keyType = commonKeyTypes.first; // Use this key type for anoncrypt

This ensures that the correct and compatible keys are used for ECDH-1PU (authcrypt) and ECDH-ES (anoncrypt) operations, and that all recipients can decrypt the message using a supported key agreement method.

Ed25519/X25519 Key Derivation #

If you select an Ed25519 key (Edwards curve) for your DID or wallet for key agreement purposes, the Dart SSI package automatically derives an X25519 key from the Ed25519 key.

Read more about Ed25519/X25519 Key Derivation from the Dart SSI project.

Support & feedback #

If you face any issues or have suggestions, please don't hesitate to contact us using this link.

Reporting technical issues #

If you have a technical issue with the DIDComm's codebase, you can also create an issue directly in GitHub.

  1. Ensure the bug was not already reported by searching on GitHub under Issues.

  2. If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behaviour that is not occurring.

Contributing #

Want to contribute?

Head over to our CONTRIBUTING guidelines.

8
likes
0
points
2.28k
downloads

Publisher

verified publisheraffinidi.com

Weekly Downloads

A Dart package for implementing secure and private communication on your app using DIDComm v2 Messaging protocol.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

analyzer, build, build_runner, collection, convert, crypto, crypto_keys_plus, dart_flutter_team_lints, dio, elliptic, json_annotation, meta, pointycastle, source_gen, ssi, uuid, web_socket_channel, x25519

More

Packages that depend on didcomm