didcomm

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: MIT Imports: 20 Imported by: 2

README

go-didcomm

A Go library for DIDComm v2 messaging with support for signed, anonymous encrypted, and authenticated encrypted messages.

Features

  • Signed messages (JWS) using Ed25519/EdDSA
  • Anonymous encryption (anoncrypt) using ECDH-ES+A256KW / A256CBC-HS512
  • Authenticated encryption (authcrypt) using sign-then-encrypt
  • JSON serialization by default for JWS and JWE; compact serialization is auto-detected on unpack
  • Auto-detection of message format on unpack (JWE, JWS, or plain JSON)
  • did:key and did:web generation with Ed25519 signing and X25519 key agreement keys
  • Automatic DID resolution for did:key (local) and did:web (HTTPS fetch)
  • Pluggable DID resolver and secrets store interfaces

Install

go get github.com/Notabene-id/go-didcomm

Usage

Generate DIDs and set up a client
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	didcomm "github.com/Notabene-id/go-didcomm"
)

func main() {
	ctx := context.Background()

	// Generate did:key identities for Alice and Bob
	aliceDoc, aliceKeys, _ := didcomm.GenerateDIDKey()
	bobDoc, bobKeys, _ := didcomm.GenerateDIDKey()

	// Set up resolver (auto-resolves did:key and did:web) and secrets store
	resolver, _ := didcomm.DefaultResolver()

	secrets := didcomm.NewInMemorySecretsStore()
	secrets.Store(aliceKeys)
	secrets.Store(bobKeys)

	client := didcomm.NewClient(resolver, secrets)

	// Create a message from Alice to Bob
	msg := &didcomm.Message{
		ID:   "msg-1",
		Type: "https://example.com/hello",
		From: aliceDoc.ID,
		To:   []string{bobDoc.ID},
		Body: json.RawMessage(`{"text": "Hello Bob!"}`),
	}

	// Pack as authenticated encrypted message
	packed, err := client.PackAuthcrypt(ctx, msg)
	if err != nil {
		log.Fatal(err)
	}

	// Unpack (auto-detects format)
	result, err := client.Unpack(ctx, packed)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Message type: %s\n", result.Message.Type)
	fmt.Printf("Encrypted: %v, Signed: %v\n", result.Encrypted, result.Signed)
}
Packing modes
// Signed (JWS) — sender is authenticated, message is not encrypted
packed, err := client.PackSigned(ctx, msg)

// Anonymous encryption — encrypted for recipients, sender is anonymous
packed, err := client.PackAnoncrypt(ctx, msg)

// Authenticated encryption — signed then encrypted
packed, err := client.PackAuthcrypt(ctx, msg)
Custom resolver

Implement the Resolver interface to integrate with your DID resolution infrastructure:

type Resolver interface {
	Resolve(ctx context.Context, did string) (*DIDDocument, error)
}

Built-in resolvers: DIDKeyResolver (local), DIDWebResolver (HTTPS), MultiResolver (routes by method), InMemoryResolver (manual). Use DefaultResolver() for a pre-configured setup.

Custom secrets resolver

Implement the SecretsResolver interface to integrate with your key management system:

type SecretsResolver interface {
	GetKey(ctx context.Context, kid string) (jwk.Key, error)
}

CLI

A command-line tool for DIDComm v2 operations: generate identities, pack/unpack messages, and send to endpoints.

Install
go install github.com/Notabene-id/go-didcomm/cmd/didcomm@latest
Commands
didcomm did generate-key [--output-dir <dir>]
didcomm did generate-web --domain <d> [--path <p>] [--service-endpoint <url>] [--output-dir <dir>]
didcomm did resolve <did> [--did-doc <f>]
didcomm pack signed    --key-file <f> [--send] [--did-doc <f>] [--message <m>]
didcomm pack anoncrypt [--send] [--did-doc <f>] [--message <m>]
didcomm pack authcrypt --key-file <f> [--send] [--did-doc <f>] [--message <m>]
didcomm unpack         --key-file <f> [--did-doc <f>] [--message <m>]
didcomm send           --to <url> [--message <m>]

