{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## DIDComm \"Hello World\" in Python\n", "This simple \"Hello World\" example shows how Alice can create an encrypted message to Bob. In that case, we are using [DIDComm Python](https://github.com/sicpa-dlab/didcomm-python) and [Peerdid Python](https://github.com/sicpa-dlab/peer-did-python) libraries from [SICPA](https://www.sicpa.com)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 1: Imports\n", "First, we need to import all required functions, clases and types from `didcomm` and `peerdid` libraries as follows:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "import json\n", "from didcomm.common.types import DID, VerificationMethodType, VerificationMaterial, VerificationMaterialFormat\n", "from didcomm.did_doc.did_doc import DIDDoc, VerificationMethod, DIDCommService\n", "from didcomm.did_doc.did_resolver import DIDResolver\n", "from didcomm.message import Message\n", "from didcomm.secrets.secrets_resolver_demo import SecretsResolverDemo\n", "from didcomm.unpack import unpack, UnpackResult\n", "from didcomm.common.resolvers import ResolversConfig\n", "from didcomm.pack_encrypted import pack_encrypted, PackEncryptedConfig, PackEncryptedResult\n", "from didcomm.secrets.secrets_util import generate_x25519_keys_as_jwk_dict, generate_ed25519_keys_as_jwk_dict, jwk_to_secret\n", "from peerdid import peer_did\n", "from peerdid.did_doc import DIDDocPeerDID\n", "from peerdid.types import VerificationMaterialAuthentication, VerificationMethodTypeAuthentication, VerificationMaterialAgreement, VerificationMethodTypeAgreement, VerificationMaterialFormatPeerDID" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 2: Helpers\n", "In this step we add three helper functionalities:\n", "\n", "1. **Secret resolver**\n", "\n", "This sample code needs a storage to keep the generated key pair secrets. It will then be referenced by the library as a `secrets_resolver`. We can instantiate it as follows:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "secrets_resolver = SecretsResolverDemo()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the `SecretsResolverDemo` simply stores the keys in a text file named `secrets.json`. As you've just realized, this secret storage is anything but secure. Keep in mind that securing keys is of utmost importance for a self-sovereign identity; never use it in production." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. **DID Resolver**\n", "\n", "The following class is needed by the library to resolve the DID Peer into a DID Document and put it in the format required by the library:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "class DIDResolverPeerDID(DIDResolver):\n", " async def resolve(self, did: DID) -> DIDDoc:\n", " did_doc_json = peer_did.resolve_peer_did(did, format = VerificationMaterialFormatPeerDID.JWK)\n", " did_doc = DIDDocPeerDID.from_json(did_doc_json)\n", "\n", " return DIDDoc(\n", " did=did_doc.did,\n", " key_agreement_kids = did_doc.agreement_kids,\n", " authentication_kids = did_doc.auth_kids,\n", " verification_methods = [\n", " VerificationMethod(\n", " id = m.id,\n", " type = VerificationMethodType.JSON_WEB_KEY_2020,\n", " controller = m.controller,\n", " verification_material = VerificationMaterial(\n", " format = VerificationMaterialFormat.JWK,\n", " value = json.dumps(m.ver_material.value)\n", " )\n", " )\n", " for m in did_doc.authentication + did_doc.key_agreement\n", " ],\n", " didcomm_services = [\n", " DIDCommService(\n", " id = s.id,\n", " service_endpoint = s.service_endpoint,\n", " routing_keys = s.routing_keys,\n", " accept = s.accept\n", " )\n", " for s in did_doc.service\n", " if isinstance(s, DIDCommServicePeerDID)\n", " ] if did_doc.service else []\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. **DID Peer creation**\n", "\n", "We add the following function to simplify key generation, key storage, and the creation of a DID Peer:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "async def create_simple_peer_did() -> str:\n", " agreem_keys = generate_x25519_keys_as_jwk_dict()\n", " auth_keys = generate_ed25519_keys_as_jwk_dict()\n", " did = peer_did.create_peer_did_numalgo_2(\n", " [VerificationMaterialAgreement(\n", " type = VerificationMethodTypeAgreement.JSON_WEB_KEY_2020,\n", " format = VerificationMaterialFormatPeerDID.JWK,\n", " value = agreem_keys[1])\n", " ],\n", " [VerificationMaterialAuthentication(\n", " type = VerificationMethodTypeAuthentication.JSON_WEB_KEY_2020,\n", " format = VerificationMaterialFormatPeerDID.JWK,\n", " value = auth_keys[1])\n", " ],\n", " None\n", " )\n", " did_doc = DIDDocPeerDID.from_json(peer_did.resolve_peer_did(did))\n", " pk = auth_keys[0]\n", " pk[\"kid\"] = did_doc.auth_kids[0]\n", " await secrets_resolver.add_key(jwk_to_secret(pk))\n", " private_key = agreem_keys[0]\n", " private_key[\"kid\"] = did_doc.agreement_kids[0]\n", " await secrets_resolver.add_key(jwk_to_secret(private_key))\n", "\n", " return did" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function creates a basic DID Peer with only one *Agreement* key, one *Authentication* key, and no *Service* part. You can find more options in the library documentation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 3: Create DIDs\n", "Using our `create_simple_peer_did` helper function, Alice and Bob can create their DID Peer that they will share and use when communicating privately between each other:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Alice's DID: did:peer:2.Ez6LSiFdFDJzL1tkXf4KS5ebtZL1NcyK9VZLatDnUK3EgwxQD.Vz6MksrzR2kUNPVsQWGiVjiFh4m2JvK5JHRfp38tSYeuFJiPT\n", "Bob's DID: did:peer:2.Ez6LStmKwK55pt15YXBVoWnYcrF7U2dCFRPLhKDqYy2ybkaxr.Vz6MkmEcZ3YpkH9jhqBDxAhVbt1EHS86tmdDnSsL2xf5vGDGe\n" ] } ], "source": [ "alice_did = await create_simple_peer_did()\n", "bob_did = await create_simple_peer_did()\n", "print(\"Alice's DID:\", alice_did)\n", "print(\"Bob's DID:\",bob_did)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember that while creating these simple DIDs, our helper function also stores the private keys in the `secrets_resolver`. In a real implementation, Alice will have her own secure store in her own wallet, and Bob will have a separated secure store in his own wallet.\n", "Also, those Peer DIDs can be resolved into DID documents that contain the *Authentication* and *Agreement* public keys." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 4: Encrypt and pack the message\n", "Alice can create a simple \"Hello World\" message with:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "message = Message(\n", " body = {\"msg\": \"Hello World\"},\n", " id = \"unique-id-24160d23ed1d\",\n", " type = \"my-protocol/1.0\",\n", " frm = alice_did,\n", " to = [bob_did]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the message includes an `id` that is mandatory and has to be unique to Alice. Also includes a `type`, also mandatory, that points to the protocol identifier that we've just invented. The `body` contains the actual message in an structured way associated by our `my-protocol/1.0`. Attributes `from` and `to` are optional. Beware that in the code above the property `from` was replaced by `frm` due to a conflict of reserved words in Python; the conversion to the correct property (`from`) is handled internally by the library.\n", "\n", "[DIDComm](https://identity.foundation/didcomm-messaging/spec/#message-formats) defines three message formats: plaintext, signed, and encrypted. We are going to use the latter since it is the most common for most applications. In that case, the message will be encrypted so only Bob can see it.\n", "The final packed message can be generated with this code:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "packed_msg = await pack_encrypted(\n", " resolvers_config = ResolversConfig(\n", " secrets_resolver = secrets_resolver,\n", " did_resolver = DIDResolverPeerDID()\n", " ),\n", " message = message,\n", " frm = alice_did,\n", " to = bob_did,\n", " sign_frm = None,\n", " pack_config = PackEncryptedConfig(protect_sender_id=False)\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This library also offers the option of anonymous encryption, encryption with no repudation, and message signing. Note also that we pass a resolver configuration pointing to our secrets store and the DID resolver.\n", "If you take a look at the packed message, you'll see that the content was hidden in the encryption:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\"protected\":\"eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLWVuY3J5cHRlZCtqc29uIiwiYWxnIjoiRUNESC0xUFUrQTI1NktXIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImFwdSI6IlpHbGtPbkJsWlhJNk1pNUZlalpNVTJsR1pFWkVTbnBNTVhScldHWTBTMU...\n" ] } ], "source": [ "print(packed_msg.packed_msg[:200]+\"...\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 5: Receive and unpack the message\n", "Alice will send the packed message to Bob using a transport. Once received, Bob can unpack it with the following code:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "unpack_msg = await unpack(\n", " resolvers_config=ResolversConfig(\n", " secrets_resolver=secrets_resolver,\n", " did_resolver=DIDResolverPeerDID()\n", " ),\n", " packed_msg=packed_msg.packed_msg\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we also passed the resolver config as before.\n", "Finally, Bob can see Alice's message by:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello World\n" ] } ], "source": [ "print(unpack_msg.message.body[\"msg\"])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.12" } }, "nbformat": 4, "nbformat_minor": 2 }