Integrate DIDComm for Dart
The DIDComm for Dart integration guide provides a step-by-step overview for developers to integrate DIDComm-based communication to enable their application to send DIDComm messages. The DIDComm for Dart package enables secure, privacy-preserving, and verifiable messaging between decentralised identifiers (DIDs), allowing users to exchange messages securely and privately without relying on centralised intermediaries.
Key Features
Implements the DIDComm v2.1 protocol.
Support for DIDComm Messaging Envelope types.
Support for digital wallets under Affinidi Dart SSI to manage cryptographic keys.
Connect and authenticate with different mediator services that follow the DIDComm v2.1 protocol.
Requirements
- Dart SDK version 3.6.0
Required Packages
The Dart package below is required to get started with the integration:
| Package | Description |
|---|---|
| DIDComm for Dart | A Dart package for implementing secure and private communication on your app using DIDComm v2.1 protocol. |
Sample Usage
The examples demonstrate how to construct, sign, encrypt, and unpack messages according to the DIDComm Messaging spec.
Set up DID Manager and DIDs
// Create DID manager for Alice
final aliceKeyStore = InMemoryKeyStore();
final aliceWallet = PersistentWallet(aliceKeyStore);
final aliceDidManager = DidKeyManager(
wallet: aliceWallet,
store: InMemoryDidStore(),
);
// Create DID manager for Bob
final bobKeyStore = InMemoryKeyStore();
final bobWallet = PersistentWallet(bobKeyStore);
final bobDidManager = DidKeyManager(
wallet: bobWallet,
store: InMemoryDidStore(),
);
// Generate key for Alice
final aliceKeyId = 'alice-key-1';
await aliceWallet.generateKey(
keyId: aliceKeyId,
keyType: KeyType.p256,
);
// Adds Alice's verification method
await aliceDidManager.addVerificationMethod(aliceKeyId);
final aliceDidDocument = await aliceDidManager.getDidDocument();
// Generate key for Bob
final bobKeyId = 'bob-key-1';
await bobWallet.generateKey(
keyId: bobKeyId,
keyType: KeyType.p256,
);
// Adds Bob's verification method
await bobDidManager.addVerificationMethod(bobKeyId);
final bobDidDocument = await bobDidManager.getDidDocument();Compose a Plain Text Message
A plain text message is a simple JSON message with headers and a body.
final plainTextMessage = PlainTextMessage(
// Unique message ID
id: '041b47d4-9c8f-4a24-ae85-b60ec91b025c',
// Sender's DID
from: aliceDidDocument.id,
// Recipient's DID(s)
to: [bobDidDocument.id],
// Message type URI
type: Uri.parse('https://didcomm.org/example/1.0/message'),
// Message payload
body: {'content': 'Hello, Bob!'},
);
// Add custom headers if needed
plainTextMessage['custom-header'] = 'custom-value';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,
);Encrypt the Message
Encryption of the DIDComm messages is optional, but it is highly recommended to protect their confidentiality. Learn more on DIDComm Messages formats and it’s encryption here .
Authenticated Encryption (authcrypt)
final aliceMatchedKeyIds = aliceDidDocument.matchKeysInKeyAgreement(
otherDidDocuments: [bobDidDocument],
);
final encryptedMessage = await EncryptedMessage.packWithAuthentication(
// The signed or plain text message to encrypt
message,
keyPair: await aliceDidManager.getKeyPairByDidKeyId(aliceMatchedKeyIds.first),
didKeyId: aliceMatchedKeyIds.first,
recipientDidDocuments: [bobDidDocument],
);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).
Anonymous Encryption (anoncrypt)
If you want to encrypt a message without revealing the sender’s identity, use packAnonymously:
final anonymousEncryptedMessage = await EncryptedMessage.packAnonymously(
// The signed or plain text message to encrypt
message,
// Key type for recipient's key agreement (required)
keyType: KeyType.p256,
// List of recipient DID Documents
recipientDidDocuments: [bobDidDocument],
// Encryption algorithm
encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
);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.
Pack DIDComm Message
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(sign(plaintext)) should be preferred over authcrypt(sign(plaintext)). Find more details here.
// Anonymous encryption - anoncrypt(sign(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(sign(plaintext)))
final signedAndEncrypted = await DidcommMessage.packIntoSignedAndEncryptedMessages(
plainTextMessage,
keyPair: aliceKeyPair,
didKeyId: aliceDidDocument.keyAgreement[0].id,
recipientDidDocuments: [bobDidDocument],
keyWrappingAlgorithm: KeyWrappingAlgorithm.ecdh1Pu,
encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
signer: aliceSigner,
);packIntoAnoncryptAndAuthcryptMessages
Packs a plain text message into two envelopes using authenticated (authcrypt) and anonymous (anoncrypt) encryption layers. This method first wraps the message with authenticated encryption (authcrypt, ECDH-1PU) and then the resulting message is wrapped with anonymous encryption (anoncrypt, ECDH-ES).
Use this when you want to provide sender authenticity (authcrypt) and sender anonymity from intermediaries (anoncrypt). Only the final recipient can verify the sender’s identity. The result is a message with the envelope type anoncrypt(authcrypt(plaintext)).
final doublyEncrypted = await DidcommMessage.packIntoAnoncryptAndAuthcryptMessages(
plainTextMessage,
keyPair: aliceKeyPair,
didKeyId: aliceDidDocument.keyAgreement[0].id,
recipientDidDocuments: [bobDidDocument],
// Algorithm for content encryption of the outer anoncrypt layer
encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
);Add Sender DID to ACL (Access Control List)
To be able to send a message, sender DID should be added to the recipient Access Control List.
// Bob adds Alice's DID to the ACL
final bobAccessListAddMessage = AccessListAddMessage(
id: const Uuid().v4(),
from: bobDidDocument.id,
to: [bobMediatorDocument.id],
theirDids: [aliceDidDocument.id],
expiresTime: expiresTime,
);
final bobAccessListAddSentMessage =
await bobMediatorClient.sendAclManagementMessage(
bobAccessListAddMessage,
);Send a Message
Alice is going to use Bob’s Mediator to send him a message.
// Alice initiates mediator client
final aliceMediatorClient = await MediatorClient.init(
mediatorDidDocument: bobMediatorDocument,
didManager: aliceDidManager,
authorizationProvider: await AffinidiAuthorizationProvider.init(
mediatorDidDocument: bobMediatorDocument,
didManager: aliceDidManager,
),
forwardMessageOptions: const ForwardMessageOptions(
shouldSign: true,
keyWrappingAlgorithm: KeyWrappingAlgorithm.ecdhEs,
encryptionAlgorithm: EncryptionAlgorithm.a256cbc,
),
);
// Alice sends a message to Bob
final sentMessage = await aliceMediatorClient.sendMessage(
forwardMessage,
);Unpack DIDComm Message
Bob receives the encrypted message and unpacks it.
unpackToPlainTextMessage
final messages = await bobMediatorClient.fetchMessages();
final unpackedMessage = await DidcommMessage.unpackToPlainTextMessage(
// The received message
message: jsonDecode(sentMessageByAlice) as Map<String, dynamic>,
// Bob's DID manager for decryption
recipientDidManager: bobDidManager,
// Expected wrapping
expectedMessageWrappingTypes: [MessageWrappingType.authcryptSignPlaintext],
// List of expected signers' key IDs
expectedSigners: [aliceSigner.didKeyId],
);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
expectedMessageWrappingTypesis omitted or set tonull, 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.
What’s Next
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.