DID resolution is automatic for did:key (decoded locally) and did:web (fetched over HTTPS). The --did-doc flag is only needed to override or supplement auto-resolved documents.

The --send flag on pack commands resolves the first recipient's DIDCommMessaging service endpoint and POSTs the packed message to it.

The --message flag accepts - for stdin (default), @filename to read from a file, or an inline JSON string.

Walkthrough

Generate identities for Alice and Bob:

didcomm did generate-key --output-dir alice
didcomm did generate-key --output-dir bob

This creates did-doc.json (public DID document) and keys.json (private JWK Set) in each directory.

Resolve a DID document (auto-resolves did:key and did:web):

didcomm did resolve did:web:notabene.id:bq

Create a message and pack it with authenticated encryption (did:key auto-resolves — no --did-doc needed):

ALICE=$(jq -r .id alice/did-doc.json)
BOB=$(jq -r .id bob/did-doc.json)

echo '{"id":"1","type":"https://example.com/hello","from":"'$ALICE'","to":["'$BOB'"],"body":{"text":"hi"}}' | \
  didcomm pack authcrypt --key-file alice/keys.json > packed.json

Unpack with Bob's keys:

didcomm unpack --key-file bob/keys.json --message @packed.json

Send a packed message to an endpoint:

didcomm send --to https://example.com/didcomm --message @packed.json

The content type is auto-detected (application/didcomm-encrypted+json, application/didcomm-signed+json, or application/didcomm-plain+json).

Development

Prerequisites
  • Go 1.25 or later
Running tests
go test ./...

With verbose output:

go test -v ./...

With coverage:

go test -cover ./...
Project structure
.
├── didcomm.go           # Client with Pack*/Unpack operations
├── message.go           # DIDComm v2 Message type and JSON marshaling
├── did.go               # DID document types, did:key/did:web generation, Resolver interface
├── resolve_didkey.go    # DIDKeyResolver — local did:key resolution
├── resolve_didweb.go    # DIDWebResolver — HTTPS did:web resolution
├── resolve_multi.go     # MultiResolver — routes by DID method, DefaultResolver()
├── keys.go              # Ed25519/X25519 key pair generation
├── secrets.go           # SecretsResolver interface and in-memory implementation
├── encrypt.go           # Anonymous encryption (anoncrypt) using JWE
├── authcrypt.go         # Authenticated encryption (sign-then-encrypt)
├── sign.go              # JWS signing and verification
├── errors.go            # Sentinel errors
├── cli/                # Exported CLI utilities (shared with tap-go)
├── cmd/
│   └── didcomm/         # CLI tool
└── internal/
    └── convert/         # Ed25519 ↔ X25519 key conversion

Changelog

See CHANGELOG.md for release notes.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrKeyNotFound is returned when a key cannot be found in the secrets resolver.
	ErrKeyNotFound = errors.New("didcomm: key not found")

	// ErrDIDNotFound is returned when a DID document cannot be resolved.
	ErrDIDNotFound = errors.New("didcomm: DID not found")

	// ErrInvalidMessage is returned when a message is malformed or missing required fields.
	ErrInvalidMessage = errors.New("didcomm: invalid message")

	// ErrEncryptionFailed is returned when JWE encryption fails.
	ErrEncryptionFailed = errors.New("didcomm: encryption failed")

	// ErrDecryptionFailed is returned when JWE decryption fails.
	ErrDecryptionFailed = errors.New("didcomm: decryption failed")

	// ErrSigningFailed is returned when JWS signing fails.
	ErrSigningFailed = errors.New("didcomm: signing failed")

	// ErrVerificationFailed is returned when JWS signature verification fails.
	ErrVerificationFailed = errors.New("didcomm: verification failed")

	// ErrUnsupportedKeyType is returned when a key type is not supported.
	ErrUnsupportedKeyType = errors.New("didcomm: unsupported key type")

	// ErrNoRecipients is returned when no recipients are specified for encryption.
	ErrNoRecipients = errors.New("didcomm: no recipients")

	// ErrNoSender is returned when no sender is specified for operations that require one.
	ErrNoSender = errors.New("didcomm: no sender")

	// ErrNoServiceEndpoint is returned when a DID document has no DIDCommMessaging service endpoint.
	ErrNoServiceEndpoint = errors.New("didcomm: no service endpoint")
)

