Skip to content

Commit bafdba6

Browse files
committed
docs: update readme
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
1 parent 310540f commit bafdba6

File tree

1 file changed

+207
-20
lines changed

1 file changed

+207
-20
lines changed

README.md

Lines changed: 207 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,236 @@ This library has the following core components (as outlined in the layer cake ar
1717
The CryptoService provides the core cryptographic capabilities needed to encrypt and decrypt DIDComm Messages. This service is designed to be implemented by users of this library; however, an implementation using Aries Askar is available as an extra (install the `askar` extra to use it). Additional implementations may be added as extras in the future (i.e. an implementation using [Authlib's JWE implementation](https://docs.authlib.org/en/latest/jose/jwe.html) or perhaps an implementation backed by an HSM). The service is seprate from but closely coupled with the SecretsManager. Both must use the same public and private key representations.
1818

1919
> [!WARNING]
20-
> TODO:
21-
> - More details on implementing your own CryptoService
22-
> - More details on using the included Askar CryptoService
20+
> This library requires a crypto backend. You MUST provide a CryptoService and SecretsManager implementation. The Askar backend is available via the `askar` extra (install with `pip install -e ".[askar]"`).
21+
22+
The CryptoService interface (`didcomm_messaging.crypto.base.CryptoService`) requires:
23+
24+
- **`ecdh_es_encrypt(to_keys, message)`** - Encrypt a message using ECDH-ES (anonymous encryption)
25+
- **`ecdh_es_decrypt(wrapper, recip_key)`** - Decrypt an ECDH-ES encrypted message
26+
- **`ecdh_1pu_encrypt(to_keys, sender_key, message)`** - Encrypt using ECDH-1PU (authenticated encryption)
27+
- **`ecdh_1pu_decrypt(wrapper, recip_key, sender_key)`** - Decrypt an ECDH-1PU encrypted message
28+
- **`verification_method_to_public_key(vm)`** - Convert a DID Document verification method to a public key
29+
30+
You must also define `PublicKey` and `SecretKey` types that work with your implementation.
31+
32+
**Using the included AskarCryptoService:**
33+
34+
```python
35+
from didcomm_messaging.crypto.backend.askar import AskarCryptoService, AskarKey, AskarSecretKey
36+
37+
crypto = AskarCryptoService()
38+
```
39+
40+
The AskarCryptoService supports `Ed25519`, `X25519`, `P-256`, and `Secp256k1` key types via the Aries Askar library.
2341

2442
### SecretsManager
2543

2644
The SecretsManager is responsible for retrieving secrets for use by the CryptoService. It is notable that the secret value need not literally contain the value of a private key. For example, in the included Askar implementation, an Askar `Key` value is retrieved. This object in python _does_ permit you to retrieve the bytes of the secret key from Askar if you choose; however, this is not necessary for the operation of the library. This enables Askar to keep the private key value down in the Rust layer where it can better ensure security of the key (zeroizing memory, etc.). This is not so distant from interacting with an HSM; as long as the `SecretKey` value retrieved by the SecretsManager can be used by the CryptoService to perform the required cryptographic operations, exactly what is stored inside of the `SecretKey` object is irrelevant.
2745

28-
> [!WARNING]
29-
> TODO:
30-
> - More details on implementing your own SecretsManager
31-
> - More details on the included AskarSecretsManager
46+
The SecretsManager interface (`didcomm_messaging.crypto.base.SecretsManager`) requires:
47+
48+
- **`get_secret_by_kid(kid)`** - Retrieve a secret key by its key ID. Returns `None` if not found.
49+
50+
**Implementing your own SecretsManager:**
51+
52+
```python
53+
from didcomm_messaging.crypto.base import SecretsManager, SecretKey
54+
55+
class MySecretsManager(SecretsManager[MySecretKey]):
56+
async def get_secret_by_kid(self, kid: str) -> Optional[MySecretKey]:
57+
# Look up your secret by key ID
58+
# Return None if not found
59+
pass
60+
```
61+
62+
**Using the included AskarSecretsManager:**
63+
64+
```python
65+
from didcomm_messaging.crypto.backend.askar import AskarSecretsManager
66+
from aries_askar import Store
67+
68+
store = await Store.open("my-db")
69+
secrets = AskarSecretsManager(store)
70+
```
71+
72+
The library also includes an `InMemorySecretsManager` (`didcomm_messaging.crypto.backend.basic`) for testing and simple use cases.
3273

3374
### DIDResolver
3475

3576
This component provides a fairly generic DID Resolution interface. Users of this library will provide a resolver implementation for the DID Methods they care about. Implementations of did:peer:2 and did:peer:4 are included as part of the `did_peer` extra.
3677

37-
> [!WARNING]
38-
> TODO:
39-
> - More details on PrefixResolver
40-
> - More details on implementing your own DIDResolver
78+
The DIDResolver interface (`didcomm_messaging.resolver.DIDResolver`) requires:
79+
80+
- **`resolve(did)`** - Resolve a DID to a DID Document (dict)
81+
- **`is_resolvable(did)`** - Check if a DID can be resolved
82+
83+
The interface also provides convenience methods:
84+
- **`resolve_and_parse(did)`** - Returns a parsed `pydid.DIDDocument`
85+
- **`resolve_and_dereference(did_url)`** - Dereference a DID URL to get a resource
86+
- **`resolve_and_dereference_verification_method(did_url)`** - Dereference to a verification method
87+
88+
**Using PrefixResolver:**
89+
90+
The `PrefixResolver` delegates to sub-resolvers based on DID method prefix. This allows you to support multiple DID methods:
91+
92+
```python
93+
from didcomm_messaging.resolver import PrefixResolver
94+
from didcomm_messaging.resolver.peer import Peer2, Peer4
95+
from didcomm_messaging.resolver.web import DIDWeb
96+
97+
resolver = PrefixResolver({
98+
"did:peer:2": Peer2(),
99+
"did:peer:4": Peer4(),
100+
"did:web:": DIDWeb(),
101+
})
102+
```
103+
104+
**Implementing your own DIDResolver:**
105+
106+
```python
107+
from didcomm_messaging.resolver import DIDResolver
108+
109+
class MyDIDResolver(DIDResolver):
110+
async def resolve(self, did: str) -> dict:
111+
# Resolve DID and return DID Document as dict
112+
pass
113+
114+
async def is_resolvable(self, did: str) -> bool:
115+
# Check if this resolver can handle the DID
116+
pass
117+
```
41118

42119
### PackagingService
43120

44121
The PackagingService is responsible for the core functions of packing and unpacking messages. It depends on the CryptoService, the SecretsManager, and the DIDResolver to accomplish this.
45122

123+
The PackagingService (`didcomm_messaging.packaging.PackagingService`) provides:
46124

47-
> [!WARNING]
48-
> TODO:
49-
> - More details on PackagingService
125+
- **`pack(crypto, resolver, secrets, message, to, frm)`** - Pack a message for one or more recipients
126+
- `message`: bytes to encrypt
127+
- `to`: list of recipient DIDs or DID URLs
128+
- `frm`: sender DID (optional, for authenticated encryption)
129+
- Returns encrypted bytes
130+
131+
- **`unpack(crypto, resolver, secrets, enc_message)`** - Unpack and decrypt a message
132+
- Returns tuple of (plaintext bytes, `PackedMessageMetadata`)
133+
134+
- **`extract_packed_message_metadata(enc_message, secrets)`** - Extract metadata without decrypting
135+
136+
**Usage:**
137+
138+
```python
139+
from didcomm_messaging.packaging import PackagingService
140+
141+
packaging = PackagingService()
142+
143+
# Pack a message
144+
packed = await packaging.pack(
145+
crypto=crypto,
146+
resolver=resolver,
147+
secrets=secrets,
148+
message=b'{"hello": "world"}',
149+
to=["did:peer:2:..."],
150+
frm="did:peer:2:..." # optional for authenticated encryption
151+
)
152+
153+
# Unpack a message
154+
plaintext, metadata = await packaging.unpack(
155+
crypto=crypto,
156+
resolver=resolver,
157+
secrets=secrets,
158+
enc_message=packed
159+
)
160+
```
50161

51162
### RoutingService
52163

53164
The RoutingService is responsible for preparing messages for forwarding to a mediator. It depends on the PackagingService and the DIDResolver to accomplish this.
54165

55-
> [!WARNING]
56-
> TODO:
57-
> - More details on RoutingService
166+
The RoutingService (`didcomm_messaging.routing.RoutingService`) provides:
167+
168+
- **`prepare_forward(crypto, packaging, resolver, secrets, to, encoded_message)`** - Prepare a message for forwarding through mediators
169+
- Resolves the recipient's service endpoint
170+
- If the recipient is behind a mediator, wraps the message in forward messages
171+
- Handles multiple levels of routing (mediator chains)
172+
- Returns tuple of (packed message bytes, target service)
173+
174+
**How it works:**
175+
176+
1. Resolves the recipient's DID to find `DIDCommMessaging` service endpoints
177+
2. If the service endpoint is another DID (mediator), recursively resolves
178+
3. Creates forward messages for each layer of mediation
179+
4. Returns the outermost packed message and the final destination service
180+
181+
```python
182+
from didcomm_messaging.routing import RoutingService
183+
184+
routing = RoutingService()
185+
186+
# Prepare a message for forwarding
187+
packed, services = await routing.prepare_forward(
188+
crypto=crypto,
189+
packaging=packaging,
190+
resolver=resolver,
191+
secrets=secrets,
192+
to="did:peer:2:...", # recipient DID
193+
encoded_message=b'...' # already-packed message
194+
)
195+
```
58196

59197
### DIDCommMessaging
60198

61199
The DIDCommMessaging interface is the main entrypoint for interacting with this library. It utilizes all the layers below to prepare messages for other parties.
62200

63-
> [!WARNING]
64-
> TODO:
65-
> - More details on DIDCommMessaging
201+
**Initialization:**
202+
203+
```python
204+
from didcomm_messaging import DIDCommMessaging
205+
206+
dmp = DIDCommMessaging(
207+
crypto=crypto,
208+
secrets=secrets,
209+
resolver=resolver,
210+
packaging=packaging,
211+
routing=routing,
212+
)
213+
```
214+
215+
**Pack a message:**
216+
217+
```python
218+
# Pack and send a message
219+
result = await dmp.pack(
220+
message={"type": "https://didcomm.org/hello/1.0/greeting", "body": {"msg": "Hello!"}},
221+
to="did:peer:2:...",
222+
frm="did:peer:2:..." # optional
223+
)
224+
225+
# Get the endpoint to send to
226+
endpoint = result.get_endpoint("http")
227+
228+
# Get the packed message
229+
packed_message = result.message
230+
```
231+
232+
**Unpack a message:**
233+
234+
```python
235+
result = await dmp.unpack(encoded_message)
236+
237+
print(result.message) # The decrypted message as dict
238+
print(result.authenticated) # True if sender was authenticated
239+
print(result.sender_kid) # Key ID of sender (if authenticated)
240+
print(result.recipient_kid) # Key ID of recipient
241+
```
242+
243+
**Return values:**
244+
245+
- `PackResult.message` - The packed message bytes
246+
- `PackResult.target_services` - List of target `DIDCommV2Service` endpoints
247+
- `PackResult.get_endpoint(protocol)` - Get endpoint by protocol (e.g., "http", "ws")
248+
- `UnpackResult.message` - The unpacked message as dict
249+
- `UnpackResult.authenticated` - Whether the message was authenticated (ECDH-1PU)
250+
- `UnpackResult.encrypted` - Whether the message was encrypted
251+
- `UnpackResult.sender_kid` - Sender's key ID (if authenticated)
252+
- `UnpackResult.recipient_kid` - Recipient's key ID

0 commit comments

Comments
 (0)