From c53d5ed4606b3d6e74469c27295d61421a168edd Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Fri, 23 Feb 2024 10:51:19 -0800 Subject: [PATCH 01/17] Fix connection reuse handling in demo Signed-off-by: Ian Costanzo --- demo/runners/agent_container.py | 38 +++++++++++++++++++++++++++------ demo/runners/faber.py | 8 ++++++- demo/runners/support/agent.py | 8 ++++++- docs/demo/ReusingAConnection.md | 14 +++++++++++- 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index b2db5ed138..78c89ab62c 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -61,6 +61,8 @@ def __init__( log_file: str = None, log_config: str = None, log_level: str = None, + reuse_connections: bool = False, + public_did_connections: bool = False, extra_args: List[str] = [], **kwargs, ): @@ -90,6 +92,8 @@ def __init__( log_file=log_file, log_config=log_config, log_level=log_level, + reuse_connections=reuse_connections, + public_did_connections=public_did_connections, **kwargs, ) self.connection_id = None @@ -117,10 +121,13 @@ async def handle_out_of_band(self, message): async def handle_connection_reuse(self, message): # we are reusing an existing connection, set our status to the existing connection - if not self._connection_ready.done(): - self.connection_id = message["connection_id"] - self.log("Connected") - self._connection_ready.set_result(True) + if self._connection_ready is not None: + if not self._connection_ready.done(): + self.connection_id = message["connection_id"] + self.log("Connected") + self._connection_ready.set_result(True) + else: + self.log("Connected on existing connection") async def handle_connection_reuse_accepted(self, message): # we are reusing an existing connection, set our status to the existing connection @@ -144,7 +151,8 @@ async def handle_connections(self, message): if (not self.connection_id) and message["rfc23_state"] == "invitation-received": self.connection_id = conn_id - if conn_id == self.connection_id: + print(conn_id, self.connection_id, self.reuse_connections) + if conn_id == self.connection_id or self.reuse_connections: # inviter or invitee: if message["rfc23_state"] in ["completed", "response-sent"]: if not self._connection_ready.done(): @@ -597,6 +605,7 @@ async def generate_invitation( auto_accept: bool = True, display_qr: bool = False, reuse_connections: bool = False, + public_did_connections: bool = False, wait: bool = False, ): self._connection_ready = asyncio.Future() @@ -609,6 +618,7 @@ async def generate_invitation( use_did_exchange, auto_accept=auto_accept, reuse_connections=reuse_connections, + public_did_connections=public_did_connections, ) if display_qr: @@ -708,6 +718,7 @@ def __init__( arg_file: str = None, endorser_role: str = None, reuse_connections: bool = False, + public_did_connections: bool = False, taa_accept: bool = False, anoncreds_legacy_revocation: str = None, log_file: str = None, @@ -747,6 +758,7 @@ def __init__( self.cred_type = CRED_FORMAT_INDY self.reuse_connections = reuse_connections + self.public_did_connections = public_did_connections self.exchange_tracing = False # local agent(s) @@ -1140,6 +1152,7 @@ async def generate_invitation( auto_accept: bool = True, display_qr: bool = False, reuse_connections: bool = False, + public_did_connections: bool = False, wait: bool = False, ): return await self.agent.generate_invitation( @@ -1147,6 +1160,7 @@ async def generate_invitation( auto_accept=auto_accept, display_qr=display_qr, reuse_connections=reuse_connections, + public_did_connections=public_did_connections, wait=wait, ) @@ -1347,7 +1361,15 @@ def arg_parser(ident: str = None, port: int = 8020): "--reuse-connections", action="store_true", help=( - "Reuse connections by using Faber public key in the invite. " + "Reuse connections by generating a reusable invitation. " + "Only applicable for AIP 2.0 (OOB) connections." + ), + ) + parser.add_argument( + "--public-did-connections", + action="store_true", + help=( + "Use Faber public key in the invite. " "Only applicable for AIP 2.0 (OOB) connections." ), ) @@ -1476,6 +1498,9 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non reuse_connections = "reuse_connections" in args and args.reuse_connections if reuse_connections and aip != 20: raise Exception("Can only specify `--reuse-connections` with AIP 2.0") + public_did_connections = "public_did_connections" in args and args.public_did_connections + if public_did_connections and aip != 20: + raise Exception("Can only specify `--public-did-connections` with AIP 2.0") anoncreds_legacy_revocation = None if "anoncreds_legacy_revocation" in args and args.anoncreds_legacy_revocation: @@ -1501,6 +1526,7 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non aip=aip, endorser_role=args.endorser_role, reuse_connections=reuse_connections, + public_did_connections=public_did_connections, taa_accept=args.taa_accept, anoncreds_legacy_revocation=anoncreds_legacy_revocation, log_file=log_file, diff --git a/demo/runners/faber.py b/demo/runners/faber.py index d8b9e4347f..a811f0ae66 100644 --- a/demo/runners/faber.py +++ b/demo/runners/faber.py @@ -422,6 +422,8 @@ async def main(args): log_file=faber_agent.log_file, log_config=faber_agent.log_config, log_level=faber_agent.log_level, + reuse_connections=faber_agent.reuse_connections, + public_did_connections=faber_agent.public_did_connections, extra_args=extra_args, ) @@ -453,7 +455,10 @@ async def main(args): # generate an invitation for Alice await faber_agent.generate_invitation( - display_qr=True, reuse_connections=faber_agent.reuse_connections, wait=True + display_qr=True, + reuse_connections=faber_agent.reuse_connections, + public_did_connections=faber_agent.public_did_connections, + wait=True, ) exchange_tracing = False @@ -721,6 +726,7 @@ async def main(args): await faber_agent.generate_invitation( display_qr=True, reuse_connections=faber_agent.reuse_connections, + public_did_connections=faber_agent.public_did_connections, wait=True, ) diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index a114ef00a3..9646776f57 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -145,6 +145,8 @@ def __init__( log_file: str = None, log_config: str = None, log_level: str = None, + reuse_connections: bool = False, + public_did_connections: bool = False, **params, ): self.ident = ident @@ -179,6 +181,8 @@ def __init__( self.log_file = log_file self.log_config = log_config self.log_level = log_level + self.reuse_connections = reuse_connections + self.public_did_connections = public_did_connections self.admin_url = f"http://{self.internal_host}:{admin_port}" if AGENT_ENDPOINT: @@ -1446,16 +1450,18 @@ async def get_invite( use_did_exchange: bool, auto_accept: bool = True, reuse_connections: bool = False, + public_did_connections: bool = False, ): self.connection_id = None if use_did_exchange: # TODO can mediation be used with DID exchange connections? invi_params = { "auto_accept": json.dumps(auto_accept), + "multi_use": json.dumps(reuse_connections), } payload = { "handshake_protocols": ["rfc23"], - "use_public_did": reuse_connections, + "use_public_did": public_did_connections, } if self.mediation: payload["mediation_id"] = self.mediator_request_id diff --git a/docs/demo/ReusingAConnection.md b/docs/demo/ReusingAConnection.md index a28ba88c9b..cab8cfe50e 100644 --- a/docs/demo/ReusingAConnection.md +++ b/docs/demo/ReusingAConnection.md @@ -100,7 +100,7 @@ instruction up to the point where you are about to start the Faber and Alice age [Alice Faber Demo]: ./README.md 1. On a command line, run Faber with these parameters: `./run_demo faber - --reuse-connection --events`. + --reuse-connection --public-did-connections --events`. 2. On a second command line, run Alice as normal, perhaps with the `events` option: `./run_demo alice --events` 3. Copy the invitation from the Faber terminal and paste it into the Alice @@ -134,6 +134,18 @@ use any *resolvable* (not inline) DID, including DID Peer types 2 or 4 DIDs, as long as the DID is the same in every invitation. It is the fact that the DID is always the same that tells the invitee that they can reuse an existing connection. +For example, to run faber with connection reuse using a non-public DID: + +``` +./run_demo faber --reuse-connection --events +``` + +To run faber using a `did_peer` and reusable connections: + +``` +DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connection --events +``` + Note that the invitation does **NOT** have to be a multi-use invitation for reuse to be useful, as long as the other requirements (at the top of this document) are met. From 32230967a2b76425117af2e381366bf8b19bfca1 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Tue, 27 Feb 2024 10:04:18 -0800 Subject: [PATCH 02/17] A couple of updates Signed-off-by: Ian Costanzo --- demo/features/0160-connection.feature | 13 +++++++------ demo/runners/agent_container.py | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/demo/features/0160-connection.feature b/demo/features/0160-connection.feature index 08f5a0c55f..1748a5ec1f 100644 --- a/demo/features/0160-connection.feature +++ b/demo/features/0160-connection.feature @@ -14,9 +14,10 @@ Feature: RFC 0160 Aries agent connection functions @GHA @UnqualifiedDids Examples: | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | - | --public-did --did-exchange | --emit-did-peer-2 | --did-exchange |--emit-did-peer-2 | - | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange |--emit-did-peer-4 | - | --public-did --did-exchange | --emit-did-peer-2 | --did-exchange |--emit-did-peer-4 | - | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange |--emit-did-peer-2 | - | --public-did --did-exchange --reuse-connections | --emit-did-peer-2 | --did-exchange |--emit-did-peer-4 | - | --public-did --did-exchange --reuse-connections | --emit-did-peer-4 | --did-exchange |--emit-did-peer-2 | + #| --public-did --did-exchange | --emit-did-peer-2 | --did-exchange |--emit-did-peer-2 | + #| --public-did --did-exchange | --emit-did-peer-4 | --did-exchange |--emit-did-peer-4 | + #| --public-did --did-exchange | --emit-did-peer-2 | --did-exchange |--emit-did-peer-4 | + #| --public-did --did-exchange | --emit-did-peer-4 | --did-exchange |--emit-did-peer-2 | + #| --public-did --did-exchange --reuse-connections | --emit-did-peer-2 | --did-exchange |--emit-did-peer-4 | + #| --public-did --did-exchange --reuse-connections | --emit-did-peer-4 | --did-exchange |--emit-did-peer-2 | + | --reuse-connections | --emit-did-peer-2 | | | diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index 78c89ab62c..3d05e4cc24 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -1498,7 +1498,9 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non reuse_connections = "reuse_connections" in args and args.reuse_connections if reuse_connections and aip != 20: raise Exception("Can only specify `--reuse-connections` with AIP 2.0") - public_did_connections = "public_did_connections" in args and args.public_did_connections + public_did_connections = ( + "public_did_connections" in args and args.public_did_connections + ) if public_did_connections and aip != 20: raise Exception("Can only specify `--public-did-connections` with AIP 2.0") From 04e93a2fb9581a4f99db5fd23d8ec4556019ebbf Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Tue, 27 Feb 2024 11:51:04 -0800 Subject: [PATCH 03/17] Add did:peer support for OOB create_invitation() Signed-off-by: Ian Costanzo --- .../protocols/out_of_band/v1_0/manager.py | 73 +++++++++++++++++++ .../protocols/out_of_band/v1_0/routes.py | 11 +++ .../out_of_band/v1_0/tests/test_routes.py | 4 + aries_cloudagent/wallet/did_method.py | 1 + 4 files changed, 89 insertions(+) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 8ff3573ecd..5cf7e4d4b9 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -81,6 +81,8 @@ async def create_invitation( my_endpoint: str = None, auto_accept: bool = None, public: bool = False, + did_peer_2: bool = False, + did_peer_4: bool = False, hs_protos: Sequence[HSProto] = None, multi_use: bool = False, alias: str = None, @@ -279,6 +281,77 @@ async def create_invitation( routing_keys=[], ).serialize() + elif did_peer_4 or did_peer_2: + mediation_records = [mediation_record] if mediation_record else [] + + if my_endpoint: + my_endpoints = [my_endpoint] + else: + my_endpoints = [] + default_endpoint = self.profile.settings.get("default_endpoint") + if default_endpoint: + my_endpoints.append(default_endpoint) + my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) + + my_info = None + if did_peer_4: + my_info = await self.create_did_peer_4(my_endpoints, mediation_records) + conn_rec.my_did = my_info.did + else: + my_info = await self.create_did_peer_2(my_endpoints, mediation_records) + conn_rec.my_did = my_info.did + + invi_msg = InvitationMessage( # create invitation message + _id=invitation_message_id, + label=my_label or self.profile.settings.get("default_label"), + handshake_protocols=handshake_protocols, + requests_attach=message_attachments, + services=[my_info.did], + accept=service_accept if protocol_version != "1.0" else None, + version=protocol_version or DEFAULT_VERSION, + image_url=image_url, + ) + + our_recipient_key = my_info.did.verkey + + # Only create connection record if hanshake_protocols is defined + if handshake_protocols: + invitation_mode = ( + ConnRecord.INVITATION_MODE_MULTI + if multi_use + else ConnRecord.INVITATION_MODE_ONCE + ) + conn_rec = ConnRecord( # create connection record + invitation_key=my_info.did.verkey, + invitation_msg_id=invi_msg._id, + invitation_mode=invitation_mode, + their_role=ConnRecord.Role.REQUESTER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + accept=( + ConnRecord.ACCEPT_AUTO + if auto_accept + else ConnRecord.ACCEPT_MANUAL + ), + alias=alias, + connection_protocol=connection_protocol, + ) + + async with self.profile.session() as session: + await conn_rec.save(session, reason="Created new invitation") + await conn_rec.attach_invitation(session, invi_msg) + + await conn_rec.attach_invitation(session, invi_msg) + + if metadata: + for key, value in metadata.items(): + await conn_rec.metadata_set(session, key, value) + else: + our_service = ServiceDecorator( + recipient_keys=[our_recipient_key], + endpoint=endpoint, + routing_keys=[], + ).serialize() + else: if not my_endpoint: my_endpoint = self.profile.settings.get("default_endpoint") diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/routes.py b/aries_cloudagent/protocols/out_of_band/v1_0/routes.py index e50732ccab..4b4a9907c2 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/routes.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/routes.py @@ -233,12 +233,23 @@ async def invitation_create(request: web.BaseRequest): auto_accept = json.loads(request.query.get("auto_accept", "null")) profile = context.profile + + emit_did_peer_4 = profile.settings.get("emit_did_peer_4", False) + emit_did_peer_2 = profile.settings.get("emit_did_peer_2", False) + if emit_did_peer_2 and emit_did_peer_4: + LOGGER.warning( + "emit_did_peer_2 and emit_did_peer_4 both set, \ + using did:peer:4" + ) + oob_mgr = OutOfBandManager(profile) try: invi_rec = await oob_mgr.create_invitation( my_label=my_label, auto_accept=auto_accept, public=use_public_did, + did_peer_2=emit_did_peer_2, + did_peer_4=emit_did_peer_4, hs_protos=[ h for h in [HSProto.get(hsp) for hsp in handshake_protocols] if h ], diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py index ecf22baa31..b28a1b0b61 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py @@ -52,6 +52,8 @@ async def test_invitation_create(self): my_label=None, auto_accept=True, public=True, + did_peer_2=False, + did_peer_4=False, multi_use=True, hs_protos=[test_module.HSProto.RFC23], attachments=body["attachments"], @@ -109,6 +111,8 @@ async def test_invitation_create_with_accept(self): my_label=None, auto_accept=True, public=True, + did_peer_2=False, + did_peer_4=False, multi_use=True, hs_protos=[test_module.HSProto.RFC23], attachments=body["attachments"], diff --git a/aries_cloudagent/wallet/did_method.py b/aries_cloudagent/wallet/did_method.py index e9d885ccd4..bf6ff57304 100644 --- a/aries_cloudagent/wallet/did_method.py +++ b/aries_cloudagent/wallet/did_method.py @@ -101,6 +101,7 @@ def __init__(self) -> None: KEY.method_name: KEY, WEB.method_name: WEB, PEER2.method_name: PEER2, + PEER4.method_name: PEER4, } def registered(self, method: str) -> bool: From 17c62075bd019cf72ce84fa98d50d35544e85604 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Wed, 28 Feb 2024 11:00:05 -0800 Subject: [PATCH 04/17] Demo options for multi-use Signed-off-by: Ian Costanzo --- demo/runners/agent_container.py | 31 +++++++++++++++++++++++++------ demo/runners/faber.py | 3 +++ demo/runners/support/agent.py | 10 +++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index 3d05e4cc24..6fb200dc02 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -62,6 +62,7 @@ def __init__( log_config: str = None, log_level: str = None, reuse_connections: bool = False, + multi_use_invitations: bool = False, public_did_connections: bool = False, extra_args: List[str] = [], **kwargs, @@ -93,6 +94,7 @@ def __init__( log_config=log_config, log_level=log_level, reuse_connections=reuse_connections, + multi_use_invitations=multi_use_invitations, public_did_connections=public_did_connections, **kwargs, ) @@ -151,8 +153,7 @@ async def handle_connections(self, message): if (not self.connection_id) and message["rfc23_state"] == "invitation-received": self.connection_id = conn_id - print(conn_id, self.connection_id, self.reuse_connections) - if conn_id == self.connection_id or self.reuse_connections: + if conn_id == self.connection_id or self.reuse_connections or self.multi_use_invitations: # inviter or invitee: if message["rfc23_state"] in ["completed", "response-sent"]: if not self._connection_ready.done(): @@ -605,6 +606,7 @@ async def generate_invitation( auto_accept: bool = True, display_qr: bool = False, reuse_connections: bool = False, + multi_use_invitations: bool = False, public_did_connections: bool = False, wait: bool = False, ): @@ -618,6 +620,7 @@ async def generate_invitation( use_did_exchange, auto_accept=auto_accept, reuse_connections=reuse_connections, + multi_use_invitations=multi_use_invitations, public_did_connections=public_did_connections, ) @@ -718,6 +721,7 @@ def __init__( arg_file: str = None, endorser_role: str = None, reuse_connections: bool = False, + multi_use_invitations: bool = False, public_did_connections: bool = False, taa_accept: bool = False, anoncreds_legacy_revocation: str = None, @@ -758,6 +762,7 @@ def __init__( self.cred_type = CRED_FORMAT_INDY self.reuse_connections = reuse_connections + self.multi_use_invitations = multi_use_invitations self.public_did_connections = public_did_connections self.exchange_tracing = False @@ -1152,6 +1157,7 @@ async def generate_invitation( auto_accept: bool = True, display_qr: bool = False, reuse_connections: bool = False, + multi_use_invitations: bool = False, public_did_connections: bool = False, wait: bool = False, ): @@ -1160,6 +1166,7 @@ async def generate_invitation( auto_accept=auto_accept, display_qr=display_qr, reuse_connections=reuse_connections, + multi_use_invitations=multi_use_invitations, public_did_connections=public_did_connections, wait=wait, ) @@ -1356,20 +1363,28 @@ def arg_parser(ident: str = None, port: int = 8020): "directly." ), ) + parser.add_argument( + "--reuse-connections", + action="store_true", + help=( + "Reuse connections by generating a reusable invitation. " + "Only applicable for AIP 2.0 (OOB) connections." + ), + ) if (not ident) or (ident != "alice"): parser.add_argument( - "--reuse-connections", + "--public-did-connections", action="store_true", help=( - "Reuse connections by generating a reusable invitation. " + "Use Faber public key in the invite. " "Only applicable for AIP 2.0 (OOB) connections." ), ) parser.add_argument( - "--public-did-connections", + "--multi-use-invitations", action="store_true", help=( - "Use Faber public key in the invite. " + "Create multi-use invitations. " "Only applicable for AIP 2.0 (OOB) connections." ), ) @@ -1498,6 +1513,9 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non reuse_connections = "reuse_connections" in args and args.reuse_connections if reuse_connections and aip != 20: raise Exception("Can only specify `--reuse-connections` with AIP 2.0") + multi_use_invitations = "multi_use_invitations" in args and args.multi_use_invitations + if multi_use_invitations and aip != 20: + raise Exception("Can only specify `--multi-use-invitations` with AIP 2.0") public_did_connections = ( "public_did_connections" in args and args.public_did_connections ) @@ -1528,6 +1546,7 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non aip=aip, endorser_role=args.endorser_role, reuse_connections=reuse_connections, + multi_use_invitations=multi_use_invitations, public_did_connections=public_did_connections, taa_accept=args.taa_accept, anoncreds_legacy_revocation=anoncreds_legacy_revocation, diff --git a/demo/runners/faber.py b/demo/runners/faber.py index a811f0ae66..50b42694dd 100644 --- a/demo/runners/faber.py +++ b/demo/runners/faber.py @@ -423,6 +423,7 @@ async def main(args): log_config=faber_agent.log_config, log_level=faber_agent.log_level, reuse_connections=faber_agent.reuse_connections, + multi_use_invitations=faber_agent.multi_use_invitations, public_did_connections=faber_agent.public_did_connections, extra_args=extra_args, ) @@ -457,6 +458,7 @@ async def main(args): await faber_agent.generate_invitation( display_qr=True, reuse_connections=faber_agent.reuse_connections, + multi_use_invitations=faber_agent.multi_use_invitations, public_did_connections=faber_agent.public_did_connections, wait=True, ) @@ -726,6 +728,7 @@ async def main(args): await faber_agent.generate_invitation( display_qr=True, reuse_connections=faber_agent.reuse_connections, + multi_use_invitations=faber_agent.multi_use_invitations, public_did_connections=faber_agent.public_did_connections, wait=True, ) diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index 9646776f57..a9ea9cfc1a 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -146,6 +146,7 @@ def __init__( log_config: str = None, log_level: str = None, reuse_connections: bool = False, + multi_use_invitations: bool = False, public_did_connections: bool = False, **params, ): @@ -182,6 +183,7 @@ def __init__( self.log_config = log_config self.log_level = log_level self.reuse_connections = reuse_connections + self.multi_use_invitations = multi_use_invitations self.public_did_connections = public_did_connections self.admin_url = f"http://{self.internal_host}:{admin_port}" @@ -1450,6 +1452,7 @@ async def get_invite( use_did_exchange: bool, auto_accept: bool = True, reuse_connections: bool = False, + multi_use_invitations: bool = False, public_did_connections: bool = False, ): self.connection_id = None @@ -1457,7 +1460,7 @@ async def get_invite( # TODO can mediation be used with DID exchange connections? invi_params = { "auto_accept": json.dumps(auto_accept), - "multi_use": json.dumps(reuse_connections), + "multi_use": json.dumps(multi_use_invitations), } payload = { "handshake_protocols": ["rfc23"], @@ -1465,6 +1468,7 @@ async def get_invite( } if self.mediation: payload["mediation_id"] = self.mediator_request_id + print("Calling /out-of-band/create-invitation with:", payload, invi_params) invi_rec = await self.admin_POST( "/out-of-band/create-invitation", payload, @@ -1494,8 +1498,8 @@ async def receive_invite(self, invite, auto_accept: bool = True): if self.mediation: params["mediation_id"] = self.mediator_request_id if "/out-of-band/" in invite.get("@type", ""): - # always reuse connections if possible - params["use_existing_connection"] = "true" + # reuse connections if requested and possible + params["use_existing_connection"] = json.dumps(self.reuse_connections) connection = await self.admin_POST( "/out-of-band/receive-invitation", invite, From 7743c20cc1de8e788ae79aae0b240fb89c0af322 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Wed, 28 Feb 2024 11:43:43 -0800 Subject: [PATCH 05/17] Big fixes generating did for invite Signed-off-by: Ian Costanzo --- .../protocols/out_of_band/v1_0/manager.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 5cf7e4d4b9..dbfbfc8059 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -294,25 +294,29 @@ async def create_invitation( my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) my_info = None + my_did = None if did_peer_4: my_info = await self.create_did_peer_4(my_endpoints, mediation_records) - conn_rec.my_did = my_info.did + my_did = my_info.did else: my_info = await self.create_did_peer_2(my_endpoints, mediation_records) - conn_rec.my_did = my_info.did + my_did = my_info.did + print("my_info:", my_info) + print("my_did:", my_did) invi_msg = InvitationMessage( # create invitation message _id=invitation_message_id, label=my_label or self.profile.settings.get("default_label"), handshake_protocols=handshake_protocols, requests_attach=message_attachments, - services=[my_info.did], + services=[my_did], accept=service_accept if protocol_version != "1.0" else None, version=protocol_version or DEFAULT_VERSION, image_url=image_url, ) + invi_url = invi_msg.to_url() - our_recipient_key = my_info.did.verkey + our_recipient_key = my_info.verkey # Only create connection record if hanshake_protocols is defined if handshake_protocols: @@ -322,7 +326,7 @@ async def create_invitation( else ConnRecord.INVITATION_MODE_ONCE ) conn_rec = ConnRecord( # create connection record - invitation_key=my_info.did.verkey, + invitation_key=our_recipient_key, invitation_msg_id=invi_msg._id, invitation_mode=invitation_mode, their_role=ConnRecord.Role.REQUESTER.rfc23, From 9073602aa30cfbd50daa95d9b5d130a975715df8 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Thu, 29 Feb 2024 10:26:47 -0800 Subject: [PATCH 06/17] Allow did:peer in invitations Signed-off-by: Ian Costanzo --- .../protocols/out_of_band/v1_0/manager.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index dbfbfc8059..6745624374 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -301,8 +301,6 @@ async def create_invitation( else: my_info = await self.create_did_peer_2(my_endpoints, mediation_records) my_did = my_info.did - print("my_info:", my_info) - print("my_did:", my_did) invi_msg = InvitationMessage( # create invitation message _id=invitation_message_id, @@ -886,8 +884,10 @@ async def _perform_handshake( # If it's in the did format, we need to convert to a full service block # An existing connection can only be reused based on a public DID # in an out-of-band message (RFC 0434). + # OR did:peer:2 or did:peer:4. - public_did = service.split(":")[-1] + if not service.startswith("did:peer"): + public_did = service.split(":")[-1] # TODO: resolve_invitation should resolve key_info objects # or something else that includes the key type. We now assume @@ -912,7 +912,10 @@ async def _perform_handshake( } ) - LOGGER.debug(f"Creating connection with public did {public_did}") + if public_did: + LOGGER.debug(f"Creating connection with public did {public_did}") + else: + LOGGER.debug(f"Creating connection with service {service}") conn_record = None for protocol in supported_handshake_protocols: From 99caee769945937191d7e42f35367e575762e062 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Thu, 29 Feb 2024 10:56:08 -0800 Subject: [PATCH 07/17] Add some connection test scenarios Signed-off-by: Ian Costanzo --- demo/features/0160-connection.feature | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/demo/features/0160-connection.feature b/demo/features/0160-connection.feature index 1748a5ec1f..0a04b0b8a9 100644 --- a/demo/features/0160-connection.feature +++ b/demo/features/0160-connection.feature @@ -13,11 +13,24 @@ Feature: RFC 0160 Aries agent connection functions @GHA @UnqualifiedDids Examples: - | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | - #| --public-did --did-exchange | --emit-did-peer-2 | --did-exchange |--emit-did-peer-2 | - #| --public-did --did-exchange | --emit-did-peer-4 | --did-exchange |--emit-did-peer-4 | - #| --public-did --did-exchange | --emit-did-peer-2 | --did-exchange |--emit-did-peer-4 | - #| --public-did --did-exchange | --emit-did-peer-4 | --did-exchange |--emit-did-peer-2 | - #| --public-did --did-exchange --reuse-connections | --emit-did-peer-2 | --did-exchange |--emit-did-peer-4 | - #| --public-did --did-exchange --reuse-connections | --emit-did-peer-4 | --did-exchange |--emit-did-peer-2 | - | --reuse-connections | --emit-did-peer-2 | | | + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | + | --public-did --did-exchange | --emit-did-peer-2 | --did-exchange | --emit-did-peer-2 | + | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange | --emit-did-peer-4 | + | --public-did --did-exchange | --emit-did-peer-2 | --did-exchange | --emit-did-peer-4 | + | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange | --emit-did-peer-2 | + | --public-did --did-exchange --reuse-connections | --emit-did-peer-2 | --did-exchange --reuse-connections | --emit-did-peer-4 | + | --public-did --did-exchange --reuse-connections | --emit-did-peer-4 | --did-exchange --reuse-connections | --emit-did-peer-2 | + + @GHA @PublicDidReuse + Examples: + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | + | --public-did --did-exchange | | --did-exchange | | + | --public-did --did-exchange --reuse-connections | | --did-exchange --reuse-connections | | + + @GHA @DidPeerConnectionReuse + Examples: + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | + | --did-exchange | --emit-did-peer-2 | | --emit-did-peer-2 | + | --did-exchange --reuse-connections | --emit-did-peer-2 | --reuse-connections | --emit-did-peer-2 | + | --did-exchange | --emit-did-peer-4 | | --emit-did-peer-4 | + | --did-exchange --reuse-connections | --emit-did-peer-4 | --reuse-connections | --emit-did-peer-4 | From a328954152dcc963cf0ca1a54bfcb7be4d36b12c Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Fri, 1 Mar 2024 10:42:55 -0800 Subject: [PATCH 08/17] Reuse peer2 and peer4 dids in invitations (WIP) Signed-off-by: Ian Costanzo --- aries_cloudagent/connections/base_manager.py | 41 +++++++++++-- .../protocols/out_of_band/v1_0/manager.py | 39 ++++++++++--- .../protocols/out_of_band/v1_0/routes.py | 15 +++++ aries_cloudagent/wallet/askar.py | 57 +++++++++++-------- aries_cloudagent/wallet/did_info.py | 3 + aries_cloudagent/wallet/routes.py | 13 ++++- demo/runners/support/agent.py | 1 + 7 files changed, 132 insertions(+), 37 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index 0541dbcd89..3363efa12a 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -5,7 +5,7 @@ import json import logging -from typing import List, Optional, Sequence, Text, Tuple, Union +from typing import Dict, List, Optional, Sequence, Text, Tuple, Union import pydid from base58 import b58decode @@ -52,7 +52,7 @@ from ..utils.multiformats import multibase, multicodec from ..wallet.base import BaseWallet from ..wallet.crypto import create_keypair, seed_to_did -from ..wallet.did_info import DIDInfo, KeyInfo +from ..wallet.did_info import DIDInfo, KeyInfo, INVITATION_REUSE_KEY from ..wallet.did_method import PEER2, PEER4, SOV from ..wallet.error import WalletNotFoundError from ..wallet.key_type import ED25519 @@ -113,6 +113,7 @@ async def create_did_peer_4( self, svc_endpoints: Optional[Sequence[str]] = None, mediation_records: Optional[List[MediationRecord]] = None, + metadata: Optional[Dict] = None, ) -> DIDInfo: """Create a did:peer:4 DID for a connection. @@ -159,8 +160,13 @@ async def create_did_peer_4( ) did = encode(input_doc) + did_metadata = metadata if metadata else {} did_info = DIDInfo( - did=did, method=PEER4, verkey=key.verkey, metadata={}, key_type=ED25519 + did=did, + method=PEER4, + verkey=key.verkey, + metadata=did_metadata, + key_type=ED25519, ) await wallet.store_did(did_info) @@ -170,6 +176,7 @@ async def create_did_peer_2( self, svc_endpoints: Optional[Sequence[str]] = None, mediation_records: Optional[List[MediationRecord]] = None, + metadata: Optional[Dict] = None, ) -> DIDInfo: """Create a did:peer:2 DID for a connection. @@ -215,13 +222,39 @@ async def create_did_peer_2( [KeySpec.verification(self._key_info_to_multikey(key))], services ) + did_metadata = metadata if metadata else {} did_info = DIDInfo( - did=did, method=PEER2, verkey=key.verkey, metadata={}, key_type=ED25519 + did=did, + method=PEER2, + verkey=key.verkey, + metadata=did_metadata, + key_type=ED25519, ) await wallet.store_did(did_info) return did_info + async def fetch_invitation_reuse_did( + self, + did_method: str, + ) -> DIDDoc: + """Fetch a DID from the wallet to use across multiple invitations. + + Args: + did_method: The DID method used (e.g. PEER2 or PEER4) + + Returns: + The `DIDDoc` instance, or "None" if no DID is found + """ + did_info = None + async with self._profile.session() as session: + wallet = session.inject(BaseWallet) + did_list = await wallet.get_local_dids() + for did in did_list: + if did.method == did_method and INVITATION_REUSE_KEY in did.metadata: + return did + return did_info + async def create_did_document( self, did_info: DIDInfo, diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 6745624374..22fefc9f35 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -21,6 +21,8 @@ from ....storage.error import StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet +from ....wallet.did_info import INVITATION_REUSE_KEY +from ....wallet.did_method import PEER2, PEER4 from ....wallet.key_type import ED25519 from ...connections.v1_0.manager import ConnectionManager from ...connections.v1_0.messages.connection_invitation import ConnectionInvitation @@ -85,6 +87,7 @@ async def create_invitation( did_peer_4: bool = False, hs_protos: Sequence[HSProto] = None, multi_use: bool = False, + create_unique_did: bool = False, alias: str = None, attachments: Sequence[Mapping] = None, metadata: dict = None, @@ -291,16 +294,38 @@ async def create_invitation( default_endpoint = self.profile.settings.get("default_endpoint") if default_endpoint: my_endpoints.append(default_endpoint) - my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) + my_endpoints.extend( + self.profile.settings.get("additional_endpoints", []) + ) my_info = None my_did = None - if did_peer_4: - my_info = await self.create_did_peer_4(my_endpoints, mediation_records) - my_did = my_info.did - else: - my_info = await self.create_did_peer_2(my_endpoints, mediation_records) - my_did = my_info.did + if not create_unique_did: + # check wallet to see if there is an existing "invitation" DID available + did_method = PEER4 if did_peer_4 else PEER2 + my_info = await self.fetch_invitation_reuse_did(did_method) + if my_info: + my_did = my_info.did + + if not my_did: + did_metadata = ( + {INVITATION_REUSE_KEY: "true"} if not create_unique_did else {} + ) + if did_peer_4: + my_info = await self.create_did_peer_4( + my_endpoints, mediation_records, did_metadata + ) + my_did = my_info.did + else: + my_info = await self.create_did_peer_2( + my_endpoints, mediation_records, did_metadata + ) + my_did = my_info.did + + if not create_unique_did: + # save DID to wallet to re-use in next invitation + # TODO + pass invi_msg = InvitationMessage( # create invitation message _id=invitation_message_id, diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/routes.py b/aries_cloudagent/protocols/out_of_band/v1_0/routes.py index 4b4a9907c2..12cc792c62 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/routes.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/routes.py @@ -45,6 +45,12 @@ class InvitationCreateQueryStringSchema(OpenAPISchema): required=False, metadata={"description": "Create invitation for multiple use (default false)"}, ) + create_unique_did = fields.Boolean( + required=False, + metadata={ + "description": "Create unique DID for this invitation (default false)" + }, + ) class InvitationCreateRequestSchema(OpenAPISchema): @@ -231,6 +237,12 @@ async def invitation_create(request: web.BaseRequest): multi_use = json.loads(request.query.get("multi_use", "false")) auto_accept = json.loads(request.query.get("auto_accept", "null")) + create_unique_did = json.loads(request.query.get("create_unique_did", "false")) + + if create_unique_did and use_public_did: + raise web.HTTPBadRequest( + reason="create_unique_did cannot be used with use_public_did" + ) profile = context.profile @@ -241,6 +253,8 @@ async def invitation_create(request: web.BaseRequest): "emit_did_peer_2 and emit_did_peer_4 both set, \ using did:peer:4" ) + if create_unique_did and not (emit_did_peer_4 or emit_did_peer_2): + raise web.HTTPBadRequest(reason="create_unique_did must be used with did:peer") oob_mgr = OutOfBandManager(profile) try: @@ -254,6 +268,7 @@ async def invitation_create(request: web.BaseRequest): h for h in [HSProto.get(hsp) for hsp in handshake_protocols] if h ], multi_use=multi_use, + create_unique_did=create_unique_did, attachments=attachments, metadata=metadata, alias=alias, diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py index c7a55501ca..53bd0a507d 100644 --- a/aries_cloudagent/wallet/askar.py +++ b/aries_cloudagent/wallet/askar.py @@ -30,6 +30,7 @@ validate_seed, verify_signed_message, ) +from .did_info import INVITATION_REUSE_KEY from .did_method import SOV, DIDMethod, DIDMethods from .error import WalletError, WalletDuplicateError, WalletNotFoundError from .key_type import BLS12381G2, ED25519, KeyType, KeyTypes @@ -230,21 +231,25 @@ async def create_local_did( CATEGORY_DID, did, value_json=did_info, tags=item.tags ) else: + value_json = { + "did": did, + "method": method.method_name, + "verkey": verkey, + "verkey_type": key_type.key_type, + "metadata": metadata, + } + tags = { + "method": method.method_name, + "verkey": verkey, + "verkey_type": key_type.key_type, + } + if INVITATION_REUSE_KEY in metadata: + tags[INVITATION_REUSE_KEY] = "true" await self._session.handle.insert( CATEGORY_DID, did, - value_json={ - "did": did, - "method": method.method_name, - "verkey": verkey, - "verkey_type": key_type.key_type, - "metadata": metadata, - }, - tags={ - "method": method.method_name, - "verkey": verkey, - "verkey_type": key_type.key_type, - }, + value_json=value_json, + tags=tags, ) except AskarError as err: @@ -273,21 +278,25 @@ async def store_did(self, did_info: DIDInfo) -> DIDInfo: if item: raise WalletDuplicateError("DID already present in wallet") else: + value_json = { + "did": did_info.did, + "method": did_info.method.method_name, + "verkey": did_info.verkey, + "verkey_type": did_info.key_type.key_type, + "metadata": did_info.metadata, + } + tags = { + "method": did_info.method.method_name, + "verkey": did_info.verkey, + "verkey_type": did_info.key_type.key_type, + } + if INVITATION_REUSE_KEY in did_info.metadata: + tags[INVITATION_REUSE_KEY] = "true" await self._session.handle.insert( CATEGORY_DID, did_info.did, - value_json={ - "did": did_info.did, - "method": did_info.method.method_name, - "verkey": did_info.verkey, - "verkey_type": did_info.key_type.key_type, - "metadata": did_info.metadata, - }, - tags={ - "method": did_info.method.method_name, - "verkey": did_info.verkey, - "verkey_type": did_info.key_type.key_type, - }, + value_json=value_json, + tags=tags, ) except AskarError as err: raise WalletError("Error when storing DID") from err diff --git a/aries_cloudagent/wallet/did_info.py b/aries_cloudagent/wallet/did_info.py index 2f5ebbdccb..fb25ebe9b4 100644 --- a/aries_cloudagent/wallet/did_info.py +++ b/aries_cloudagent/wallet/did_info.py @@ -5,6 +5,9 @@ from .did_method import DIDMethod from .key_type import KeyType + +INVITATION_REUSE_KEY = "invitation_reuse" + KeyInfo = NamedTuple( "KeyInfo", [("verkey", str), ("metadata", dict), ("key_type", KeyType)] ) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index e394df1b2a..c4f30969a5 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -59,7 +59,7 @@ from ..wallet.sd_jwt import sd_jwt_sign, sd_jwt_verify from .base import BaseWallet from .did_info import DIDInfo -from .did_method import KEY, SOV, DIDMethod, DIDMethods, HolderDefinedDid +from .did_method import KEY, SOV, DIDMethod, DIDMethods, HolderDefinedDid, PEER2, PEER4 from .did_posture import DIDPosture from .error import WalletError, WalletNotFoundError from .key_type import BLS12381G2, ED25519, KeyTypes @@ -114,6 +114,12 @@ class DIDSchema(OpenAPISchema): "example": ED25519.key_type, }, ) + metadata = fields.Dict( + required=False, + metadata={ + "description": "Additional metadata associated with the DID", + }, + ) class DIDResultSchema(OpenAPISchema): @@ -287,7 +293,9 @@ class DIDListQueryStringSchema(OpenAPISchema): ) method = fields.Str( required=False, - validate=validate.OneOf([KEY.method_name, SOV.method_name]), + validate=validate.OneOf( + [KEY.method_name, SOV.method_name, PEER2.method_name, PEER4.method_name] + ), metadata={ "example": KEY.method_name, "description": ( @@ -411,6 +419,7 @@ def format_did_info(info: DIDInfo): "posture": DIDPosture.get(info.metadata).moniker, "key_type": info.key_type.key_type, "method": info.method.method_name, + "metadata": info.metadata, } diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index a9ea9cfc1a..10fcae3f3e 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -1461,6 +1461,7 @@ async def get_invite( invi_params = { "auto_accept": json.dumps(auto_accept), "multi_use": json.dumps(multi_use_invitations), + "create_unique_did": json.dumps(not reuse_connections), } payload = { "handshake_protocols": ["rfc23"], From 1ae711454de4d195ba88124ecc908d0689339df3 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Mon, 4 Mar 2024 11:21:22 -0800 Subject: [PATCH 09/17] Fix connection reuse with did:peer WIP Signed-off-by: Ian Costanzo --- .../connections/models/conn_record.py | 10 +++++++--- .../protocols/didexchange/v1_0/manager.py | 8 ++++++++ .../protocols/out_of_band/v1_0/manager.py | 19 ++++++++++++------- demo/runners/agent_container.py | 10 ++++++++-- demo/runners/alice.py | 1 + demo/runners/support/agent.py | 1 + 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/aries_cloudagent/connections/models/conn_record.py b/aries_cloudagent/connections/models/conn_record.py index 6415542aca..76a49ff202 100644 --- a/aries_cloudagent/connections/models/conn_record.py +++ b/aries_cloudagent/connections/models/conn_record.py @@ -360,13 +360,17 @@ async def retrieve_by_invitation_msg_id( async def find_existing_connection( cls, session: ProfileSession, their_public_did: str ) -> Optional["ConnRecord"]: - """Retrieve existing active connection records (public did). + """Retrieve existing active connection records (public did or did:peer). Args: session: The active profile session - their_public_did: Inviter public DID + their_public_did: Inviter public DID (or did:peer) """ - tag_filter = {"their_public_did": their_public_did} + print("find connection with did:", their_public_did) + if their_public_did.startswith("did:peer"): + tag_filter = {"their_did": their_public_did} + else: + tag_filter = {"their_public_did": their_public_did} conn_records = await cls.query( session, tag_filter=tag_filter, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 65d8051b06..d9a66cf450 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -414,6 +414,10 @@ async def receive_request( {"request": request}, settings=self.profile.settings, ) + print( + "Receiving connection request:", + {"request": request}, + ) conn_rec = None connection_key = None @@ -436,6 +440,7 @@ async def receive_request( f"in state {ConnRecord.State.INVITATION.rfc23}: " "a prior connection request may have updated the connection state" ) + print("EXISTING conn_rec:", conn_rec) else: if not self.profile.settings.get("public_invites"): raise DIDXManagerError( @@ -458,11 +463,14 @@ async def receive_request( invitation_msg_id=request._thread.pthid, their_role=ConnRecord.Role.REQUESTER.rfc23, ) + print("PUBLIC DID conn_rec with new DID:", conn_rec, my_info) if conn_rec: # invitation was explicit connection_key = conn_rec.invitation_key if conn_rec.is_multiuse_invitation: + print("Handling multi-use invitation ...") async with self.profile.session() as session: + print("Creating a new local SOV did ...") wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did( method=SOV, diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 22fefc9f35..b5685f4baf 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -300,17 +300,22 @@ async def create_invitation( my_info = None my_did = None + print("Create unique DID:", create_unique_did) if not create_unique_did: # check wallet to see if there is an existing "invitation" DID available did_method = PEER4 if did_peer_4 else PEER2 my_info = await self.fetch_invitation_reuse_did(did_method) if my_info: + print("Reusing DID for invitation:", my_info) my_did = my_info.did + else: + print("No invitation DID found, creating new DID") if not my_did: did_metadata = ( {INVITATION_REUSE_KEY: "true"} if not create_unique_did else {} ) + print("Creating new DID with did_metadata:", did_metadata) if did_peer_4: my_info = await self.create_did_peer_4( my_endpoints, mediation_records, did_metadata @@ -322,11 +327,6 @@ async def create_invitation( ) my_did = my_info.did - if not create_unique_did: - # save DID to wallet to re-use in next invitation - # TODO - pass - invi_msg = InvitationMessage( # create invitation message _id=invitation_message_id, label=my_label or self.profile.settings.get("default_label"), @@ -361,6 +361,7 @@ async def create_invitation( ), alias=alias, connection_protocol=connection_protocol, + my_did=my_did, ) async with self.profile.session() as session: @@ -554,7 +555,7 @@ async def receive_invitation( # service_accept service_accept = invitation.accept - # Get the DID public did, if any + # Get the DID public did, if any (might also be a did:peer) public_did = None if isinstance(oob_service_item, str): if bool(IndyDID.PATTERN.match(oob_service_item)): @@ -565,7 +566,7 @@ async def receive_invitation( conn_rec = None # Find existing connection - only if started by an invitation with Public DID - # and use_existing_connection is true + # (or did:peer) and use_existing_connection is true if ( public_did is not None and use_existing_connection ): # invite has public DID: seek existing connection @@ -573,10 +574,12 @@ async def receive_invitation( "Trying to find existing connection for oob invitation with " f"did {public_did}" ) + print("Trying to find existing connection with did:", public_did) async with self._profile.session() as session: conn_rec = await ConnRecord.find_existing_connection( session=session, their_public_did=public_did ) + print("Found conn_rec:", conn_rec) oob_record = OobRecord( role=OobRecord.ROLE_RECEIVER, @@ -939,8 +942,10 @@ async def _perform_handshake( if public_did: LOGGER.debug(f"Creating connection with public did {public_did}") + print("Creating connection with public did:", public_did) else: LOGGER.debug(f"Creating connection with service {service}") + print("Creating connection with service:", service) conn_record = None for protocol in supported_handshake_protocols: diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index 6fb200dc02..1503785a4f 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -153,7 +153,11 @@ async def handle_connections(self, message): if (not self.connection_id) and message["rfc23_state"] == "invitation-received": self.connection_id = conn_id - if conn_id == self.connection_id or self.reuse_connections or self.multi_use_invitations: + if ( + conn_id == self.connection_id + or self.reuse_connections + or self.multi_use_invitations + ): # inviter or invitee: if message["rfc23_state"] in ["completed", "response-sent"]: if not self._connection_ready.done(): @@ -1513,7 +1517,9 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non reuse_connections = "reuse_connections" in args and args.reuse_connections if reuse_connections and aip != 20: raise Exception("Can only specify `--reuse-connections` with AIP 2.0") - multi_use_invitations = "multi_use_invitations" in args and args.multi_use_invitations + multi_use_invitations = ( + "multi_use_invitations" in args and args.multi_use_invitations + ) if multi_use_invitations and aip != 20: raise Exception("Can only specify `--multi-use-invitations` with AIP 2.0") public_did_connections = ( diff --git a/demo/runners/alice.py b/demo/runners/alice.py index 50da4035a6..a65baf3654 100644 --- a/demo/runners/alice.py +++ b/demo/runners/alice.py @@ -150,6 +150,7 @@ async def main(args): log_file=alice_agent.log_file, log_config=alice_agent.log_config, log_level=alice_agent.log_level, + reuse_connections=alice_agent.reuse_connections, extra_args=extra_args, ) diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index 10fcae3f3e..f7b53530e3 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -1501,6 +1501,7 @@ async def receive_invite(self, invite, auto_accept: bool = True): if "/out-of-band/" in invite.get("@type", ""): # reuse connections if requested and possible params["use_existing_connection"] = json.dumps(self.reuse_connections) + print("Receiving invitation with params:", params) connection = await self.admin_POST( "/out-of-band/receive-invitation", invite, From 15b6895df725ea9de5958c2d2c2869f67495f7ad Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Tue, 5 Mar 2024 09:06:40 -0800 Subject: [PATCH 10/17] Clean up debugging code Signed-off-by: Ian Costanzo --- aries_cloudagent/connections/models/conn_record.py | 1 - aries_cloudagent/protocols/didexchange/v1_0/manager.py | 8 -------- aries_cloudagent/protocols/out_of_band/v1_0/manager.py | 9 +-------- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/aries_cloudagent/connections/models/conn_record.py b/aries_cloudagent/connections/models/conn_record.py index 76a49ff202..c45089c8ce 100644 --- a/aries_cloudagent/connections/models/conn_record.py +++ b/aries_cloudagent/connections/models/conn_record.py @@ -366,7 +366,6 @@ async def find_existing_connection( session: The active profile session their_public_did: Inviter public DID (or did:peer) """ - print("find connection with did:", their_public_did) if their_public_did.startswith("did:peer"): tag_filter = {"their_did": their_public_did} else: diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index d9a66cf450..65d8051b06 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -414,10 +414,6 @@ async def receive_request( {"request": request}, settings=self.profile.settings, ) - print( - "Receiving connection request:", - {"request": request}, - ) conn_rec = None connection_key = None @@ -440,7 +436,6 @@ async def receive_request( f"in state {ConnRecord.State.INVITATION.rfc23}: " "a prior connection request may have updated the connection state" ) - print("EXISTING conn_rec:", conn_rec) else: if not self.profile.settings.get("public_invites"): raise DIDXManagerError( @@ -463,14 +458,11 @@ async def receive_request( invitation_msg_id=request._thread.pthid, their_role=ConnRecord.Role.REQUESTER.rfc23, ) - print("PUBLIC DID conn_rec with new DID:", conn_rec, my_info) if conn_rec: # invitation was explicit connection_key = conn_rec.invitation_key if conn_rec.is_multiuse_invitation: - print("Handling multi-use invitation ...") async with self.profile.session() as session: - print("Creating a new local SOV did ...") wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did( method=SOV, diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index b5685f4baf..a8f356d689 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -300,22 +300,19 @@ async def create_invitation( my_info = None my_did = None - print("Create unique DID:", create_unique_did) if not create_unique_did: # check wallet to see if there is an existing "invitation" DID available did_method = PEER4 if did_peer_4 else PEER2 my_info = await self.fetch_invitation_reuse_did(did_method) if my_info: - print("Reusing DID for invitation:", my_info) my_did = my_info.did else: - print("No invitation DID found, creating new DID") + LOGGER.warn("No invitation DID found, creating new DID") if not my_did: did_metadata = ( {INVITATION_REUSE_KEY: "true"} if not create_unique_did else {} ) - print("Creating new DID with did_metadata:", did_metadata) if did_peer_4: my_info = await self.create_did_peer_4( my_endpoints, mediation_records, did_metadata @@ -574,12 +571,10 @@ async def receive_invitation( "Trying to find existing connection for oob invitation with " f"did {public_did}" ) - print("Trying to find existing connection with did:", public_did) async with self._profile.session() as session: conn_rec = await ConnRecord.find_existing_connection( session=session, their_public_did=public_did ) - print("Found conn_rec:", conn_rec) oob_record = OobRecord( role=OobRecord.ROLE_RECEIVER, @@ -942,10 +937,8 @@ async def _perform_handshake( if public_did: LOGGER.debug(f"Creating connection with public did {public_did}") - print("Creating connection with public did:", public_did) else: LOGGER.debug(f"Creating connection with service {service}") - print("Creating connection with service:", service) conn_record = None for protocol in supported_handshake_protocols: From e847bf24bf76cc0cdb780b1c0e4f99fbc0a14376 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Tue, 5 Mar 2024 10:37:03 -0800 Subject: [PATCH 11/17] Clean up unit tests and docs Signed-off-by: Ian Costanzo --- .../protocols/out_of_band/v1_0/routes.py | 2 -- .../out_of_band/v1_0/tests/test_routes.py | 2 ++ aries_cloudagent/wallet/tests/test_routes.py | 16 ++++++++++++ demo/runners/support/agent.py | 3 ++- docs/demo/README.md | 6 ++++- docs/demo/ReusingAConnection.md | 26 ++++++++----------- 6 files changed, 36 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/routes.py b/aries_cloudagent/protocols/out_of_band/v1_0/routes.py index 12cc792c62..210fedd7f9 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/routes.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/routes.py @@ -253,8 +253,6 @@ async def invitation_create(request: web.BaseRequest): "emit_did_peer_2 and emit_did_peer_4 both set, \ using did:peer:4" ) - if create_unique_did and not (emit_did_peer_4 or emit_did_peer_2): - raise web.HTTPBadRequest(reason="create_unique_did must be used with did:peer") oob_mgr = OutOfBandManager(profile) try: diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py index b28a1b0b61..a6ea7eb8c0 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_routes.py @@ -55,6 +55,7 @@ async def test_invitation_create(self): did_peer_2=False, did_peer_4=False, multi_use=True, + create_unique_did=False, hs_protos=[test_module.HSProto.RFC23], attachments=body["attachments"], metadata=body["metadata"], @@ -114,6 +115,7 @@ async def test_invitation_create_with_accept(self): did_peer_2=False, did_peer_4=False, multi_use=True, + create_unique_did=False, hs_protos=[test_module.HSProto.RFC23], attachments=body["attachments"], metadata=body["metadata"], diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index da15998b6c..05a6eed6e7 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -138,6 +138,7 @@ async def test_create_did(self): "posture": DIDPosture.WALLET_ONLY.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": False, "posted": False}, } } ) @@ -238,6 +239,7 @@ async def test_did_list(self): "posture": DIDPosture.POSTED.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": False, "posted": True}, }, { "did": self.test_did, @@ -245,6 +247,7 @@ async def test_did_list(self): "posture": DIDPosture.WALLET_ONLY.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": False, "posted": False}, }, ] } @@ -283,6 +286,7 @@ async def test_did_list_filter_public(self): "posture": DIDPosture.PUBLIC.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": True, "posted": True}, } ] } @@ -324,6 +328,7 @@ async def test_did_list_filter_posted(self): "posture": DIDPosture.POSTED.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": False, "posted": True}, } ] } @@ -353,6 +358,7 @@ async def test_did_list_filter_did(self): "posture": DIDPosture.WALLET_ONLY.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": False, "posted": False}, } ] } @@ -393,6 +399,7 @@ async def test_did_list_filter_verkey(self): "posture": DIDPosture.WALLET_ONLY.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": False, "posted": False}, } ] } @@ -431,6 +438,7 @@ async def test_get_public_did(self): "posture": DIDPosture.PUBLIC.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": True, "posted": True}, } } ) @@ -484,6 +492,7 @@ async def test_set_public_did(self): "posture": DIDPosture.PUBLIC.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": True, "posted": True}, } } ) @@ -664,6 +673,11 @@ async def test_set_public_did_update_endpoint(self): "posture": DIDPosture.PUBLIC.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": { + "public": True, + "posted": True, + "endpoint": "http://mediator.example.com", + }, } } ) @@ -722,6 +736,7 @@ async def test_set_public_did_update_endpoint_use_default_update_in_wallet(self) "posture": DIDPosture.PUBLIC.moniker, "key_type": ED25519.key_type, "method": SOV.method_name, + "metadata": {"public": True, "posted": True}, } } ) @@ -765,6 +780,7 @@ async def test_set_public_did_with_non_sov_did(self): "posture": DIDPosture.PUBLIC.moniker, "key_type": ED25519.key_type, "method": WEB.method_name, + "metadata": {"public": True, "posted": True}, } } ) diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index f7b53530e3..2d19d98898 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -1458,10 +1458,11 @@ async def get_invite( self.connection_id = None if use_did_exchange: # TODO can mediation be used with DID exchange connections? + create_unique_did = (not reuse_connections) and (not public_did_connections) invi_params = { "auto_accept": json.dumps(auto_accept), "multi_use": json.dumps(multi_use_invitations), - "create_unique_did": json.dumps(not reuse_connections), + "create_unique_did": json.dumps(create_unique_did), } payload = { "handshake_protocols": ["rfc23"], diff --git a/docs/demo/README.md b/docs/demo/README.md index 61a0fe5b2d..9e94677534 100644 --- a/docs/demo/README.md +++ b/docs/demo/README.md @@ -265,7 +265,11 @@ You can enable DID Exchange using the `--did-exchange` parameter for the `alice` This will use the new DID Exchange protocol when establishing connections between the agents, rather than the older Connection protocol. There is no other affect on the operation of the agents. -Note that you can't (currently) use the DID Exchange protocol to connect with any of the available mobile agents. +With DID Exchange, you can also enable use of the inviter's public DID for invitations, multi-use invitations, and connection re-use: + +- `--public-did-connections` - use the inviter's public DID in invitations, and allow use of implicit invitations +- `--reuse-connections` - support connection re-use (invitee will reuse an existing connection if it uses the same DID as in the new invitation) +- `--multi-use-invitations` - inviter will issue multi-use invitations ### Endorser diff --git a/docs/demo/ReusingAConnection.md b/docs/demo/ReusingAConnection.md index cab8cfe50e..00945eef78 100644 --- a/docs/demo/ReusingAConnection.md +++ b/docs/demo/ReusingAConnection.md @@ -102,27 +102,21 @@ instruction up to the point where you are about to start the Faber and Alice age 1. On a command line, run Faber with these parameters: `./run_demo faber --reuse-connection --public-did-connections --events`. 2. On a second command line, run Alice as normal, perhaps with the `events` - option: `./run_demo alice --events` + option: `./run_demo alice --reuse-connection --events` 3. Copy the invitation from the Faber terminal and paste it into the Alice terminal at the prompt. 4. Verify that the connection was established. 1. If you want, go to the Alice OpenAPI screen (port `8031`, path `api/docs`), and then use the `GET Connections` to see that Alice has one connection to Faber. -5. In the Alice terminal, type `4` to get a prompt for a new connection, and - paste the same invitation as in Step 3 (above). -6. Note from the webhook events in the Faber terminal that the `reuse` message +5. In the Faber terminal, type `4` to get a prompt for a new connection. This + will generate a new invitation with the same public DID. +6. In the Alice terminal, type `4` to get a prompt for a new connection, and + paste the new invitation. +7. Note from the webhook events in the Faber terminal that the `reuse` message is received from Alice, and as a result, no new connection was created. 1. Execute again the `GET Connections` endpoint on the Alice OpenAPI screen to confirm that there is still just one established connection. -7. In the Faber terminal, type `4` to get a new invitation, copy the invitation, - in the Alice terminal, type `4` to get prompted for an invitation, and paste - in the new invitation from Faber. Again, the `reuse` webhook event will be - visible in the Faber terminal. - 1. Execute again the `GET Connections` endpoint on the Alice OpenAPI screen - to confirm that there is still just one established connection. - 2. Notice that in the invitations in Step 3 and 7 both have the same DID in - the `services`. 8. Try running the demo again **without** the `--reuse-connection` parameter and compare the `services` value in the new invitation vs. what was generated in Steps 3 and 7. It is not a DID, but rather a one time use, inline DIDDoc @@ -146,6 +140,8 @@ To run faber using a `did_peer` and reusable connections: DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connection --events ``` -Note that the invitation does **NOT** have to be a multi-use invitation for -reuse to be useful, as long as the other requirements (at the top of this -document) are met. +To run this demo using a multi-use invitation (from Faber): + +``` +DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connection --multi-use-invitations --events +``` From 94f72ba0927307aad7d50fdd9bf832eaa555365b Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Thu, 7 Mar 2024 11:12:12 -0800 Subject: [PATCH 12/17] Couple of bug fixes Signed-off-by: Ian Costanzo --- aries_cloudagent/resolver/default/peer3.py | 4 ++-- demo/runners/agent_container.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/resolver/default/peer3.py b/aries_cloudagent/resolver/default/peer3.py index bca776e449..4ef207263b 100644 --- a/aries_cloudagent/resolver/default/peer3.py +++ b/aries_cloudagent/resolver/default/peer3.py @@ -89,8 +89,8 @@ async def remove_record_for_deleted_conn(self, profile: Profile, event: Event): if not their_did and not my_did: return dids = [ - *(did for did in (their_did, my_did) if PEER3_PATTERN.match(did)), - *(peer2to3(did) for did in (their_did, my_did) if PEER2_PATTERN.match(did)), + *(did for did in (their_did, my_did) if did and PEER3_PATTERN.match(did)), + *(peer2to3(did) for did in (their_did, my_did) if did and PEER2_PATTERN.match(did)), ] if dids: LOGGER.debug( diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index 1503785a4f..5fe0459c9a 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -159,7 +159,10 @@ async def handle_connections(self, message): or self.multi_use_invitations ): # inviter or invitee: - if message["rfc23_state"] in ["completed", "response-sent"]: + if message["state"] == "deleted": + # connection reuse - invitation is getting deleted - ignore + pass + elif message["rfc23_state"] in ["completed", "response-sent"]: if not self._connection_ready.done(): self.log("Connected") self._connection_ready.set_result(True) From 5a17be7a4012a245dd198c5ce016d2839dd9a69a Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Fri, 8 Mar 2024 07:34:41 -0800 Subject: [PATCH 13/17] Formatting isse Signed-off-by: Ian Costanzo --- aries_cloudagent/resolver/default/peer3.py | 6 +++++- demo/features/0160-connection.feature | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/resolver/default/peer3.py b/aries_cloudagent/resolver/default/peer3.py index 4ef207263b..8d07776d7c 100644 --- a/aries_cloudagent/resolver/default/peer3.py +++ b/aries_cloudagent/resolver/default/peer3.py @@ -90,7 +90,11 @@ async def remove_record_for_deleted_conn(self, profile: Profile, event: Event): return dids = [ *(did for did in (their_did, my_did) if did and PEER3_PATTERN.match(did)), - *(peer2to3(did) for did in (their_did, my_did) if did and PEER2_PATTERN.match(did)), + *( + peer2to3(did) + for did in (their_did, my_did) + if did and PEER2_PATTERN.match(did) + ), ] if dids: LOGGER.debug( diff --git a/demo/features/0160-connection.feature b/demo/features/0160-connection.feature index 0a04b0b8a9..1bde0a54b2 100644 --- a/demo/features/0160-connection.feature +++ b/demo/features/0160-connection.feature @@ -34,3 +34,15 @@ Feature: RFC 0160 Aries agent connection functions | --did-exchange --reuse-connections | --emit-did-peer-2 | --reuse-connections | --emit-did-peer-2 | | --did-exchange | --emit-did-peer-4 | | --emit-did-peer-4 | | --did-exchange --reuse-connections | --emit-did-peer-4 | --reuse-connections | --emit-did-peer-4 | + + @GHA @MultiUseConnectionReuse + Examples: + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | + | --did-exchange --multi-use-invitations | --emit-did-peer-2 | | --emit-did-peer-2 | + | --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-2 | --reuse-connections | --emit-did-peer-2 | + | --did-exchange --multi-use-invitations | --emit-did-peer-4 | | --emit-did-peer-4 | + | --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-4 | --reuse-connections | --emit-did-peer-4 | + | --public-did --did-exchange --multi-use-invitations | --emit-did-peer-2 | --did-exchange | --emit-did-peer-4 | + | --public-did --did-exchange --multi-use-invitations | --emit-did-peer-4 | --did-exchange | --emit-did-peer-2 | + | --public-did --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-2 | --did-exchange --reuse-connections | --emit-did-peer-4 | + | --public-did --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-4 | --did-exchange --reuse-connections | --emit-did-peer-2 | From bfc610c851073f32e34111f22cc0812c2990d7b3 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Mon, 11 Mar 2024 11:19:20 -0700 Subject: [PATCH 14/17] Some did:peer:4 fixes to work with connection reuse Signed-off-by: Ian Costanzo --- aries_cloudagent/connections/base_manager.py | 11 ++++++++++- .../protocols/out_of_band/v1_0/manager.py | 6 +++++- aries_cloudagent/wallet/askar.py | 13 +++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index 3363efa12a..df5815128c 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -89,6 +89,12 @@ def _key_info_to_multikey(key_info: KeyInfo) -> str: multicodec.wrap("ed25519-pub", b58decode(key_info.verkey)), "base58btc" ) + def long_did_peer_to_short(self, long_did: str) -> DIDInfo: + """Convert did:peer:4 long format to short format and return.""" + + short_did_peer = long_to_short(long_did) + return short_did_peer + async def long_did_peer_4_to_short(self, long_dp4: str) -> DIDInfo: """Convert did:peer:4 long format to short format and store in wallet.""" @@ -379,7 +385,10 @@ async def find_did_for_key(self, key: str) -> str: async with self._profile.session() as session: storage: BaseStorage = session.inject(BaseStorage) record = await storage.find_record(self.RECORD_TYPE_DID_KEY, {"key": key}) - return record.tags["did"] + ret_did = record.tags["did"] + if ret_did.startswith("did:peer:4"): + ret_did = self.long_did_peer_to_short(ret_did) + return ret_did async def remove_keys_for_did(self, did: str): """Remove all keys associated with a DID. diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index a8f356d689..e334e8d8fa 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -571,9 +571,13 @@ async def receive_invitation( "Trying to find existing connection for oob invitation with " f"did {public_did}" ) + if public_did.startswith("did:peer:4"): + search_public_did = self.long_did_peer_to_short(public_did) + else: + search_public_did = public_did async with self._profile.session() as session: conn_rec = await ConnRecord.find_existing_connection( - session=session, their_public_did=public_did + session=session, their_public_did=search_public_did ) oob_record = OobRecord( diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py index 53bd0a507d..b8c58446d9 100644 --- a/aries_cloudagent/wallet/askar.py +++ b/aries_cloudagent/wallet/askar.py @@ -357,12 +357,21 @@ async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: try: dids = await self._session.handle.fetch_all( - CATEGORY_DID, {"verkey": verkey}, limit=1 + CATEGORY_DID, {"verkey": verkey} ) except AskarError as err: raise WalletError("Error when fetching local DID for verkey") from err if dids: - return self._load_did_entry(dids[0]) + ret_did = dids[0] + ret_did_info = ret_did.value_json + if len(dids) > 1 and ret_did_info["did"].startswith("did:peer:4"): + # if it is a peer:did:4 make sure we are using the short version + other_did = dids[1] # assume only 2 + other_did_info = other_did.value_json + if len(other_did_info["did"]) < len(ret_did_info["did"]): + ret_did = other_did + ret_did_info = ret_did.value_json + return self._load_did_entry(ret_did) raise WalletNotFoundError("No DID defined for verkey: {}".format(verkey)) async def replace_local_did_metadata(self, did: str, metadata: dict): From c3c58e57a2cec5d111fd85224b73bda0b8dc2525 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Thu, 14 Mar 2024 10:20:21 -0700 Subject: [PATCH 15/17] Tweak integration tests for connection reuse Signed-off-by: Ian Costanzo --- demo/features/0160-connection.feature | 31 ++++++++++++---- demo/features/0453-issue-credential.feature | 40 +++++++++++++-------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/demo/features/0160-connection.feature b/demo/features/0160-connection.feature index 1bde0a54b2..85d2652161 100644 --- a/demo/features/0160-connection.feature +++ b/demo/features/0160-connection.feature @@ -1,3 +1,4 @@ +@RFC0160 Feature: RFC 0160 Aries agent connection functions @T001-RFC0160 @@ -16,18 +17,22 @@ Feature: RFC 0160 Aries agent connection functions | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | | --public-did --did-exchange | --emit-did-peer-2 | --did-exchange | --emit-did-peer-2 | | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange | --emit-did-peer-4 | + | --public-did --did-exchange --reuse-connections | --emit-did-peer-4 | --did-exchange --reuse-connections | --emit-did-peer-4 | + + @UnqualifiedDids + Examples: + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | | --public-did --did-exchange | --emit-did-peer-2 | --did-exchange | --emit-did-peer-4 | - | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange | --emit-did-peer-2 | - | --public-did --did-exchange --reuse-connections | --emit-did-peer-2 | --did-exchange --reuse-connections | --emit-did-peer-4 | | --public-did --did-exchange --reuse-connections | --emit-did-peer-4 | --did-exchange --reuse-connections | --emit-did-peer-2 | + | --public-did --did-exchange | --emit-did-peer-4 | --did-exchange | --emit-did-peer-2 | - @GHA @PublicDidReuse + @PublicDidReuse Examples: | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | | --public-did --did-exchange | | --did-exchange | | | --public-did --did-exchange --reuse-connections | | --did-exchange --reuse-connections | | - @GHA @DidPeerConnectionReuse + @DidPeerConnectionReuse Examples: | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | | --did-exchange | --emit-did-peer-2 | | --emit-did-peer-2 | @@ -39,10 +44,22 @@ Feature: RFC 0160 Aries agent connection functions Examples: | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | | --did-exchange --multi-use-invitations | --emit-did-peer-2 | | --emit-did-peer-2 | - | --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-2 | --reuse-connections | --emit-did-peer-2 | - | --did-exchange --multi-use-invitations | --emit-did-peer-4 | | --emit-did-peer-4 | | --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-4 | --reuse-connections | --emit-did-peer-4 | | --public-did --did-exchange --multi-use-invitations | --emit-did-peer-2 | --did-exchange | --emit-did-peer-4 | + | --public-did --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-4 | --did-exchange --reuse-connections | --emit-did-peer-2 | + + @MultiUseConnectionReuse + Examples: + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | + | --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-2 | --reuse-connections | --emit-did-peer-2 | + | --did-exchange --multi-use-invitations | --emit-did-peer-4 | | --emit-did-peer-4 | | --public-did --did-exchange --multi-use-invitations | --emit-did-peer-4 | --did-exchange | --emit-did-peer-2 | | --public-did --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-2 | --did-exchange --reuse-connections | --emit-did-peer-4 | - | --public-did --did-exchange --multi-use-invitations --reuse-connections | --emit-did-peer-4 | --did-exchange --reuse-connections | --emit-did-peer-2 | + + @GHA @WalletType_Askar_AnonCreds + Examples: + | Acme_capabilities | Acme_extra | Bob_capabilities | Bob_extra | + | --public-did --did-exchange --wallet-type askar-anoncreds | --emit-did-peer-2 | --did-exchange --wallet-type askar-anoncreds | --emit-did-peer-2 | + | --public-did --did-exchange --wallet-type askar-anoncreds --reuse-connections | --emit-did-peer-4 | --did-exchange --wallet-type askar-anoncreds --reuse-connections | --emit-did-peer-4 | + | --did-exchange --wallet-type askar-anoncreds | --emit-did-peer-2 | --wallet-type askar-anoncreds | --emit-did-peer-2 | + | --did-exchange --wallet-type askar-anoncreds --reuse-connections | --emit-did-peer-4 | --wallet-type askar-anoncreds --reuse-connections | --emit-did-peer-4 | diff --git a/demo/features/0453-issue-credential.feature b/demo/features/0453-issue-credential.feature index 748a8027c0..245144a2ff 100644 --- a/demo/features/0453-issue-credential.feature +++ b/demo/features/0453-issue-credential.feature @@ -4,9 +4,9 @@ Feature: RFC 0453 Aries agent issue credential @T003-RFC0453 Scenario Outline: Issue a credential with the Issuer beginning with an offer Given we have "2" agents - | name | role | capabilities | - | Acme | issuer | | - | Bob | holder | | + | name | role | capabilities | extra | + | Acme | issuer | | | + | Bob | holder | | | And "Acme" and "Bob" have an existing connection And "Acme" is ready to issue a credential for When "Acme" offers a credential with data @@ -14,26 +14,38 @@ Feature: RFC 0453 Aries agent issue credential @GHA @WalletType_Askar @BasicTest Examples: - | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | - | --public-did --did-exchange | --did-exchange | driverslicense | Data_DL_NormalizedValues | + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | + | --public-did --did-exchange | --did-exchange | driverslicense | Data_DL_NormalizedValues | | | @GHA @WalletType_Askar @AltTests Examples: - | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | - | --public-did | | driverslicense | Data_DL_NormalizedValues | - | --public-did --mediation | --mediation | driverslicense | Data_DL_NormalizedValues | - | --public-did --multitenant | --multitenant --log-file | driverslicense | Data_DL_NormalizedValues | + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | + | --public-did | | driverslicense | Data_DL_NormalizedValues | | | + | --public-did --mediation | --mediation | driverslicense | Data_DL_NormalizedValues | | | + | --public-did --multitenant | --multitenant --log-file | driverslicense | Data_DL_NormalizedValues | | | @GHA @WalletType_Askar_AnonCreds @BasicTest Examples: - | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | - | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | + | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | @GHA @WalletType_Askar_AnonCreds @AltTests Examples: - | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | - | --public-did --wallet-type askar-anoncreds | | driverslicense | Data_DL_NormalizedValues | - | --public-did | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | + | --public-did --wallet-type askar-anoncreds | | driverslicense | Data_DL_NormalizedValues | | | + | --public-did | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | + + @GHA @WalletType_Askar @ConnectionTests + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | + | --did-exchange | --did-exchange | driverslicense | Data_DL_NormalizedValues | --emit-did-peer-4 | --emit-did-peer-4 | + | --did-exchange --reuse-connections | --did-exchange --reuse-connections | driverslicense | Data_DL_NormalizedValues | --emit-did-peer-4 | --emit-did-peer-4 | + + @GHA @WalletType_Askar_AnonCreds @ConnectionTests + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | + | --did-exchange --wallet-type askar-anoncreds | --did-exchange --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | --emit-did-peer-4 | --emit-did-peer-4 | + | --did-exchange --wallet-type askar-anoncreds --reuse-connections | --did-exchange --wallet-type askar-anoncreds --reuse-connections | driverslicense | Data_DL_NormalizedValues | --emit-did-peer-4 | --emit-did-peer-4 | @T003-RFC0453 Scenario Outline: Holder accepts a deleted credential offer From 7856a5ed34943ab4c851cb39a88298cdd832a207 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Thu, 14 Mar 2024 10:24:28 -0700 Subject: [PATCH 16/17] Fix docs Signed-off-by: Ian Costanzo --- docs/demo/ReusingAConnection.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/demo/ReusingAConnection.md b/docs/demo/ReusingAConnection.md index 00945eef78..1a4f8ee7e8 100644 --- a/docs/demo/ReusingAConnection.md +++ b/docs/demo/ReusingAConnection.md @@ -100,9 +100,9 @@ instruction up to the point where you are about to start the Faber and Alice age [Alice Faber Demo]: ./README.md 1. On a command line, run Faber with these parameters: `./run_demo faber - --reuse-connection --public-did-connections --events`. + --reuse-connections --public-did-connections --events`. 2. On a second command line, run Alice as normal, perhaps with the `events` - option: `./run_demo alice --reuse-connection --events` + option: `./run_demo alice --reuse-connections --events` 3. Copy the invitation from the Faber terminal and paste it into the Alice terminal at the prompt. 4. Verify that the connection was established. @@ -117,7 +117,7 @@ instruction up to the point where you are about to start the Faber and Alice age is received from Alice, and as a result, no new connection was created. 1. Execute again the `GET Connections` endpoint on the Alice OpenAPI screen to confirm that there is still just one established connection. -8. Try running the demo again **without** the `--reuse-connection` parameter and +8. Try running the demo again **without** the `--reuse-connections` parameter and compare the `services` value in the new invitation vs. what was generated in Steps 3 and 7. It is not a DID, but rather a one time use, inline DIDDoc item. @@ -131,17 +131,17 @@ always the same that tells the invitee that they can reuse an existing connectio For example, to run faber with connection reuse using a non-public DID: ``` -./run_demo faber --reuse-connection --events +./run_demo faber --reuse-connections --events ``` To run faber using a `did_peer` and reusable connections: ``` -DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connection --events +DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connections --events ``` To run this demo using a multi-use invitation (from Faber): ``` -DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connection --multi-use-invitations --events +DEMO_EXTRA_AGENT_ARGS="[\"--emit-did-peer-2\"]" ./run_demo faber --reuse-connections --multi-use-invitations --events ``` From 22ecfcc2804f0e9790756946ab707d717ef23b9d Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Tue, 19 Mar 2024 08:15:34 -0700 Subject: [PATCH 17/17] Minor fix Signed-off-by: Ian Costanzo --- aries_cloudagent/wallet/askar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py index b8c58446d9..6150adc6d3 100644 --- a/aries_cloudagent/wallet/askar.py +++ b/aries_cloudagent/wallet/askar.py @@ -370,7 +370,7 @@ async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: other_did_info = other_did.value_json if len(other_did_info["did"]) < len(ret_did_info["did"]): ret_did = other_did - ret_did_info = ret_did.value_json + ret_did_info = other_did.value_json return self._load_did_entry(ret_did) raise WalletNotFoundError("No DID defined for verkey: {}".format(verkey))