Functions

func DefaultResolver

func DefaultResolver() (*MultiResolver, *InMemoryResolver)

DefaultResolver creates a MultiResolver pre-configured with did:key and did:web resolvers, and an InMemoryResolver as fallback for manually stored documents. Returns both the MultiResolver (for use with Client) and the InMemoryResolver (for Store calls).

func GenerateDIDKey

func GenerateDIDKey() (*DIDDocument, *KeyPair, error)

GenerateDIDKey generates a new did:key with Ed25519 signing and X25519 encryption keys.

func GenerateDIDWeb

func GenerateDIDWeb(domain, path string) (*DIDDocument, *KeyPair, error)

GenerateDIDWeb generates a did:web with Ed25519/X25519 keys. The caller is responsible for hosting the returned DID document at the appropriate URL.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client provides DIDComm v2 pack and unpack operations.

func NewClient

func NewClient(resolver DIDResolver, crypto CryptoOperations) *Client

NewClient creates a new DIDComm client.

func (*Client) PackAnoncrypt

func (c *Client) PackAnoncrypt(ctx context.Context, msg *Message) ([]byte, error)

PackAnoncrypt creates an anonymous encrypted JWE DIDComm message. No sender identification is included.

func (*Client) PackAuthcrypt

func (c *Client) PackAuthcrypt(ctx context.Context, msg *Message) ([]byte, error)

PackAuthcrypt creates an authenticated encrypted DIDComm message (sign-then-encrypt). The message must have From and To fields.

func (*Client) PackSigned

func (c *Client) PackSigned(ctx context.Context, msg *Message) ([]byte, error)

PackSigned creates a JWS-signed DIDComm message. The message must have a From field to identify the signing key.

func (*Client) Unpack

func (c *Client) Unpack(ctx context.Context, envelope []byte) (*UnpackResult, error)

Unpack auto-detects the format (JWE, JWS, or plain JSON) and unpacks accordingly.

type CryptoOperations added in v0.4.0

type CryptoOperations interface {
	// Sign signs payload using the EdDSA key identified by kid.
	// The provided headers are set as JWS protected headers.
	//
	// Implementations MUST emit JWS JSON serialization (flattened or general),
	// per DIDComm v2 §Message Signing: "When transmitted in a normal JWM
	// fashion, the JSON Serialization MUST be used." Compact serialization is
	// not a conforming DIDComm transmission format.
	Sign(ctx context.Context, kid string, payload []byte, headers jws.Headers) ([]byte, error)

	// Decrypt decrypts a JWE message using the ECDH-ES+A256KW key identified by kid.
	Decrypt(ctx context.Context, kid string, encrypted []byte) ([]byte, error)
}

CryptoOperations provides sealed signing and decryption operations. Implementations hold private key material internally — callers receive only the results of cryptographic operations, never raw keys.

type DIDDocument

type DIDDocument struct {
	ID                 string               `json:"id"`
	VerificationMethod []VerificationMethod `json:"verificationMethod,omitempty"`
	Authentication     []VerificationMethod `json:"authentication,omitempty"`
	KeyAgreement       []VerificationMethod `json:"keyAgreement,omitempty"`
	Service            []Service            `json:"service,omitempty"`
}

DIDDocument is a thin DID document with only DIDComm-relevant fields.

func (*DIDDocument) FindDIDCommEndpoint

func (doc *DIDDocument) FindDIDCommEndpoint() (string, error)

