Awesome
DIDComm Python
Basic DIDComm v2 support in Python.
Installation
pip install didcomm
DIDComm + peerdid Demo
See https://github.com/sicpa-dlab/didcomm-demo.
Assumptions and Limitations
- Python >= 3.7.
- In order to use the library,
SecretsResolver
andDIDResolver
interfaces must be implemented on the application level. Implementation of that interfaces is out of DIDComm library scope.- 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-384, P-256, P-521
- 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
Examples
See demo scripts 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:
pack_encrypted
to build an Encrypted DIDComm messagepack_signed
to build a Signed DIDComm messagepack_plaintext
to build a Plaintext DIDComm message
- Build a
- Receiver side:
- Call
unpack
on receiver side that will decrypt the message, verify signature if needed and return aMessage
for further processing on the application level.
- Call
1. Build an Encrypted DIDComm message for the given recipient
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 pack_encrypted
documentation for more details.
Authentication encryption example (most common case):
# ALICE
message = Message(
body={"aaa": 1, "bbb": 2},
id="1234567890",
type="my-protocol/1.0",
frm=ALICE_DID,
to=[BOB_DID],
)
pack_result = await pack_encrypted(
resolvers_config=resolvers_config_alice,
message=message,
frm=ALICE_DID,
to=BOB_DID,
pack_config=PackEncryptedConfig(),
)
packed_msg = pack_result.packed_msg
print(f"Sending ${packed_msg} to ${pack_result.service_metadata.service_endpoint}")
# BOB
unpack_result = await unpack(resolvers_config_bob, packed_msg)
print(f"Got ${unpack_result.message} message")
Anonymous encryption example:
message = Message(
body={"aaa": 1, "bbb": 2},
id="1234567890",
type="my-protocol/1.0",
frm=ALICE_DID,
to=[BOB_DID],
)
pack_result = await pack_encrypted(
resolvers_config=resolvers_config_alice,
message=message,
to=BOB_DID,
pack_config=PackEncryptedConfig(),
)
Encryption with non-repudiation example:
message = Message(
body={"aaa": 1, "bbb": 2},
id="1234567890",
type="my-protocol/1.0",
frm=ALICE_DID,
to=[BOB_DID],
)
pack_result = await pack_encrypted(
resolvers_config=resolvers_config_alice,
message=message,
frm=ALICE_DID,
sign_frm=ALICE_DID,
to=BOB_DID,
pack_config=PackEncryptedConfig(),
)
2. Build an unencrypted but Signed DIDComm message
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 pack_signed
documentation for more details.
# ALICE
message = Message(
body={"aaa": 1, "bbb": 2},
id="1234567890",
type="my-protocol/1.0",
frm=ALICE_DID,
to=[BOB_DID],
)
pack_result = await pack_signed(
resolvers_config=resolvers_config_alice,
message=message,
sign_frm=ALICE_DID
)
packed_msg = pack_result.packed_msg
print(f"Publishing ${packed_msg}")
# BOB
unpack_result = await unpack(resolvers_config_bob, packed_msg)
print(f"Got ${unpack_result.message} message signed as ${unpack_result.metadata.signed_message}")
3. Build a Plaintext DIDComm message
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
message = Message(
body={"aaa": 1, "bbb": 2},
id="1234567890",
type="my-protocol/1.0",
frm=ALICE_DID,
to=[BOB_DID],
)
pack_result = await pack_plaintext(resolvers_config=resolvers_config_alice, message)
print(f"Publishing ${pack_result.packed_msg}")
# BOB
unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg)
print(f"Got ${unpack_result.message} message")
Contribution
PRs are welcome!
The following CI checks are run against every PR: