Skip to content

Commit 34c7783

Browse files
Merge pull request #207 from andrewwhitehead/test-static
Add support for test-suite-specific static connection
2 parents ca34806 + 08e4154 commit 34c7783

File tree

5 files changed

+180
-28
lines changed

5 files changed

+180
-28
lines changed

aries_cloudagent/conductor.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import asyncio
1212
from collections import OrderedDict
13+
import hashlib
1314
import logging
1415
from typing import Coroutine, Union
1516

@@ -188,6 +189,23 @@ async def start(self) -> None:
188189
self.admin_server,
189190
)
190191

192+
# Create a static connection for use by the test-suite
193+
if context.settings.get("debug.test_suite_endpoint"):
194+
mgr = ConnectionManager(self.context)
195+
their_endpoint = context.settings["debug.test_suite_endpoint"]
196+
test_conn = await mgr.create_static_connection(
197+
my_seed=hashlib.sha256(b"aries-protocol-test-subject").digest(),
198+
their_seed=hashlib.sha256(b"aries-protocol-test-suite").digest(),
199+
their_endpoint=their_endpoint,
200+
their_role="tester",
201+
alias="test-suite",
202+
)
203+
print("Created static connection for test suite")
204+
print(" - My DID:", test_conn.my_did)
205+
print(" - Their DID:", test_conn.their_did)
206+
print(" - Their endpoint:", their_endpoint)
207+
print()
208+
191209
# Print an invitation to the terminal
192210
if context.settings.get("debug.print_invitation"):
193211
try:

aries_cloudagent/config/argparse.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,12 @@ def add_arguments(self, parser: ArgumentParser):
233233
action="store_true",
234234
help="Flag specifying the generated invite should be public."
235235
)
236+
parser.add_argument(
237+
"--test-suite-endpoint",
238+
type=str,
239+
metavar="<endpoint>",
240+
help="URL endpoint for sending messages to the test suite agent."
241+
)
236242

237243
parser.add_argument(
238244
"--auto-accept-invites",
@@ -318,6 +324,8 @@ def get_settings(self, args: Namespace) -> dict:
318324
settings["debug.invite_multi_use"] = True
319325
if args.invite_public:
320326
settings["debug.invite_public"] = True
327+
if args.test_suite_endpoint:
328+
settings["debug.test_suite_endpoint"] = args.test_suite_endpoint
321329

322330
if args.auto_respond_credential_proposal:
323331
settings["debug.auto_respond_credential_proposal"] = True

aries_cloudagent/messaging/connections/manager.py

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
from ...storage.error import StorageError, StorageNotFoundError
1414
from ...storage.record import StorageRecord
1515
from ...wallet.base import BaseWallet, DIDInfo
16+
from ...wallet.crypto import create_keypair, seed_to_did
1617
from ...wallet.error import WalletNotFoundError
18+
from ...wallet.util import bytes_to_b58
1719

1820
from ..message_delivery import MessageDelivery
1921
from ..responder import BaseResponder
@@ -68,7 +70,7 @@ async def create_invitation(
6870
accept: str = None,
6971
public: bool = False,
7072
multi_use: bool = False,
71-
alias: str = None
73+
alias: str = None,
7274
) -> Tuple[ConnectionRecord, ConnectionInvitation]:
7375
"""
7476
Generate new connection invitation.
@@ -161,7 +163,7 @@ async def create_invitation(
161163
state=ConnectionRecord.STATE_INVITATION,
162164
accept=accept,
163165
invitation_mode=invitation_mode,
164-
alias=alias
166+
alias=alias,
165167
)
166168

167169
await connection.save(self.context, reason="Created new invitation")
@@ -217,7 +219,7 @@ async def receive_invitation(
217219
their_role=their_role,
218220
state=ConnectionRecord.STATE_INVITATION,
219221
accept=accept,
220-
alias=alias
222+
alias=alias,
221223
)
222224

223225
await connection.save(
@@ -276,8 +278,10 @@ async def create_request(
276278
connection.my_did = my_info.did
277279

278280
# Create connection request message
281+
if not my_endpoint:
282+
my_endpoint = self.context.settings.get("default_endpoint")
279283
did_doc = await self.create_did_document(
280-
my_info, connection.inbound_connection_id
284+
my_info, connection.inbound_connection_id, my_endpoint
281285
)
282286
if not my_label:
283287
my_label = self.context.settings.get("default_label")
@@ -357,7 +361,7 @@ async def receive_request(
357361

358362
await new_connection.save(
359363
self.context,
360-
reason="Received connection request from multi-use invitation DID"
364+
reason="Received connection request from multi-use invitation DID",
361365
)
362366
connection = new_connection
363367

@@ -461,6 +465,8 @@ async def create_response(
461465
connection.my_did = my_info.did
462466

463467
# Create connection response message
468+
if not my_endpoint:
469+
my_endpoint = self.context.settings.get("default_endpoint")
464470
did_doc = await self.create_did_document(
465471
my_info, connection.inbound_connection_id, my_endpoint
466472
)
@@ -565,6 +571,70 @@ async def accept_response(
565571

566572
return connection
567573

574+
async def create_static_connection(
575+
self,
576+
my_did: str = None,
577+
my_seed: str = None,
578+
their_did: str = None,
579+
their_seed: str = None,
580+
their_verkey: str = None,
581+
their_endpoint: str = None,
582+
their_role: str = None,
583+
alias: str = None,
584+
) -> ConnectionRecord:
585+
"""
586+
Register a new static connection (for use by the test suite).
587+
588+
Args:
589+
my_did: override the DID used in the connection
590+
my_seed: provide a seed used to generate our DID and keys
591+
their_did: provide the DID used by the other party
592+
their_seed: provide a seed used to generate their DID and keys
593+
their_verkey: provide the verkey used by the other party
594+
their_endpoint: their URL endpoint for routing messages
595+
their_role: their role in this connection
596+
alias: an alias for this connection record
597+
598+
Returns:
599+
The new `ConnectionRecord` instance
600+
601+
"""
602+
wallet: BaseWallet = await self.context.inject(BaseWallet)
603+
604+
# seed and DID optional
605+
my_info = await wallet.create_local_did(my_seed, my_did)
606+
607+
# must provide their DID and verkey if the seed is not known
608+
if (not their_did or not their_verkey) and not their_seed:
609+
raise ConnectionManagerError(
610+
"Either a verkey or seed must be provided for the other party"
611+
)
612+
if not their_did:
613+
their_did = seed_to_did(their_seed)
614+
if not their_verkey:
615+
their_verkey_bin, _ = create_keypair(their_seed)
616+
their_verkey = bytes_to_b58(their_verkey_bin)
617+
their_info = DIDInfo(their_did, their_verkey, {})
618+
619+
print(their_info, my_info)
620+
621+
# Create connection record
622+
connection = ConnectionRecord(
623+
initiator=ConnectionRecord.INITIATOR_SELF,
624+
my_did=my_info.did,
625+
their_did=their_info.did,
626+
their_role=their_role,
627+
state=ConnectionRecord.STATE_ACTIVE,
628+
alias=alias,
629+
)
630+
await connection.save(self.context, reason="Created new static connection")
631+
632+
# Synthesize their DID doc
633+
did_doc = await self.create_did_document(their_info, None, their_endpoint)
634+
await self.store_did_document(did_doc)
635+
636+
return connection
637+
568638
async def find_connection(
569639
self,
570640
their_did: str,
@@ -675,27 +745,27 @@ async def find_message_connection(
675745

676746
async def create_did_document(
677747
self,
678-
my_info: DIDInfo,
748+
did_info: DIDInfo,
679749
inbound_connection_id: str = None,
680-
my_endpoint: str = None,
750+
svc_endpoint: str = None,
681751
) -> DIDDoc:
682752
"""Create our DID document for a given DID.
683753
684754
Args:
685-
my_info: The DID I am using in this connection
686-
inbound_connection_id: The inbound routing connection id to use
687-
my_endpoint: A custom endpoint for the DID Document
755+
did_info: The DID information (DID and verkey) used in the connection
756+
inbound_connection_id: The ID of the inbound routing connection to use
757+
svc_endpoint: A custom endpoint for the DID Document
688758
689759
Returns:
690760
The prepared `DIDDoc` instance
691761
692762
"""
693763

694-
did_doc = DIDDoc(did=my_info.did)
695-
did_controller = my_info.did
696-
did_key = my_info.verkey
764+
did_doc = DIDDoc(did=did_info.did)
765+
did_controller = did_info.did
766+
did_key = did_info.verkey
697767
pk = PublicKey(
698-
my_info.did,
768+
did_info.did,
699769
"1",
700770
did_key,
701771
PublicKeyType.ED25519_SIG_2018,
@@ -729,24 +799,23 @@ async def create_did_document(
729799
"Routing DIDDoc service has no recipient key(s)"
730800
)
731801
rk = PublicKey(
732-
my_info.did,
802+
did_info.did,
733803
f"routing-{router_idx}",
734804
service.recip_keys[0].value,
735805
PublicKeyType.ED25519_SIG_2018,
736806
did_controller,
737807
True,
738808
)
739809
routing_keys.append(rk)
740-
my_endpoint = service.endpoint
810+
svc_endpoint = service.endpoint
741811
break
742812
router_id = router.inbound_connection_id
743813

744-
if not my_endpoint:
745-
my_endpoint = self.context.settings.get("default_endpoint")
746-
service = Service(
747-
my_info.did, "indy", "IndyAgent", [pk], routing_keys, my_endpoint
748-
)
749-
did_doc.set(service)
814+
if svc_endpoint:
815+
service = Service(
816+
did_info.did, "indy", "IndyAgent", [pk], routing_keys, svc_endpoint
817+
)
818+
did_doc.set(service)
750819

751820
return did_doc
752821

aries_cloudagent/messaging/connections/routes.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from ...storage.error import StorageNotFoundError
99

10-
from ..valid import UUIDFour
10+
from ..valid import IndyDID, UUIDFour
1111

1212
from .manager import ConnectionManager
1313
from .messages.connection_invitation import (
@@ -30,16 +30,40 @@ class InvitationResultSchema(Schema):
3030
"""Result schema for a new connection invitation."""
3131

3232
connection_id = fields.Str(
33-
description="Connection identifier",
34-
example=UUIDFour.EXAMPLE,
33+
description="Connection identifier", example=UUIDFour.EXAMPLE
3534
)
3635
invitation = fields.Nested(ConnectionInvitationSchema())
3736
invitation_url = fields.Str(
3837
description="Invitation URL",
39-
example="http:192.168.56.101:8020/invite?c_i=eyJAdHlwZSI6Li4ufQ=="
38+
example="http://192.168.56.101:8020/invite?c_i=eyJAdHlwZSI6Li4ufQ==",
4039
)
4140

4241

42+
class ConnectionStaticRequestSchema(Schema):
43+
"""Request schema for a new static connection."""
44+
45+
my_seed = fields.Str(description="Seed to use for the local DID", required=False)
46+
my_did = fields.Str(
47+
description="Local DID", required=False, example=IndyDID.EXAMPLE
48+
)
49+
their_seed = fields.Str(
50+
description="Seed to use for the remote DID", required=False
51+
)
52+
their_did = fields.Str(
53+
description="Remote DID", required=False, example=IndyDID.EXAMPLE
54+
)
55+
their_verkey = fields.Str(description="Remote verification key", required=False)
56+
their_endpoint = fields.Str(
57+
description="URL endpoint for the other party",
58+
required=False,
59+
example="http://192.168.56.101:5000",
60+
)
61+
their_role = fields.Str(
62+
description="Role to assign to this connection", required=False
63+
)
64+
alias = fields.Str(description="Alias to assign to this connection", required=False)
65+
66+
4367
def connection_sort_key(conn):
4468
"""Get the sorting key for a particular connection."""
4569
if conn["state"] == ConnectionRecord.STATE_INACTIVE:
@@ -401,6 +425,39 @@ async def connections_remove(request: web.BaseRequest):
401425
return web.json_response({})
402426

403427

428+
@docs(tags=["connection"], summary="Create a new static connection")
429+
@request_schema(ConnectionStaticRequestSchema())
430+
@response_schema(ConnectionRecordSchema(), 200)
431+
async def connections_create_static(request: web.BaseRequest):
432+
"""
433+
Request handler for creating a new static connection.
434+
435+
Args:
436+
request: aiohttp request object
437+
438+
Returns:
439+
The new connection record
440+
441+
"""
442+
context = request.app["request_context"]
443+
body = await request.json()
444+
445+
connection_mgr = ConnectionManager(context)
446+
connection = await connection_mgr.create_static_connection(
447+
my_seed=body.get("my_seed") or None,
448+
my_did=body.get("my_did") or None,
449+
their_seed=body.get("their_seed") or None,
450+
their_did=body.get("their_did") or None,
451+
their_verkey=body.get("their_verkey") or None,
452+
their_endpoint=body.get("their_endpoint") or None,
453+
their_role=body.get("their_role") or None,
454+
alias=body.get("alias") or None,
455+
)
456+
result = connection.serialize()
457+
458+
return web.json_response(result)
459+
460+
404461
async def register(app: web.Application):
405462
"""Register routes."""
406463

aries_cloudagent/wallet/crypto.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ def random_seed() -> bytes:
7777

7878
def seed_to_did(seed: str) -> str:
7979
"""
80-
Derive a did from a seed value.
80+
Derive a DID from a seed value.
8181
8282
Args:
8383
seed: The seed to derive
8484
8585
Returns:
86-
The did derived from the seed
86+
The DID derived from the seed
8787
8888
"""
8989
seed = validate_seed(seed)

0 commit comments

Comments
 (0)