github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/didexchange/states.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package didexchange
     8  
     9  import (
    10  	"crypto/ed25519"
    11  	"encoding/base64"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"strings"
    16  
    17  	"github.com/google/uuid"
    18  	"github.com/mitchellh/mapstructure"
    19  
    20  	"github.com/hyperledger/aries-framework-go/pkg/doc/util/jwkkid"
    21  
    22  	"github.com/hyperledger/aries-framework-go/pkg/common/model"
    23  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    24  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
    25  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator"
    26  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
    27  	"github.com/hyperledger/aries-framework-go/pkg/doc/did"
    28  	vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
    29  	"github.com/hyperledger/aries-framework-go/pkg/internal/didcommutil"
    30  	"github.com/hyperledger/aries-framework-go/pkg/kms"
    31  	connectionstore "github.com/hyperledger/aries-framework-go/pkg/store/connection"
    32  	"github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint"
    33  	"github.com/hyperledger/aries-framework-go/spi/storage"
    34  )
    35  
    36  const (
    37  	stateNameNoop = "noop"
    38  	stateNameNull = "null"
    39  	// StateIDInvited marks the invited phase of the did-exchange protocol.
    40  	StateIDInvited = "invited"
    41  	// StateIDRequested marks the requested phase of the did-exchange protocol.
    42  	StateIDRequested = "requested"
    43  	// StateIDResponded marks the responded phase of the did-exchange protocol.
    44  	StateIDResponded = "responded"
    45  	// StateIDCompleted marks the completed phase of the did-exchange protocol.
    46  	StateIDCompleted = "completed"
    47  	// StateIDAbandoned marks the abandoned phase of the did-exchange protocol.
    48  	StateIDAbandoned   = "abandoned"
    49  	ackStatusOK        = "ok"
    50  	didCommServiceType = "did-communication"
    51  	// legacyDIDCommServiceType for aca-py interop.
    52  	legacyDIDCommServiceType = "IndyAgent"
    53  	// DIDComm V2 service type ref: https://identity.foundation/didcomm-messaging/spec/#did-document-service-endpoint
    54  	didCommV2ServiceType       = "DIDCommMessaging"
    55  	ed25519VerificationKey2018 = "Ed25519VerificationKey2018"
    56  	bls12381G2Key2020          = "Bls12381G2Key2020"
    57  	jsonWebKey2020             = "JsonWebKey2020"
    58  	didMethod                  = "peer"
    59  	x25519KeyAgreementKey2019  = "X25519KeyAgreementKey2019"
    60  )
    61  
    62  var errVerKeyNotFound = errors.New("verkey not found")
    63  
    64  // state action for network call.
    65  type stateAction func() error
    66  
    67  // The did-exchange protocol's state.
    68  type state interface {
    69  	// Name of this state.
    70  	Name() string
    71  
    72  	// Whether this state allows transitioning into the next state.
    73  	CanTransitionTo(next state) bool
    74  
    75  	// ExecuteInbound this state, returning a followup state to be immediately executed as well.
    76  	// The 'noOp' state should be returned if the state has no followup.
    77  	ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (connRecord *connectionstore.Record,
    78  		state state, action stateAction, err error)
    79  }
    80  
    81  // Returns the state towards which the protocol will transition to if the msgType is processed.
    82  func stateFromMsgType(msgType string) (state, error) {
    83  	switch msgType {
    84  	case InvitationMsgType, oobMsgType:
    85  		return &invited{}, nil
    86  	case RequestMsgType:
    87  		return &requested{}, nil
    88  	case ResponseMsgType:
    89  		return &responded{}, nil
    90  	case AckMsgType, CompleteMsgType:
    91  		return &completed{}, nil
    92  	default:
    93  		return nil, fmt.Errorf("unrecognized msgType: %s", msgType)
    94  	}
    95  }
    96  
    97  // Returns the state representing the name.
    98  func stateFromName(name string) (state, error) {
    99  	switch name {
   100  	case stateNameNoop:
   101  		return &noOp{}, nil
   102  	case stateNameNull:
   103  		return &null{}, nil
   104  	case StateIDInvited:
   105  		return &invited{}, nil
   106  	case StateIDRequested:
   107  		return &requested{}, nil
   108  	case StateIDResponded:
   109  		return &responded{}, nil
   110  	case StateIDCompleted:
   111  		return &completed{}, nil
   112  	case StateIDAbandoned:
   113  		return &abandoned{}, nil
   114  	default:
   115  		return nil, fmt.Errorf("invalid state name %s", name)
   116  	}
   117  }
   118  
   119  type noOp struct{}
   120  
   121  func (s *noOp) Name() string {
   122  	return stateNameNoop
   123  }
   124  
   125  func (s *noOp) CanTransitionTo(_ state) bool {
   126  	return false
   127  }
   128  
   129  func (s *noOp) ExecuteInbound(_ *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record,
   130  	state, stateAction, error) {
   131  	return nil, nil, nil, errors.New("cannot execute no-op")
   132  }
   133  
   134  // null state.
   135  type null struct{}
   136  
   137  func (s *null) Name() string {
   138  	return stateNameNull
   139  }
   140  
   141  func (s *null) CanTransitionTo(next state) bool {
   142  	return StateIDInvited == next.Name() || StateIDRequested == next.Name()
   143  }
   144  
   145  func (s *null) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record,
   146  	state, stateAction, error) {
   147  	return &connectionstore.Record{}, &noOp{}, nil, nil
   148  }
   149  
   150  // invited state.
   151  type invited struct{}
   152  
   153  func (s *invited) Name() string {
   154  	return StateIDInvited
   155  }
   156  
   157  func (s *invited) CanTransitionTo(next state) bool {
   158  	return StateIDRequested == next.Name()
   159  }
   160  
   161  func (s *invited) ExecuteInbound(msg *stateMachineMsg, _ string, _ *context) (*connectionstore.Record,
   162  	state, stateAction, error) {
   163  	if msg.Type() != InvitationMsgType && msg.Type() != oobMsgType {
   164  		return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name())
   165  	}
   166  
   167  	return msg.connRecord, &requested{}, func() error { return nil }, nil
   168  }
   169  
   170  // requested state.
   171  type requested struct{}
   172  
   173  func (s *requested) Name() string {
   174  	return StateIDRequested
   175  }
   176  
   177  func (s *requested) CanTransitionTo(next state) bool {
   178  	return StateIDResponded == next.Name()
   179  }
   180  
   181  func (s *requested) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record,
   182  	state, stateAction, error) {
   183  	switch msg.Type() {
   184  	case oobMsgType:
   185  		oobInvitation := &OOBInvitation{}
   186  
   187  		err := msg.Decode(oobInvitation)
   188  		if err != nil {
   189  			return nil, nil, nil, fmt.Errorf("failed to decode oob invitation: %w", err)
   190  		}
   191  
   192  		action, record, err := ctx.handleInboundOOBInvitation(oobInvitation, thid, msg.options, msg.connRecord)
   193  		if err != nil {
   194  			return nil, nil, nil, fmt.Errorf("failed to handle inbound oob invitation : %w", err)
   195  		}
   196  
   197  		return record, &noOp{}, action, nil
   198  	case InvitationMsgType:
   199  		invitation := &Invitation{}
   200  
   201  		err := msg.Decode(invitation)
   202  		if err != nil {
   203  			return nil, nil, nil, fmt.Errorf("JSON unmarshalling of invitation: %w", err)
   204  		}
   205  
   206  		action, connRecord, err := ctx.handleInboundInvitation(invitation, thid, msg.options, msg.connRecord)
   207  		if err != nil {
   208  			return nil, nil, nil, fmt.Errorf("handle inbound invitation: %w", err)
   209  		}
   210  
   211  		return connRecord, &noOp{}, action, nil
   212  	case RequestMsgType:
   213  		return msg.connRecord, &responded{}, func() error { return nil }, nil
   214  	default:
   215  		return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name())
   216  	}
   217  }
   218  
   219  // responded state.
   220  type responded struct{}
   221  
   222  func (s *responded) Name() string {
   223  	return StateIDResponded
   224  }
   225  
   226  func (s *responded) CanTransitionTo(next state) bool {
   227  	return StateIDCompleted == next.Name()
   228  }
   229  
   230  func (s *responded) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record,
   231  	state, stateAction, error) {
   232  	switch msg.Type() {
   233  	case RequestMsgType:
   234  		request := &Request{}
   235  
   236  		err := msg.Decode(request)
   237  		if err != nil {
   238  			return nil, nil, nil, fmt.Errorf("JSON unmarshalling of request: %w", err)
   239  		}
   240  
   241  		action, connRecord, err := ctx.handleInboundRequest(request, msg.options, msg.connRecord)
   242  		if err != nil {
   243  			return nil, nil, nil, fmt.Errorf("handle inbound request: %w", err)
   244  		}
   245  
   246  		return connRecord, &noOp{}, action, nil
   247  	case ResponseMsgType, CompleteMsgType:
   248  		return msg.connRecord, &completed{}, func() error { return nil }, nil
   249  	default:
   250  		return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name())
   251  	}
   252  }
   253  
   254  // completed state.
   255  type completed struct{}
   256  
   257  func (s *completed) Name() string {
   258  	return StateIDCompleted
   259  }
   260  
   261  func (s *completed) CanTransitionTo(next state) bool {
   262  	return false
   263  }
   264  
   265  func (s *completed) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record,
   266  	state, stateAction, error) {
   267  	switch msg.Type() {
   268  	case ResponseMsgType:
   269  		response := &Response{}
   270  
   271  		err := msg.Decode(response)
   272  		if err != nil {
   273  			return nil, nil, nil, fmt.Errorf("JSON unmarshalling of response: %w", err)
   274  		}
   275  
   276  		action, connRecord, err := ctx.handleInboundResponse(response)
   277  		if err != nil {
   278  			return nil, nil, nil, fmt.Errorf("handle inbound response: %w", err)
   279  		}
   280  
   281  		return connRecord, &noOp{}, action, nil
   282  	case AckMsgType:
   283  		action := func() error { return nil }
   284  		return msg.connRecord, &noOp{}, action, nil
   285  	case CompleteMsgType:
   286  		complete := &Complete{}
   287  
   288  		err := msg.Decode(complete)
   289  		if err != nil {
   290  			return nil, nil, nil, fmt.Errorf("JSON unmarshalling of complete: %w", err)
   291  		}
   292  
   293  		action := func() error { return nil }
   294  
   295  		if msg.connRecord == nil {
   296  			return nil, &noOp{}, action, nil
   297  		}
   298  
   299  		connRec := *msg.connRecord
   300  
   301  		return &connRec, &noOp{}, action, nil
   302  	default:
   303  		return nil, nil, nil, fmt.Errorf("illegal msg type %s for state %s", msg.Type(), s.Name())
   304  	}
   305  }
   306  
   307  // abandoned state.
   308  type abandoned struct{}
   309  
   310  func (s *abandoned) Name() string {
   311  	return StateIDAbandoned
   312  }
   313  
   314  func (s *abandoned) CanTransitionTo(next state) bool {
   315  	return false
   316  }
   317  
   318  func (s *abandoned) ExecuteInbound(msg *stateMachineMsg, thid string, ctx *context) (*connectionstore.Record,
   319  	state, stateAction, error) {
   320  	return nil, nil, nil, errors.New("not implemented")
   321  }
   322  
   323  func (ctx *context) handleInboundOOBInvitation(oobInv *OOBInvitation, thid string, options *options,
   324  	connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) {
   325  	svc, err := ctx.getServiceBlock(oobInv)
   326  	if err != nil {
   327  		return nil, nil, fmt.Errorf("failed to get service block: %w", err)
   328  	}
   329  
   330  	dest := &service.Destination{
   331  		RecipientKeys:     svc.RecipientKeys,
   332  		ServiceEndpoint:   svc.ServiceEndpoint,
   333  		RoutingKeys:       svc.RoutingKeys,
   334  		MediaTypeProfiles: svc.Accept,
   335  	}
   336  
   337  	connRec.ThreadID = thid
   338  
   339  	return ctx.createInvitedRequest(dest, oobInv.MyLabel, thid, connRec.ParentThreadID, options, connRec)
   340  }
   341  
   342  func (ctx *context) handleInboundInvitation(invitation *Invitation, thid string, options *options,
   343  	connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) {
   344  	// create a destination from invitation
   345  	destination, err := ctx.getDestination(invitation)
   346  	if err != nil {
   347  		return nil, nil, err
   348  	}
   349  
   350  	pid := invitation.ID
   351  	if connRec.Implicit {
   352  		pid = invitation.DID
   353  	}
   354  
   355  	return ctx.createInvitedRequest(destination, getLabel(options), thid, pid, options, connRec)
   356  }
   357  
   358  func (ctx *context) createInvitedRequest(destination *service.Destination, label, thid, pthid string, options *options,
   359  	connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) {
   360  	request := &Request{
   361  		Type:  RequestMsgType,
   362  		ID:    thid,
   363  		Label: label,
   364  		Thread: &decorator.Thread{
   365  			PID: pthid,
   366  		},
   367  	}
   368  
   369  	accept, err := destination.ServiceEndpoint.Accept() // didcomm v2
   370  	if err != nil {
   371  		accept = destination.MediaTypeProfiles // didcomm v1
   372  	}
   373  
   374  	// get did document to use in exchange request
   375  	myDIDDoc, err := ctx.getMyDIDDoc(getPublicDID(options), getRouterConnections(options),
   376  		serviceTypeByMediaProfile(accept))
   377  	if err != nil {
   378  		return nil, nil, err
   379  	}
   380  
   381  	connRec.MyDID = myDIDDoc.ID
   382  
   383  	senderKey, err := recipientKeyAsDIDKey(myDIDDoc)
   384  	if err != nil {
   385  		return nil, nil, fmt.Errorf("getting recipient key: %w", err)
   386  	}
   387  
   388  	// Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   389  	requestDidDoc, err := convertPeerToSov(myDIDDoc)
   390  	if err != nil {
   391  		return nil, nil, fmt.Errorf("converting my did doc to a 'sov' doc for request message: %w", err)
   392  	}
   393  
   394  	// Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   395  	if ctx.doACAPyInterop {
   396  		request.DID = strings.TrimPrefix(myDIDDoc.ID, "did:sov:")
   397  	} else {
   398  		request.DID = myDIDDoc.ID
   399  	}
   400  
   401  	request.DocAttach, err = ctx.didDocAttachment(requestDidDoc, senderKey)
   402  	if err != nil {
   403  		return nil, nil, fmt.Errorf("creating did doc attachment for request: %w", err)
   404  	}
   405  
   406  	return func() error {
   407  		return ctx.outboundDispatcher.Send(request, senderKey, destination)
   408  	}, connRec, nil
   409  }
   410  
   411  func serviceTypeByMediaProfile(mediaTypeProfiles []string) string {
   412  	serviceType := didCommServiceType
   413  
   414  	for _, mtp := range mediaTypeProfiles {
   415  		var breakFor bool
   416  
   417  		switch mtp {
   418  		case transport.MediaTypeDIDCommV2Profile, transport.MediaTypeAIP2RFC0587Profile,
   419  			transport.MediaTypeV2EncryptedEnvelope, transport.MediaTypeV2EncryptedEnvelopeV1PlaintextPayload,
   420  			transport.MediaTypeV1EncryptedEnvelope:
   421  			serviceType = didCommV2ServiceType
   422  
   423  			breakFor = true
   424  		}
   425  
   426  		if breakFor {
   427  			break
   428  		}
   429  	}
   430  
   431  	return serviceType
   432  }
   433  
   434  // nolint:gocyclo,funlen
   435  func (ctx *context) handleInboundRequest(request *Request, options *options,
   436  	connRec *connectionstore.Record) (stateAction, *connectionstore.Record, error) {
   437  	logger.Debugf("handling request: %#v", request)
   438  
   439  	// Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   440  	if ctx.doACAPyInterop && !strings.HasPrefix(request.DID, "did") {
   441  		request.DID = "did:peer:" + request.DID
   442  	}
   443  
   444  	requestDidDoc, err := ctx.resolveDidDocFromMessage(request.DID, request.DocAttach)
   445  	if err != nil {
   446  		return nil, nil, fmt.Errorf("resolve did doc from exchange request: %w", err)
   447  	}
   448  
   449  	// get did document that will be used in exchange response
   450  	// (my did doc)
   451  	myDID := getPublicDID(options)
   452  
   453  	destination, err := service.CreateDestination(requestDidDoc)
   454  	if err != nil {
   455  		return nil, nil, err
   456  	}
   457  
   458  	var serviceType string
   459  	if len(requestDidDoc.Service) > 0 {
   460  		serviceType = didcommutil.GetServiceType(requestDidDoc.Service[0].Type)
   461  	} else {
   462  		accept, e := destination.ServiceEndpoint.Accept()
   463  		if e != nil {
   464  			accept = []string{}
   465  		}
   466  
   467  		serviceType = serviceTypeByMediaProfile(accept)
   468  	}
   469  
   470  	responseDidDoc, err := ctx.getMyDIDDoc(myDID, getRouterConnections(options), serviceType)
   471  	if err != nil {
   472  		return nil, nil, fmt.Errorf("get response did doc and connection: %w", err)
   473  	}
   474  
   475  	var senderVerKey string
   476  
   477  	if myDID != "" { // empty myDID means a new DID was just created and not exchanged yet, use did:key instead
   478  		senderVerKey, err = recipientKey(responseDidDoc)
   479  		if err != nil {
   480  			return nil, nil, fmt.Errorf("get recipient key: %w", err)
   481  		}
   482  	} else {
   483  		senderVerKey, err = recipientKeyAsDIDKey(responseDidDoc)
   484  		if err != nil {
   485  			return nil, nil, fmt.Errorf("get recipient key as did:key: %w", err)
   486  		}
   487  	}
   488  
   489  	connRec.MyDID = responseDidDoc.ID
   490  
   491  	if ctx.doACAPyInterop {
   492  		// Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   493  		responseDidDoc, err = convertPeerToSov(responseDidDoc)
   494  		if err != nil {
   495  			return nil, nil, fmt.Errorf("converting my did doc to a 'sov' doc for response message: %w", err)
   496  		}
   497  	}
   498  
   499  	response, err := ctx.prepareResponse(request, responseDidDoc)
   500  	if err != nil {
   501  		return nil, nil, fmt.Errorf("preparing response: %w", err)
   502  	}
   503  
   504  	connRec.TheirDID = request.DID
   505  	connRec.TheirLabel = request.Label
   506  
   507  	accept, err := destination.ServiceEndpoint.Accept()
   508  	if err != nil {
   509  		accept = []string{}
   510  	}
   511  
   512  	if len(accept) > 0 {
   513  		connRec.MediaTypeProfiles = accept
   514  	}
   515  
   516  	// send exchange response
   517  	return func() error {
   518  		return ctx.outboundDispatcher.Send(response, senderVerKey, destination)
   519  	}, connRec, nil
   520  }
   521  
   522  func (ctx *context) prepareResponse(request *Request, responseDidDoc *did.Doc) (*Response, error) {
   523  	// prepare the response
   524  	response := &Response{
   525  		Type: ResponseMsgType,
   526  		ID:   uuid.New().String(),
   527  		Thread: &decorator.Thread{
   528  			ID: request.ID,
   529  		},
   530  	}
   531  
   532  	if request.Thread != nil {
   533  		response.Thread.PID = request.Thread.PID
   534  	}
   535  
   536  	invitationKey, err := ctx.getVerKey(request.Thread.PID)
   537  	if err != nil {
   538  		return nil, fmt.Errorf("getting sender verkey: %w", err)
   539  	}
   540  
   541  	docAttach, err := ctx.didDocAttachment(responseDidDoc, invitationKey)
   542  	if err != nil {
   543  		return nil, err
   544  	}
   545  
   546  	// Interop: aca-py expects naked DID method-specific identifier for sov DIDs
   547  	// https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   548  	response.DID = strings.TrimPrefix(responseDidDoc.ID, "did:sov:")
   549  	response.DocAttach = docAttach
   550  
   551  	return response, nil
   552  }
   553  
   554  func (ctx *context) didDocAttachment(doc *did.Doc, myVerKey string) (*decorator.Attachment, error) {
   555  	docBytes, err := doc.SerializeInterop()
   556  	if err != nil {
   557  		return nil, fmt.Errorf("marshaling did doc: %w", err)
   558  	}
   559  
   560  	docAttach := &decorator.Attachment{
   561  		MimeType: "application/json",
   562  		Data: decorator.AttachmentData{
   563  			Base64: base64.StdEncoding.EncodeToString(docBytes),
   564  		},
   565  	}
   566  
   567  	// Interop: signing did_doc~attach has been removed from the spec, but aca-py still verifies signatures
   568  	// TODO make aca-py issue
   569  	if ctx.doACAPyInterop {
   570  		pubKeyBytes, err := ctx.resolvePublicKey(myVerKey)
   571  		if err != nil {
   572  			return nil, fmt.Errorf("failed to resolve public key: %w", err)
   573  		}
   574  
   575  		// TODO: use dynamic context KeyType
   576  		signingKID, err := jwkkid.CreateKID(pubKeyBytes, kms.ED25519Type)
   577  		if err != nil {
   578  			return nil, fmt.Errorf("failed to generate KID from public key: %w", err)
   579  		}
   580  
   581  		kh, err := ctx.kms.Get(signingKID)
   582  		if err != nil {
   583  			return nil, fmt.Errorf("failed to get key handle: %w", err)
   584  		}
   585  
   586  		err = docAttach.Data.Sign(ctx.crypto, kh, ed25519.PublicKey(pubKeyBytes), pubKeyBytes)
   587  		if err != nil {
   588  			return nil, fmt.Errorf("signing did_doc~attach: %w", err)
   589  		}
   590  	}
   591  
   592  	return docAttach, nil
   593  }
   594  
   595  func (ctx *context) resolvePublicKey(kid string) ([]byte, error) {
   596  	if strings.HasPrefix(kid, "did:key:") {
   597  		pubKeyBytes, err := fingerprint.PubKeyFromDIDKey(kid)
   598  		if err != nil {
   599  			return nil, fmt.Errorf("failed to extract pubKeyBytes from did:key [%s]: %w", kid, err)
   600  		}
   601  
   602  		return pubKeyBytes, nil
   603  	} else if strings.HasPrefix(kid, "did:") {
   604  		vkDID := strings.Split(kid, "#")[0]
   605  
   606  		pubDoc, err := ctx.vdRegistry.Resolve(vkDID)
   607  		if err != nil {
   608  			return nil, fmt.Errorf("failed to resolve public did for key ID '%s': %w", kid, err)
   609  		}
   610  
   611  		vm, ok := did.LookupPublicKey(kid, pubDoc.DIDDocument)
   612  		if !ok {
   613  			return nil, fmt.Errorf("failed to lookup public key for ID %s", kid)
   614  		}
   615  
   616  		return vm.Value, nil
   617  	}
   618  
   619  	return nil, fmt.Errorf("failed to resolve public key value from kid '%s'", kid)
   620  }
   621  
   622  func getPublicDID(options *options) string {
   623  	if options == nil {
   624  		return ""
   625  	}
   626  
   627  	return options.publicDID
   628  }
   629  
   630  func getRouterConnections(options *options) []string {
   631  	if options == nil {
   632  		return nil
   633  	}
   634  
   635  	return options.routerConnections
   636  }
   637  
   638  // returns the label given in the options, otherwise an empty string.
   639  func getLabel(options *options) string {
   640  	if options == nil {
   641  		return ""
   642  	}
   643  
   644  	return options.label
   645  }
   646  
   647  func (ctx *context) getDestination(invitation *Invitation) (*service.Destination, error) {
   648  	if invitation.DID != "" {
   649  		return service.GetDestination(invitation.DID, ctx.vdRegistry)
   650  	}
   651  
   652  	accept := ctx.mediaTypeProfiles
   653  
   654  	var dest *service.Destination
   655  
   656  	if isDIDCommV2(accept) {
   657  		dest = &service.Destination{
   658  			RecipientKeys: invitation.RecipientKeys,
   659  			ServiceEndpoint: model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{
   660  				{URI: invitation.ServiceEndpoint, Accept: accept, RoutingKeys: invitation.RoutingKeys},
   661  			}),
   662  		}
   663  	} else {
   664  		dest = &service.Destination{
   665  			RecipientKeys:     invitation.RecipientKeys,
   666  			ServiceEndpoint:   model.NewDIDCommV1Endpoint(invitation.ServiceEndpoint),
   667  			MediaTypeProfiles: accept,
   668  			RoutingKeys:       invitation.RoutingKeys,
   669  		}
   670  	}
   671  
   672  	return dest, nil
   673  }
   674  
   675  // nolint:gocyclo,funlen
   676  func (ctx *context) getMyDIDDoc(pubDID string, routerConnections []string, serviceType string) (*did.Doc, error) {
   677  	if pubDID != "" {
   678  		logger.Debugf("using public did[%s] for connection", pubDID)
   679  
   680  		docResolution, err := ctx.vdRegistry.Resolve(pubDID)
   681  		if err != nil {
   682  			return nil, fmt.Errorf("resolve public did[%s]: %w", pubDID, err)
   683  		}
   684  
   685  		err = ctx.connectionStore.SaveDIDFromDoc(docResolution.DIDDocument)
   686  		if err != nil {
   687  			return nil, err
   688  		}
   689  
   690  		return docResolution.DIDDocument, nil
   691  	}
   692  
   693  	logger.Debugf("creating new '%s' did for connection", didMethod)
   694  
   695  	var (
   696  		services   []did.Service
   697  		newService bool
   698  	)
   699  
   700  	for _, connID := range routerConnections {
   701  		// get the route configs (pass empty service endpoint, as default service endpoint added in VDR)
   702  		serviceEndpoint, routingKeys, err := mediator.GetRouterConfig(ctx.routeSvc, connID, "")
   703  		if err != nil {
   704  			return nil, fmt.Errorf("did doc - fetch router config: %w", err)
   705  		}
   706  
   707  		var svc did.Service
   708  
   709  		switch serviceType {
   710  		case didCommServiceType, legacyDIDCommServiceType:
   711  			svc = did.Service{
   712  				Type:            didCommServiceType,
   713  				ServiceEndpoint: model.NewDIDCommV1Endpoint(serviceEndpoint),
   714  				RoutingKeys:     routingKeys,
   715  			}
   716  		case didCommV2ServiceType:
   717  			svc = did.Service{
   718  				Type: didCommV2ServiceType,
   719  				ServiceEndpoint: model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{
   720  					{URI: serviceEndpoint, RoutingKeys: routingKeys},
   721  				}),
   722  			}
   723  		}
   724  
   725  		services = append(services, svc)
   726  	}
   727  
   728  	if len(services) == 0 {
   729  		newService = true
   730  
   731  		services = append(services, did.Service{Type: serviceType})
   732  	}
   733  
   734  	newDID := &did.Doc{Service: services}
   735  
   736  	err := ctx.createNewKeyAndVM(newDID)
   737  	if err != nil {
   738  		return nil, fmt.Errorf("failed to create and export public key: %w", err)
   739  	}
   740  
   741  	if newService {
   742  		switch didcommutil.GetServiceType(newDID.Service[0].Type) {
   743  		case didCommServiceType, "IndyAgent":
   744  			recKey, _ := fingerprint.CreateDIDKey(newDID.VerificationMethod[0].Value)
   745  			newDID.Service[0].RecipientKeys = []string{recKey}
   746  		case didCommV2ServiceType:
   747  			var recKeys []string
   748  
   749  			for _, r := range newDID.KeyAgreement {
   750  				recKeys = append(recKeys, r.VerificationMethod.ID)
   751  			}
   752  
   753  			newDID.Service[0].RecipientKeys = recKeys
   754  
   755  		default:
   756  			return nil, fmt.Errorf("getMyDIDDoc: invalid DID Doc service type: '%v'", newDID.Service[0].Type)
   757  		}
   758  	}
   759  
   760  	// by default use peer did
   761  	docResolution, err := ctx.vdRegistry.Create(didMethod, newDID)
   762  	if err != nil {
   763  		return nil, fmt.Errorf("create %s did: %w", didMethod, err)
   764  	}
   765  
   766  	if len(routerConnections) != 0 {
   767  		err = ctx.addRouterKeys(docResolution.DIDDocument, routerConnections)
   768  		if err != nil {
   769  			return nil, err
   770  		}
   771  	}
   772  
   773  	err = ctx.connectionStore.SaveDIDFromDoc(docResolution.DIDDocument)
   774  	if err != nil {
   775  		return nil, err
   776  	}
   777  
   778  	return docResolution.DIDDocument, nil
   779  }
   780  
   781  func (ctx *context) addRouterKeys(doc *did.Doc, routerConnections []string) error {
   782  	// try DIDComm V2 and use it if found, else use default DIDComm v1 bloc.
   783  	_, ok := did.LookupService(doc, didCommV2ServiceType)
   784  	if ok {
   785  		// use KeyAgreement.ID as recKey for DIDComm V2
   786  		for _, ka := range doc.KeyAgreement {
   787  			for _, connID := range routerConnections {
   788  				// TODO https://github.com/hyperledger/aries-framework-go/issues/1105 Support to Add multiple
   789  				//  recKeys to the Router. (DIDComm V2 uses list of keyAgreements as router keys here, double check
   790  				//  if this issue can be closed).
   791  				kaID := ka.VerificationMethod.ID
   792  				if strings.HasPrefix(kaID, "#") {
   793  					kaID = doc.ID + kaID
   794  				}
   795  
   796  				if err := mediator.AddKeyToRouter(ctx.routeSvc, connID, kaID); err != nil {
   797  					return fmt.Errorf("did doc - add key to the router: %w", err)
   798  				}
   799  			}
   800  		}
   801  
   802  		return nil
   803  	}
   804  
   805  	svc, ok := did.LookupService(doc, didCommServiceType)
   806  	if ok {
   807  		for _, recKey := range svc.RecipientKeys {
   808  			for _, connID := range routerConnections {
   809  				// TODO https://github.com/hyperledger/aries-framework-go/issues/1105 Support to Add multiple
   810  				//  recKeys to the Router
   811  				if err := mediator.AddKeyToRouter(ctx.routeSvc, connID, recKey); err != nil {
   812  					return fmt.Errorf("did doc - add key to the router: %w", err)
   813  				}
   814  			}
   815  		}
   816  	}
   817  
   818  	return nil
   819  }
   820  
   821  func (ctx *context) isPrivateDIDMethod(method string) bool {
   822  	// todo: find better solution to forcing test dids to be treated as private dids
   823  	if method == "local" || method == "test" {
   824  		return true
   825  	}
   826  
   827  	// Interop: treat sov as a peer did: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   828  	return method == "peer" || (ctx.doACAPyInterop && method == "sov")
   829  }
   830  
   831  // nolint:gocyclo
   832  func (ctx *context) resolveDidDocFromMessage(didValue string, attachment *decorator.Attachment) (*did.Doc, error) {
   833  	parsedDID, err := did.Parse(didValue)
   834  	// Interop: aca-py dids missing schema:method:, ignore error and skip checking if it's a public did
   835  	// aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   836  	if err != nil && !ctx.doACAPyInterop {
   837  		return nil, fmt.Errorf("failed to parse did: %w", err)
   838  	}
   839  
   840  	if err == nil && !ctx.isPrivateDIDMethod(parsedDID.Method) {
   841  		docResolution, e := ctx.vdRegistry.Resolve(didValue)
   842  		if e != nil {
   843  			return nil, fmt.Errorf("failed to resolve public did %s: %w", didValue, e)
   844  		}
   845  
   846  		return docResolution.DIDDocument, nil
   847  	}
   848  
   849  	if attachment == nil {
   850  		return nil, fmt.Errorf("missing did_doc~attach")
   851  	}
   852  
   853  	docData, err := attachment.Data.Fetch()
   854  	if err != nil {
   855  		return nil, fmt.Errorf("failed to parse base64 attachment data: %w", err)
   856  	}
   857  
   858  	didDoc, err := did.ParseDocument(docData)
   859  	if err != nil {
   860  		logger.Errorf("failed to parse doc bytes: '%s'", string(docData))
   861  
   862  		return nil, fmt.Errorf("failed to parse did document: %w", err)
   863  	}
   864  
   865  	// Interop: accommodate aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   866  	var method string
   867  
   868  	if parsedDID != nil && parsedDID.Method != "sov" {
   869  		method = parsedDID.Method
   870  	} else {
   871  		method = "peer"
   872  	}
   873  
   874  	// Interop: part of above issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   875  	if ctx.doACAPyInterop {
   876  		didDoc.ID = didValue
   877  	}
   878  
   879  	// store provided did document
   880  	_, err = ctx.vdRegistry.Create(method, didDoc, vdrapi.WithOption("store", true))
   881  	if err != nil {
   882  		return nil, fmt.Errorf("failed to store provided did document: %w", err)
   883  	}
   884  
   885  	return didDoc, nil
   886  }
   887  
   888  func (ctx *context) handleInboundResponse(response *Response) (stateAction, *connectionstore.Record, error) {
   889  	nsThID, err := connectionstore.CreateNamespaceKey(myNSPrefix, response.Thread.ID)
   890  	if err != nil {
   891  		return nil, nil, err
   892  	}
   893  
   894  	connRecord, err := ctx.connectionRecorder.GetConnectionRecordByNSThreadID(nsThID)
   895  	if err != nil {
   896  		return nil, nil, fmt.Errorf("get connection record: %w", err)
   897  	}
   898  
   899  	// Interop: aca-py issue https://github.com/hyperledger/aries-cloudagent-python/issues/1048
   900  	if ctx.doACAPyInterop && !strings.HasPrefix(response.DID, "did") {
   901  		response.DID = "did:peer:" + response.DID
   902  	}
   903  
   904  	connRecord.TheirDID = response.DID
   905  
   906  	responseDidDoc, err := ctx.resolveDidDocFromMessage(response.DID, response.DocAttach)
   907  	if err != nil {
   908  		return nil, nil, fmt.Errorf("resolve response did doc: %w", err)
   909  	}
   910  
   911  	destination, err := service.CreateDestination(responseDidDoc)
   912  	if err != nil {
   913  		return nil, nil, fmt.Errorf("prepare destination from response did doc: %w", err)
   914  	}
   915  
   916  	docResolution, err := ctx.vdRegistry.Resolve(connRecord.MyDID)
   917  	if err != nil {
   918  		return nil, nil, fmt.Errorf("fetching did document: %w", err)
   919  	}
   920  
   921  	recKey, err := recipientKey(docResolution.DIDDocument)
   922  	if err != nil {
   923  		return nil, nil, fmt.Errorf("handle inbound response: %w", err)
   924  	}
   925  
   926  	completeMsg := &Complete{
   927  		Type: CompleteMsgType,
   928  		ID:   uuid.New().String(),
   929  		Thread: &decorator.Thread{
   930  			ID:  response.Thread.ID,
   931  			PID: connRecord.ParentThreadID,
   932  		},
   933  	}
   934  
   935  	return func() error {
   936  		return ctx.outboundDispatcher.Send(completeMsg, recKey, destination)
   937  	}, connRecord, nil
   938  }
   939  
   940  func (ctx *context) getVerKey(invitationID string) (string, error) {
   941  	pubKey, err := ctx.getVerKeyFromOOBInvitation(invitationID)
   942  	if err != nil && !errors.Is(err, errVerKeyNotFound) {
   943  		return "", fmt.Errorf("failed to get my verkey from oob invitation: %w", err)
   944  	}
   945  
   946  	if err == nil {
   947  		return pubKey, nil
   948  	}
   949  
   950  	var invitation Invitation
   951  	if isDID(invitationID) {
   952  		invitation = Invitation{ID: invitationID, DID: invitationID}
   953  	} else {
   954  		err = ctx.connectionRecorder.GetInvitation(invitationID, &invitation)
   955  		if err != nil {
   956  			return "", fmt.Errorf("get invitation for signature [invitationID=%s]: %w", invitationID, err)
   957  		}
   958  	}
   959  
   960  	invPubKey, err := ctx.getInvitationRecipientKey(&invitation)
   961  	if err != nil {
   962  		return "", fmt.Errorf("get invitation recipient key: %w", err)
   963  	}
   964  
   965  	return invPubKey, nil
   966  }
   967  
   968  func (ctx *context) getInvitationRecipientKey(invitation *Invitation) (string, error) {
   969  	if invitation.DID != "" {
   970  		docResolution, err := ctx.vdRegistry.Resolve(invitation.DID)
   971  		if err != nil {
   972  			return "", fmt.Errorf("get invitation recipient key: %w", err)
   973  		}
   974  
   975  		recKey, err := recipientKey(docResolution.DIDDocument)
   976  		if err != nil {
   977  			return "", fmt.Errorf("getInvitationRecipientKey: %w", err)
   978  		}
   979  
   980  		return recKey, nil
   981  	}
   982  
   983  	return invitation.RecipientKeys[0], nil
   984  }
   985  
   986  func (ctx *context) getVerKeyFromOOBInvitation(invitationID string) (string, error) {
   987  	logger.Debugf("invitationID=%s", invitationID)
   988  
   989  	var invitation OOBInvitation
   990  
   991  	err := ctx.connectionRecorder.GetInvitation(invitationID, &invitation)
   992  	if errors.Is(err, storage.ErrDataNotFound) {
   993  		return "", errVerKeyNotFound
   994  	}
   995  
   996  	if err != nil {
   997  		return "", fmt.Errorf("failed to load oob invitation: %w", err)
   998  	}
   999  
  1000  	if invitation.Type != oobMsgType {
  1001  		return "", errVerKeyNotFound
  1002  	}
  1003  
  1004  	unmarshalServiceEndpointInOOBTarget(&invitation)
  1005  
  1006  	pubKey, err := ctx.resolveVerKey(&invitation)
  1007  	if err != nil {
  1008  		return "", fmt.Errorf("failed to get my verkey: %w", err)
  1009  	}
  1010  
  1011  	return pubKey, nil
  1012  }
  1013  
  1014  //nolint:nestif
  1015  func unmarshalServiceEndpointInOOBTarget(invitation *OOBInvitation) {
  1016  	// for DIDCommV1, oobInvitation's target serviceEndpoint is a string, transform it to model.Endpoint map equivalent
  1017  	// for a successful service decode().
  1018  	// for DIDCommV2, transform the target from map[string]interface{} to model.Endpoint
  1019  	if targetMap, ok := invitation.Target.(map[string]interface{}); ok {
  1020  		if se, ok := targetMap["serviceEndpoint"]; ok {
  1021  			seStr, ok := se.(string)
  1022  			if ok {
  1023  				targetMap["serviceEndpoint"] = model.NewDIDCommV1Endpoint(seStr)
  1024  			} else if seMap, ok := se.(map[string]interface{}); ok {
  1025  				seStr, ok = seMap["uri"].(string)
  1026  				if !ok {
  1027  					seStr = ""
  1028  				}
  1029  
  1030  				accept, ok := seMap["accept"].([]string)
  1031  				if !ok {
  1032  					accept = []string{}
  1033  				}
  1034  
  1035  				routingKeys, ok := seMap["routingKeys"].([]string)
  1036  				if !ok {
  1037  					routingKeys = []string{}
  1038  				}
  1039  
  1040  				targetMap["serviceEndpoint"] = model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{
  1041  					{URI: seStr, Accept: accept, RoutingKeys: routingKeys},
  1042  				})
  1043  			}
  1044  		}
  1045  	}
  1046  }
  1047  
  1048  // nolint:gocyclo,funlen
  1049  func (ctx *context) getServiceBlock(i *OOBInvitation) (*did.Service, error) {
  1050  	logger.Debugf("extracting service block from oobinvitation=%+v", i)
  1051  
  1052  	var block *did.Service
  1053  
  1054  	switch svc := i.Target.(type) {
  1055  	case string:
  1056  		docResolution, err := ctx.vdRegistry.Resolve(svc)
  1057  		if err != nil {
  1058  			return nil, fmt.Errorf("failed to resolve service=%s : %w", svc, err)
  1059  		}
  1060  
  1061  		s, found := did.LookupService(docResolution.DIDDocument, didCommV2ServiceType)
  1062  		if found {
  1063  			// s.recipientKeys are keyAgreement[].VerificationMethod.ID for didComm V2. They are not officially part of
  1064  			// the service bloc.
  1065  			block = s
  1066  
  1067  			break
  1068  		}
  1069  
  1070  		s, found = did.LookupService(docResolution.DIDDocument, didCommServiceType)
  1071  		if !found {
  1072  			if ctx.doACAPyInterop {
  1073  				s, err = interopSovService(docResolution.DIDDocument)
  1074  				if err != nil {
  1075  					return nil, fmt.Errorf("failed to get interop doc service: %w", err)
  1076  				}
  1077  			} else {
  1078  				return nil, fmt.Errorf(
  1079  					"no valid service block found on OOB invitation DID=%s with serviceType=%s",
  1080  					svc, didCommServiceType)
  1081  			}
  1082  		}
  1083  
  1084  		block = s
  1085  	case *did.Service:
  1086  		block = svc
  1087  	case map[string]interface{}:
  1088  		var s did.Service
  1089  
  1090  		decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &s})
  1091  		if err != nil {
  1092  			return nil, fmt.Errorf("failed to initialize decoder : %w", err)
  1093  		}
  1094  
  1095  		err = decoder.Decode(svc)
  1096  		if err != nil {
  1097  			// TODO this error check depend on mapstructure decoding 'ServiceEndpoint' section of service.
  1098  			// TODO Find a  better way to build it.
  1099  			// for DIDCommV2, decoder.Decode(svc) doesn't support serviceEndpoint as []interface{} representing an array
  1100  			// for model.Endpoint. Manually build the endpoint here in this case.
  1101  			if strings.Contains(err.Error(), "'serviceEndpoint' expected a map, got 'slice'") {
  1102  				extractDIDCommV2EndpointIntoService(svc, &s)
  1103  			} else {
  1104  				return nil, fmt.Errorf("failed to decode service block : %w", err)
  1105  			}
  1106  		}
  1107  
  1108  		block = &s
  1109  	default:
  1110  		return nil, fmt.Errorf("unsupported target type: %+v", svc)
  1111  	}
  1112  
  1113  	//nolint:nestif
  1114  	if len(i.MediaTypeProfiles) > 0 {
  1115  		// marshal/unmarshal to "clone" service block
  1116  		blockBytes, err := json.Marshal(block)
  1117  		if err != nil {
  1118  			return nil, fmt.Errorf("service block marhsal error: %w", err)
  1119  		}
  1120  
  1121  		block = &did.Service{}
  1122  
  1123  		err = json.Unmarshal(blockBytes, block)
  1124  		if err != nil {
  1125  			return nil, fmt.Errorf("service block unmarhsal error: %w", err)
  1126  		}
  1127  
  1128  		// updating Accept header requires a cloned service block to avoid Data Race errors.
  1129  		// RFC0587: In case the accept property is set in both the DID service block and the out-of-band message,
  1130  		// the out-of-band property takes precedence.
  1131  		if isDIDCommV2(i.MediaTypeProfiles) {
  1132  			block.Type = didCommV2ServiceType
  1133  
  1134  			uri, err := block.ServiceEndpoint.URI()
  1135  			if err != nil {
  1136  				logger.Debugf("block ServiceEndpoint URI empty for DIDcomm V2, skipping it.")
  1137  			}
  1138  
  1139  			routingKeys, err := block.ServiceEndpoint.RoutingKeys()
  1140  			if err != nil {
  1141  				logger.Debugf("block ServiceEndpoint RoutingKeys empty for DIDcomm V2, skipping these.")
  1142  			}
  1143  
  1144  			block.ServiceEndpoint = model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{
  1145  				{URI: uri, Accept: i.MediaTypeProfiles, RoutingKeys: routingKeys},
  1146  			})
  1147  		} else {
  1148  			block.Type = didCommServiceType
  1149  			block.Accept = i.MediaTypeProfiles
  1150  		}
  1151  	}
  1152  
  1153  	logger.Debugf("extracted service block=%+v", block)
  1154  
  1155  	return block, nil
  1156  }
  1157  
  1158  //nolint:gocognit,gocyclo,nestif
  1159  func extractDIDCommV2EndpointIntoService(svc map[string]interface{}, s *did.Service) {
  1160  	if svcEndpointModel, ok := svc["serviceEndpoint"]; ok {
  1161  		if svcEndpointArr, ok := svcEndpointModel.([]interface{}); ok && len(svcEndpointArr) > 0 {
  1162  			if svcEndpointMap, ok := svcEndpointArr[0].(map[string]interface{}); ok {
  1163  				var (
  1164  					uri         string
  1165  					accept      []string
  1166  					routingKeys []string
  1167  				)
  1168  
  1169  				if uriVal, ok := svcEndpointMap["uri"]; ok {
  1170  					if uri, ok = uriVal.(string); !ok {
  1171  						uri = ""
  1172  					}
  1173  				}
  1174  
  1175  				if acceptVal, ok := svcEndpointMap["accept"]; ok {
  1176  					if acceptArr, ok := acceptVal.([]interface{}); ok {
  1177  						for _, a := range acceptArr {
  1178  							accept = append(accept, a.(string))
  1179  						}
  1180  					}
  1181  				}
  1182  
  1183  				if routingKeysVal, ok := svcEndpointMap["routingKeys"]; ok {
  1184  					if routingKeysArr, ok := routingKeysVal.([]interface{}); ok {
  1185  						for _, r := range routingKeysArr {
  1186  							routingKeys = append(routingKeys, r.(string))
  1187  						}
  1188  					}
  1189  				}
  1190  
  1191  				s.ServiceEndpoint = model.NewDIDCommV2Endpoint([]model.DIDCommV2Endpoint{
  1192  					{URI: uri, Accept: accept, RoutingKeys: routingKeys},
  1193  				})
  1194  			}
  1195  		}
  1196  	}
  1197  }
  1198  
  1199  func isDIDCommV2(mediaTypeProfiles []string) bool {
  1200  	for _, mtp := range mediaTypeProfiles {
  1201  		switch mtp {
  1202  		case transport.MediaTypeDIDCommV2Profile, transport.MediaTypeAIP2RFC0587Profile:
  1203  			return true
  1204  		}
  1205  	}
  1206  
  1207  	return false
  1208  }
  1209  
  1210  func interopSovService(doc *did.Doc) (*did.Service, error) {
  1211  	s, found := did.LookupService(doc, "endpoint")
  1212  	if !found {
  1213  		return nil, fmt.Errorf("no valid service block found on OOB invitation DID=%s with serviceType=%s",
  1214  			doc.ID, "endpoint")
  1215  	}
  1216  
  1217  	if len(s.RecipientKeys) == 0 {
  1218  		for _, vm := range doc.VerificationMethod {
  1219  			didKey, _ := fingerprint.CreateDIDKey(vm.Value)
  1220  
  1221  			s.RecipientKeys = append(s.RecipientKeys, didKey)
  1222  		}
  1223  	}
  1224  
  1225  	return s, nil
  1226  }
  1227  
  1228  func (ctx *context) resolveVerKey(i *OOBInvitation) (string, error) {
  1229  	logger.Debugf("extracting verkey from oobinvitation=%+v", i)
  1230  
  1231  	svc, err := ctx.getServiceBlock(i)
  1232  	if err != nil {
  1233  		return "", fmt.Errorf("failed to get service block from oobinvitation : %w", err)
  1234  	}
  1235  
  1236  	logger.Debugf("extracted verkey=%s", svc.RecipientKeys[0])
  1237  
  1238  	// use RecipientKeys[0] (DIDComm V1)
  1239  	return svc.RecipientKeys[0], nil
  1240  }
  1241  
  1242  func isDID(str string) bool {
  1243  	const didPrefix = "did:"
  1244  	return strings.HasPrefix(str, didPrefix)
  1245  }
  1246  
  1247  // returns the did:key ID of the first element in the doc's destination RecipientKeys.
  1248  func recipientKey(doc *did.Doc) (string, error) {
  1249  	dest, err := service.CreateDestination(doc)
  1250  	if err != nil {
  1251  		return "", fmt.Errorf("failed to create destination: %w", err)
  1252  	}
  1253  
  1254  	return dest.RecipientKeys[0], nil
  1255  }
  1256  
  1257  func recipientKeyAsDIDKey(doc *did.Doc) (string, error) {
  1258  	var (
  1259  		key string
  1260  		err error
  1261  	)
  1262  
  1263  	serviceType := didcommutil.GetServiceType(doc.Service[0].Type)
  1264  
  1265  	switch serviceType {
  1266  	case vdrapi.DIDCommServiceType:
  1267  		return recipientKey(doc)
  1268  	case vdrapi.DIDCommV2ServiceType:
  1269  		// DIDComm V2 recipientKeys are KeyAgreement.ID, convert corresponding verification material to did:key since
  1270  		// recipient doesn't have the DID 'doc' yet.
  1271  		switch doc.KeyAgreement[0].VerificationMethod.Type {
  1272  		case x25519KeyAgreementKey2019:
  1273  			key, _ = fingerprint.CreateDIDKeyByCode(fingerprint.X25519PubKeyMultiCodec,
  1274  				doc.KeyAgreement[0].VerificationMethod.Value)
  1275  		case jsonWebKey2020:
  1276  			key, _, err = fingerprint.CreateDIDKeyByJwk(doc.KeyAgreement[0].VerificationMethod.JSONWebKey())
  1277  			if err != nil {
  1278  				return "", fmt.Errorf("recipientKeyAsDIDKey: unable to create did:key from JWK: %w", err)
  1279  			}
  1280  		default:
  1281  			return "", fmt.Errorf("keyAgreement type '%v' not supported", doc.KeyAgreement[0].VerificationMethod.Type)
  1282  		}
  1283  
  1284  		return key, nil
  1285  	default:
  1286  		return interopRecipientKey(doc)
  1287  	}
  1288  }