FindDIDCommEndpoint returns the first DIDCommMessaging service endpoint URL from the document.

func (*DIDDocument) FindEncryptionKey

func (doc *DIDDocument) FindEncryptionKey() (*VerificationMethod, error)

FindEncryptionKey returns the first key agreement key from a DID document.

func (*DIDDocument) FindSigningKey

func (doc *DIDDocument) FindSigningKey() (*VerificationMethod, error)

FindSigningKey returns the first authentication key from a DID document.

func (*DIDDocument) UnmarshalJSON added in v0.3.0

func (doc *DIDDocument) UnmarshalJSON(data []byte) error

UnmarshalJSON handles DID document fields where authentication and keyAgreement can contain either inline verification method objects or string references to entries in the verificationMethod array.

type DIDKeyResolver

type DIDKeyResolver struct{}

DIDKeyResolver resolves did:key DIDs locally by decoding the multicodec-encoded public key.

func (*DIDKeyResolver) Resolve

func (r *DIDKeyResolver) Resolve(_ context.Context, did string) (*DIDDocument, error)

Resolve parses a did:key DID and returns a DIDDocument with authentication and key agreement keys.

type DIDResolver

type DIDResolver interface {
	Resolve(ctx context.Context, did string) (*DIDDocument, error)
}

DIDResolver resolves DIDs to DID documents.

type DIDWebResolver

type DIDWebResolver struct {
	// HTTPClient is the HTTP client used for fetching DID documents.
	// If nil, http.DefaultClient is used.
	HTTPClient *http.Client
}

DIDWebResolver resolves did:web DIDs by fetching DID documents over HTTPS.

func (*DIDWebResolver) Resolve

func (r *DIDWebResolver) Resolve(ctx context.Context, did string) (*DIDDocument, error)

Resolve fetches and parses a did:web DID document.

type InMemoryResolver

type InMemoryResolver struct {
	// contains filtered or unexported fields
}

InMemoryResolver is a simple in-memory implementation of DIDResolver.

func NewInMemoryResolver

func NewInMemoryResolver() *InMemoryResolver

NewInMemoryResolver creates a new in-memory DID resolver.

func (*InMemoryResolver) Resolve

func (r *InMemoryResolver) Resolve(_ context.Context, did string) (*DIDDocument, error)

Resolve looks up a DID document by DID.

func (*InMemoryResolver) Store

func (r *InMemoryResolver) Store(doc *DIDDocument)

Store registers a DID document with the resolver.

type InMemorySecretsStore

type InMemorySecretsStore struct {
	// contains filtered or unexported fields
}

InMemorySecretsStore is an in-memory implementation of CryptoOperations. Private keys are held in memory and never exposed via a getter.

func NewInMemorySecretsStore

func NewInMemorySecretsStore() *InMemorySecretsStore

NewInMemorySecretsStore creates a new empty in-memory secrets store.

func (*InMemorySecretsStore) Decrypt added in v0.4.0

func (s *InMemorySecretsStore) Decrypt(_ context.Context, kid string, encrypted []byte) ([]byte, error)

Decrypt decrypts a JWE message using the key identified by kid.

func (*InMemorySecretsStore) Sign added in v0.4.0

func (s *InMemorySecretsStore) Sign(_ context.Context, kid string, payload []byte, headers jws.Headers) ([]byte, error)

Sign signs payload using the key identified by kid. Output uses JWS JSON serialization (flattened form for a single signer), as required by DIDComm v2: "When transmitted in a normal JWM fashion, the JSON Serialization MUST be used." Other CryptoOperations implementations should emit JSON serialization for the same reason.

func (*InMemorySecretsStore) Store

func (s *InMemorySecretsStore) Store(kp *KeyPair)

Store adds a key pair's private keys to the store, indexed by their key IDs.

func (*InMemorySecretsStore) StoreKey

func (s *InMemorySecretsStore) StoreKey(key jwk.Key)

