Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Added support for Ed25519Signature2020 signature type and Ed25519VerificationKey2020 #2241

Merged
9 changes: 8 additions & 1 deletion aries_cloudagent/connections/base_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
import logging
from typing import Optional, List, Sequence, Tuple, Text

from multiformats import multibase
from pydid import (
BaseDIDDocument as ResolvedDocument,
DIDCommService,
VerificationMethod,
)
import pydid
from pydid.verification_method import Ed25519VerificationKey2018, JsonWebKey2020
from pydid.verification_method import (
Ed25519VerificationKey2018,
JsonWebKey2020,
Ed25519VerificationKey2020,
)

from ..config.logging import get_logger_inst
from ..core.error import BaseError
Expand Down Expand Up @@ -294,6 +299,8 @@ async def resolve_invitation(
def _extract_key_material_in_base58_format(method: VerificationMethod) -> str:
if isinstance(method, Ed25519VerificationKey2018):
return method.material
elif isinstance(method, Ed25519VerificationKey2020):
return bytes_to_b58(multibase.decode(method.material))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this also needs an "unwrap" on the material to remove the 0xed multicodec prefix added when using the publicKeyMultibase

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually a little ambiguous in the DID Spec whether publicKeyMultibase also includes the multicodec prefix but in practice (in libraries like didcomm-python), it seems to be expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added support for both (with and without multicodec). Do you think it should be like that or should we support multibase keys only with multicodec?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the ambiguity, I think accepting either is probably a good choice for now at least.

elif isinstance(method, JsonWebKey2020):
if method.public_key_jwk.get("kty") == "OKP":
return bytes_to_b58(b64_to_bytes(method.public_key_jwk.get("x"), True))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from unittest.mock import call

from asynctest import TestCase as AsyncTestCase, mock as async_mock
from multiformats import multibase
from pydid import DIDDocument, DIDDocumentBuilder
from pydid.verification_method import Ed25519VerificationKey2018, JsonWebKey2020
from pydid.verification_method import (
Ed25519VerificationKey2018,
Ed25519VerificationKey2020,
JsonWebKey2020,
)

from .....cache.base import BaseCache
from .....cache.in_memory import InMemoryCache
Expand Down Expand Up @@ -2323,6 +2328,66 @@ async def test_fetch_connection_targets_conn_invitation_no_didcomm_services(self
with self.assertRaises(BaseConnectionManagerError):
await self.manager.fetch_connection_targets(mock_conn)

async def test_fetch_connection_targets_conn_invitation_supports_Ed25519VerificationKey2018_key_type(
self,
):
async with self.profile.session() as session:
builder = DIDDocumentBuilder("did:btcr:x705-jznz-q3nl-srs")
vmethod = builder.verification_method.add(
Ed25519VerificationKey2020,
public_key_multibase=multibase.encode(
b58_to_bytes(self.test_target_verkey), "base58btc"
),
)
builder.service.add_didcomm(
type_="IndyAgent",
service_endpoint=self.test_endpoint,
recipient_keys=[vmethod],
)
did_doc = builder.build()
self.resolver = async_mock.MagicMock()
self.resolver.get_endpoint_for_did = async_mock.CoroutineMock(
return_value=self.test_endpoint
)
self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc)
self.resolver.dereference = async_mock.CoroutineMock(
return_value=did_doc.verification_method[0]
)
self.context.injector.bind_instance(DIDResolver, self.resolver)
local_did = await session.wallet.create_local_did(
method=SOV,
key_type=ED25519,
seed=self.test_seed,
did=did_doc.id,
metadata=None,
)

conn_invite = ConnectionInvitation(
did=did_doc.id,
endpoint=self.test_endpoint,
recipient_keys=[vmethod.public_key_jwk],
routing_keys=[self.test_verkey],
label="label",
)
mock_conn = async_mock.MagicMock(
my_did=did_doc.id,
their_did=self.test_target_did,
connection_id="dummy",
their_role=ConnRecord.Role.RESPONDER.rfc23,
state=ConnRecord.State.INVITATION.rfc23,
retrieve_invitation=async_mock.CoroutineMock(return_value=conn_invite),
)

targets = await self.manager.fetch_connection_targets(mock_conn)
assert len(targets) == 1
target = targets[0]
assert target.did == mock_conn.their_did
assert target.endpoint == self.test_endpoint
assert target.label == conn_invite.label
assert target.recipient_keys == [self.test_target_verkey]
assert target.routing_keys == []
assert target.sender_key == local_did.verkey

async def test_fetch_connection_targets_conn_invitation_supported_JsonWebKey2020_key_type(
self,
):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
"""V2.0 issue-credential linked data proof credential format handler."""


from ......vc.ld_proofs.error import LinkedDataProofException
from ......vc.ld_proofs.check import get_properties_without_context
import logging

from typing import Mapping, Optional

from marshmallow import EXCLUDE, INCLUDE

from pyld import jsonld
from pyld.jsonld import JsonLdProcessor

from ......messaging.decorators.attach_decorator import AttachDecorator
from ......storage.vc_holder.base import VCHolder
from ......storage.vc_holder.vc_record import VCRecord
from ......vc.vc_ld import (
issue_vc as issue,
verify_credential,
VerifiableCredentialSchema,
LDProof,
VerifiableCredential,
)
from ......vc.ld_proofs import (
AuthenticationProofPurpose,
BbsBlsSignature2020,
CredentialIssuancePurpose,
DocumentLoader,
Ed25519Signature2018,
Ed25519Signature2020,
LinkedDataProof,
ProofPurpose,
WalletKeyPair,
)
from ......vc.ld_proofs.constants import SECURITY_CONTEXT_BBS_URL
from ......wallet.base import BaseWallet, DIDInfo
from ......wallet.default_verification_key_strategy import (
BaseVerificationKeyStrategy,
from ......vc.ld_proofs.check import get_properties_without_context
from ......vc.ld_proofs.constants import (
SECURITY_CONTEXT_BBS_URL,
SECURITY_CONTEXT_ED25519_2020_URL,
)
from ......vc.ld_proofs.error import LinkedDataProofException
from ......vc.vc_ld import LDProof, VerifiableCredential, VerifiableCredentialSchema
from ......vc.vc_ld import issue_vc as issue
from ......vc.vc_ld import verify_credential
from ......wallet.base import BaseWallet, DIDInfo
from ......wallet.default_verification_key_strategy import BaseVerificationKeyStrategy
from ......wallet.error import WalletNotFoundError
from ......wallet.key_type import BLS12381G2, ED25519

from ...message_types import (
ATTACHMENT_FORMAT,
CRED_20_ISSUE,
Expand All @@ -54,20 +49,20 @@
from ...messages.cred_request import V20CredRequest
from ...models.cred_ex_record import V20CredExRecord
from ...models.detail.ld_proof import V20CredExRecordLDProof

from ..handler import CredFormatAttachment, V20CredFormatError, V20CredFormatHandler

from .models.cred_detail import LDProofVCDetailSchema
from .models.cred_detail import LDProofVCDetail
from .models.cred_detail import LDProofVCDetail, LDProofVCDetailSchema

LOGGER = logging.getLogger(__name__)

SUPPORTED_ISSUANCE_PROOF_PURPOSES = {
CredentialIssuancePurpose.term,
AuthenticationProofPurpose.term,
}
SUPPORTED_ISSUANCE_SUITES = {Ed25519Signature2018}
SIGNATURE_SUITE_KEY_TYPE_MAPPING = {Ed25519Signature2018: ED25519}
SUPPORTED_ISSUANCE_SUITES = {Ed25519Signature2018, Ed25519Signature2020}
SIGNATURE_SUITE_KEY_TYPE_MAPPING = {
Ed25519Signature2018: ED25519,
Ed25519Signature2020: ED25519,
}


# We only want to add bbs suites to supported if the module is installed
Expand All @@ -81,8 +76,14 @@
}


KEY_TYPE_SIGNATURE_SUITE_MAPPING = {
key_type: suite for suite, key_type in SIGNATURE_SUITE_KEY_TYPE_MAPPING.items()
# key_type -> set of signature types mappings
KEY_TYPE_SIGNATURE_TYPE_MAPPING = {
key_type: {
suite.signature_type
for suite, kt in SIGNATURE_SUITE_KEY_TYPE_MAPPING.items()
if kt == key_type
}
for key_type in SIGNATURE_SUITE_KEY_TYPE_MAPPING.values()
}


Expand Down Expand Up @@ -214,13 +215,11 @@ async def _assert_can_issue_with_id_and_proof_type(

# Raise error if we cannot issue a credential with this proof type
# using this DID from
did_proof_type = KEY_TYPE_SIGNATURE_SUITE_MAPPING[
did.key_type
].signature_type
if proof_type != did_proof_type:
did_proof_types = KEY_TYPE_SIGNATURE_TYPE_MAPPING[did.key_type]
if proof_type not in did_proof_types:
raise V20CredFormatError(
f"Unable to issue credential with issuer id {issuer_id} and proof "
f"type {proof_type}. DID only supports proof type {did_proof_type}"
f"type {proof_type}. DID only supports proof types {did_proof_types}"
)

except WalletNotFoundError:
Expand Down Expand Up @@ -367,6 +366,12 @@ async def _prepare_detail(
and SECURITY_CONTEXT_BBS_URL not in detail.credential.context_urls
):
detail.credential.add_context(SECURITY_CONTEXT_BBS_URL)
# Add ED25519-2020 context if not present yet
elif (
detail.options.proof_type == Ed25519Signature2020.signature_type
and SECURITY_CONTEXT_ED25519_2020_URL not in detail.credential.context_urls
):
detail.credential.add_context(SECURITY_CONTEXT_ED25519_2020_URL)

# add holder_did as credentialSubject.id (if provided)
if holder_did and holder_did.startswith("did:key"):
Expand Down
Loading