StoreKey adds a single JWK to the store indexed by its key ID.

type KeyPair

type KeyPair struct {

	// JWK representations
	SigningJWK    jwk.Key // OKP Ed25519 private key
	EncryptionJWK jwk.Key // OKP X25519 private key
	// contains filtered or unexported fields
}

KeyPair holds a signing (Ed25519) and encryption (X25519) key pair with their JWK representations.

func GenerateKeyPair

func GenerateKeyPair() (*KeyPair, error)

GenerateKeyPair generates a new Ed25519 signing key pair and derives the corresponding X25519 encryption key pair.

func (*KeyPair) EncryptionPublicJWK

func (kp *KeyPair) EncryptionPublicJWK() (jwk.Key, error)

EncryptionPublicJWK returns the public JWK for the encryption key.

func (*KeyPair) SigningPublicJWK

func (kp *KeyPair) SigningPublicJWK() (jwk.Key, error)

SigningPublicJWK returns the public JWK for the signing key.

type Message

type Message struct {
	ID        string                 `json:"id"`
	Type      string                 `json:"type"`
	From      string                 `json:"from,omitempty"`
	To        []string               `json:"to,omitempty"`
	CreatedAt *time.Time             `json:"created_time,omitempty"`
	ExpiresAt *time.Time             `json:"expires_time,omitempty"`
	Body      json.RawMessage        `json:"body"`
	Thid      string                 `json:"thid,omitempty"`
	Pthid     string                 `json:"pthid,omitempty"`
	Extra     map[string]interface{} `json:"-"`
}

Message represents a DIDComm v2 message.

func (*Message) MarshalJSON

func (m *Message) MarshalJSON() ([]byte, error)

MarshalJSON implements custom JSON marshaling that includes Extra fields.

func (*Message) UnmarshalJSON

func (m *Message) UnmarshalJSON(data []byte) error

UnmarshalJSON implements custom JSON unmarshaling that captures extra fields.

func (*Message) Validate

func (m *Message) Validate() error

Validate checks that the message has required fields.

type MultiResolver

type MultiResolver struct {
	// contains filtered or unexported fields
}

MultiResolver routes DID resolution to method-specific resolvers based on the DID prefix.

func NewMultiResolver

func NewMultiResolver(methods map[string]DIDResolver, fallback DIDResolver) *MultiResolver

NewMultiResolver creates a MultiResolver with the given method resolvers and optional fallback. The methods map keys should be DID method prefixes like "did:key" or "did:web".

func (*MultiResolver) Resolve

func (r *MultiResolver) Resolve(ctx context.Context, did string) (*DIDDocument, error)

Resolve routes the DID to the appropriate method-specific resolver.

type Service

type Service struct {
	ID              string `json:"id"`
	Type            string `json:"type"`
	ServiceEndpoint string `json:"serviceEndpoint"`
}

Service represents a DID document service entry.

type UnpackResult

type UnpackResult struct {
	Message   *Message
	Encrypted bool
	Signed    bool
	Anonymous bool // true if anoncrypt (no skid), false if authcrypt
}

UnpackResult contains the result of unpacking a DIDComm v2 message.

type VerificationMethod

type VerificationMethod struct {
	ID         string  `json:"id"`
	Type       string  `json:"type"`
	Controller string  `json:"controller"`
	PublicKey  jwk.Key `json:"-"`
}

VerificationMethod represents a DID document verification method.

func (VerificationMethod) MarshalJSON

func (vm VerificationMethod) MarshalJSON() ([]byte, error)

MarshalJSON serializes a VerificationMethod including publicKeyJwk.

func (*VerificationMethod) UnmarshalJSON

func (vm *VerificationMethod) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes a VerificationMethod restoring publicKeyJwk into PublicKey.

Directories

Path Synopsis
cmd
didcomm command
internal
convert
Package convert provides Ed25519 to X25519 key conversion utilities.
Package convert provides Ed25519 to X25519 key conversion utilities.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL