From 80c9c282d306125f0e62cf5bcaf30b934532b4f1 Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Sat, 29 Jun 2019 15:12:50 +0000 Subject: [PATCH 1/7] Deployment Model document Signed-off-by: Stephen Curran --- docs/deploymentModel.md | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/deploymentModel.md diff --git a/docs/deploymentModel.md b/docs/deploymentModel.md new file mode 100644 index 0000000000..706a1366a1 --- /dev/null +++ b/docs/deploymentModel.md @@ -0,0 +1,68 @@ + + +# Aries Cloud Agent-Python (ACA-Py) - Deployment Model + +This document is a "concept of operations" for an instance of an Aries cloud agent deployed from the primary artifact (a PyPi package) produced by this repo. In such a deployment there are **always** two components - a configured agent itself, and a controller that injects into that agent the business rules for the particular agent instance (see diagram). + +![ACA-Py Deployment Overview](./assets/DeploymentModel-full.png "ACA-Py Deployment Overview") + +The deployed agent messages with other agents via DIDcomm protocols, and as events associated with those messages occur, sends webhook HTTP notifications to the controller. The agent also exposes for the controller's exclusive use an HTTP API covering all of the administrative handlers for those events. The controller receives the notifications from the agent, decides (with business rules - possible by asking a person using a UI) how to respond to the event and calls back to the agent via the HTTP API. Of course, the controller may also initiate events (e.g. messaging another agent) by calling that same API. + +The following is an example of the interactions involved in creating a connection using the DIDcomm "Establish Connection" protocol. The controller requests from the agent (via the administrative API) a connection invitation from the agent, and receives one back. The controller provides it to another agent (perhaps by displaying it in a QR code). Shortly after, the agent receives a DIDcomm "Connection Request" message. The agent, sends it to the controller. The controller decides to accept the connection and calls the API with instructions to the agent to send a "Connection Response" message to the other agent. Since the controller always wants to know with whom a connection has been created, the controller also sends instructions to the agent (via the API, of course) to send a request presentation message to the new connection. And so on... During the interactions, the agent is tracking the state of the connections, and the state of the protocol instances (threads). Likewise, the controller may also be retaining state - after all, it's an application that could do anything. + +Most developers will configure a "black box" instance of the ACA-Py. They need to know how it works, the DIDcomm protocols it supports, the events it will generate and the administrative API it exposes. However, they don't need to drill into and maintain the ACA-Py code. Such developers will build controller applications (basically, traditional web apps) that at their simplest, use an HTTP interface to receive notification and send HTTP requests to the agent. It's the business logic implemented in, or accessed by the controller that gives the deployment its personality and role. + +Note: the ACA-Py agent is designed to be stateless, persisting connection and protocol state to storage (such as Postgres database). As such, agents can be deployed to support horizontal scaling as necessary. Controllers can also be implemented to support horizontal scaling. + +The sections below detail the internals of the ACA-Py and it's configurable elements, and the conceptual elements of a controller. There is no "Aries controller" repo to fork, as it is essentially just a web app. There are demos of using the elements in this repo, and several sample applications that you can use to get started on your on controller. + +## Aries Cloud Agent + +**Aries cloud agent** implement services to manage the execution of DIDcomm messaging protocols for interacting with other DIDcomm agents, and exposes an administrative HTTP API that supports a controller to direct how the agent should respond to messaging events. The agent relies on the controller to provide the business rules for handling the messaging events, and to initiate the execution of new DIDcomm protocol instances. The internals of an ACA-Py instance is diagramed below. + +![ACA-Py Agent Internals](assets/DeploymentModel-agent.png "ACA-Py Agent Internals") + +Instances of the Aries cloud agents are configured with the following sub-components: + +* **Transport Plugins** - pluggable transport-specific message sender/receiver modules that interact with other agents. Messages outside the plugins are transport-agnostic JSON structures. Current modules include HTTP and WebSockets. In the future, we might add ZMQ, SMTP and so on. +* **Conductor **receives inbound messages from, and sends outbound messages to, the transport plugins. After internal processing, the conductor passes inbound messages to, and receives outbound messages from, the Dispatcher. In processing the messages, the conductor manages the message’s protocol instance thread state, retrieving the state on inbound messages and saving the state on outbound messages. The conductor handles generic decorators in messages such as verifying and generating signatures on message data elements, internationalization and so on. +* **Dispatcher **handles the distribution of messages to the DIDcomm protocol message handlers and the responses received. The dispatcher passes to the conductor the thread state to be persistance and message data (if any) to be sent out from the Aries cloud agent instance. +* **DIDcomm Protocols **- implement the DIDcomm protocols supported by the agent instance, including the state object for the protocol, the DIDcomm message handlers and the admin message handlers. Protocols are bundled as Python modules and loaded for during the agent deployment. Each protocol contributes the admin messages for the protocol to the controller REST interface. The protocols implement a number of events that invoke the controller via webhooks so that controller’s business logic can respond to the event. +* **Controller REST API** - a dynamically generated REST API (with a Swagger/OpenAPI user interface) based on the set of DIDcomm protocols included in the agent deployment. The controller, activated via the webhooks from the protocol DIDcomm message handlers, controls the Aries cloud agent by calling the REST API that invoke the protocol admin message handlers. +* **Handler API** - provides abstract interfaces to various handlers needed by the protocols and core Aries cloud agent components for accessing the secure storage (wallet), other storage, the ledger and so on. The API calls the handler implementations configured into the agent deployment. +* **Handler Plugins** - are the handler implementations** **called from the Handler API. The plugins may be internal to the Agent (in the same process space) or could be external (for example, in other processes/containers). +* **Secure Storage Plugin** - the Indy SDK is embedded in the Aries cloud agent and implements the default secure storage. An Aries cloud agent can be configured to use one of a number of indy-sdk storage implementations - in-memory, SQLite and Postgres at this time. +* **Ledger Interface** **Plugin **- In the current Aries cloud agent implementation, the Indy SDK provides an interface to an Indy-based public ledger for verifiable credential protocols. In future, ledger implementations (including those other than Indy) might be moved into the DIDcomm protocol modules to be included as needed within a configured Aries cloud agent instance based on the DIDcomm protocols used by the agent. + +## Controller + +A controller provides the personality of Aries cloud agent instance - the business logic (human, machine or rules driven) that drive the behaviour of the agent. The controller’s “Business Logic” in a cloud agent could be built into the controller app, could be an integration back to an enterprise system, or even a user interface for an individual. In all cases, the business logic provide responses to agent events or initiates agent actions. A deployed controller talks to a single Aries cloud agent deployment and manages the configuration of that agent. Both can be configured and deployed to support horizontal scaling. + +![Controller Internals](assets/DeploymentModel-controller.png "Controller Internals") + +Generically, a controller is a web app invoked by HTTP webhook calls from its corresponding Aries cloud agent and invoking the DIDcomm administration capabilities of the Aries cloud agent by calling the REST API exposed by that cloud agent. As well as responding to Aries cloud agent events, the controller initiates DIDcomm protocol instances using the same REST API. + +The controller and Aries cloud agent deployment **MUST **secure the HTTP interface between the two components. The interface provides the same HTTP integration between services as modern apps found in any enterprise today, and must be correspondingly secured. + +A controller implements the following capabilities. + +* **Initiator** - provides a mechanism to initiate newDIDcomm protocol instances. The initiator invokes the REST API exposed by the Aries cloud agent to initiate the creation of a DIDcomm protocol instance. For example, a permit-issuing service uses this mechanism to issue a Verifiable Credential associated with the issuance of a new permit. +* **Responder** - subscribes to and responds to events from the Aries cloud agent protocol message handlers, providing business-driven responses. The responder might respond immediately, or the event might cause a delay while the decision is determined, perhaps by sending the request to a person to decide. The controller may persist the event response state if the event is asynchronous - for example, when the event is passed to a person who may respond when they next use the web app. +* **Configuration **- manages the controller configuration data and the configuration of the Aries cloud agent. Configuration in this context includes things like: + * Credentials and Proof Requests to be Issued/Verified (respectively) by the Aries cloud agent. + * The configuration of the webhook handler to which the responder subscribes. + +While there are several examples of controllers, there is no “cookie cutter” repository to fork and customize. A controller is just a web service that receives HTTP requests (webhooks) and sends HTTP messages to the Aries cloud agent it controls via the REST API exposed by that agent. + +## Deployment + +The Aries cloud agent CI pipeline configured into the repository generates a PyPi package as an artifact. Implementers will generally have a controller repository, possibly copied from an existing controller instance, that has the code (business logic) for the controller and the configuration (transports, handlers, DIDcomm protocols, etc.) for the Aries cloud agent instance. In the most common scenario, the Aries cloud agent and controller instances will be deployed based on the artifacts (e.g. container images) generated from that controller repository. With the simple HTTP-based interface between the controller and Aries cloud agent, both components can be horizontally scaled as needed, with a load balancer between the components. The configuration of the Aries cloud agent to use the Postgres wallet supports enterprise scale agent deployments. + +Current examples of deployed instances of Aries cloud agent and controllers include: + +* [indy-email-verification](https://github.com/bcgov/indy-email-verification) - a web app Controller that sends an email to a given email address with an embedded DIDcomm invitation and on establishment of a connection, offers and provides the connected agent with an email control verifiable credential. +* [iiwbook](https://github.com/bcgov/iiwbook) - a web app Controller that on creation of a DIDcomm connection, requests a proof of email control, and then sends to the connection a verifiable credential proving attendance at IIW. In between the proof and issuance is a human approval step using a simple web-based UI that implements a request queue. +* Indy-catalyst-agent - an issuer/verifier agent that proofs credentials from, and issues credentials to, an instance of an Indy Catalyst Credential Registry. \ No newline at end of file From 9a71301cbb10f8c01f60655ee443d0d6cc787da4 Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Sat, 29 Jun 2019 15:15:25 +0000 Subject: [PATCH 2/7] Correction to image names Signed-off-by: Stephen Curran --- docs/deploymentModel.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/deploymentModel.md b/docs/deploymentModel.md index 706a1366a1..4e228044ba 100644 --- a/docs/deploymentModel.md +++ b/docs/deploymentModel.md @@ -7,7 +7,7 @@ This document is a "concept of operations" for an instance of an Aries cloud agent deployed from the primary artifact (a PyPi package) produced by this repo. In such a deployment there are **always** two components - a configured agent itself, and a controller that injects into that agent the business rules for the particular agent instance (see diagram). -![ACA-Py Deployment Overview](./assets/DeploymentModel-full.png "ACA-Py Deployment Overview") +![ACA-Py Deployment Overview](assets/deploymentModel-full.png "ACA-Py Deployment Overview") The deployed agent messages with other agents via DIDcomm protocols, and as events associated with those messages occur, sends webhook HTTP notifications to the controller. The agent also exposes for the controller's exclusive use an HTTP API covering all of the administrative handlers for those events. The controller receives the notifications from the agent, decides (with business rules - possible by asking a person using a UI) how to respond to the event and calls back to the agent via the HTTP API. Of course, the controller may also initiate events (e.g. messaging another agent) by calling that same API. @@ -23,7 +23,7 @@ The sections below detail the internals of the ACA-Py and it's configurable elem **Aries cloud agent** implement services to manage the execution of DIDcomm messaging protocols for interacting with other DIDcomm agents, and exposes an administrative HTTP API that supports a controller to direct how the agent should respond to messaging events. The agent relies on the controller to provide the business rules for handling the messaging events, and to initiate the execution of new DIDcomm protocol instances. The internals of an ACA-Py instance is diagramed below. -![ACA-Py Agent Internals](assets/DeploymentModel-agent.png "ACA-Py Agent Internals") +![ACA-Py Agent Internals](assets/deploymentModel-agent.png "ACA-Py Agent Internals") Instances of the Aries cloud agents are configured with the following sub-components: @@ -41,7 +41,7 @@ Instances of the Aries cloud agents are configured with the following sub-compon A controller provides the personality of Aries cloud agent instance - the business logic (human, machine or rules driven) that drive the behaviour of the agent. The controller’s “Business Logic” in a cloud agent could be built into the controller app, could be an integration back to an enterprise system, or even a user interface for an individual. In all cases, the business logic provide responses to agent events or initiates agent actions. A deployed controller talks to a single Aries cloud agent deployment and manages the configuration of that agent. Both can be configured and deployed to support horizontal scaling. -![Controller Internals](assets/DeploymentModel-controller.png "Controller Internals") +![Controller Internals](assets/deploymentModel-controller.png "Controller Internals") Generically, a controller is a web app invoked by HTTP webhook calls from its corresponding Aries cloud agent and invoking the DIDcomm administration capabilities of the Aries cloud agent by calling the REST API exposed by that cloud agent. As well as responding to Aries cloud agent events, the controller initiates DIDcomm protocol instances using the same REST API. From b6c02bbcb410009feb330eae434860d98c53577a Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Sat, 29 Jun 2019 15:27:34 +0000 Subject: [PATCH 3/7] Simplify the overview picture Signed-off-by: Stephen Curran --- docs/assets/deploymentModel-full.png | Bin 62879 -> 21156 bytes docs/assets/deploymentModel-full.puml | 52 +++++--------------------- 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/docs/assets/deploymentModel-full.png b/docs/assets/deploymentModel-full.png index fc6724b788570cf622f96f1ab0cb604d1d4e85cc..65e69d1508892f6f61cdc6d8a174ce8fa781dd1d 100644 GIT binary patch literal 21156 zcmZ_0bzD?i`#vlnsFbMSAR$Uf2n?X4grbNbN(cxNLyC0w08)ZdA}JsxNT*0Mbc-k< zsdV?y{ocbl&-47gf4t{&;2ieev-aA1t##kmeO=d@w+c_C$%tu)Pn|kNCiCcl(y3E; z_NPwa3!TM>BQyoe`S2fhJ1I3geM>86GsBm5r=$%n3~hAn3=OX7IbVBaXJ;jXL|U2Y zTG-i}n{n$~nqS}%rh^vVxG1aH{pa|pQ%-UA;fj`nJQU3%Ol$N{MZcZD=X0-5s#}>@ zMVgS3zi5a+$?$`Q-dKOAZBk;!h+9-jQa05EubqR6^<$6XvNjnt4y9fjY*5d~d`921 z$NrMPE;`;;=KIZV;`P4zXK|eVduH}Z0RhQ<9b)>Rz}{E2v~jN^-;xT_tkb!$`iCku zc{R?iSDqCt$a&8G5%re&0QJo4Akp1tzot#BYm2bE^Qkk;pkbjB6Jx<>|j zToKGqvwpt+oX}zYf|$g##phwKPSfX0%QO^~5#p8Gs^!nz$S=61>tml4OFsm%*fz!pz?I4PmbXWi7i0L{pmAjjGUzHIF8`5dc1dR9#9 z$$uCub22u)Gz|O{G6*$qPWTA@rZ%}t9zoTY>5^j~<-GbwEII3u*6rx108~!=kSZN zN#bAH|4MuDxsy?S?5pEgnQ7k4@cK)uPJ7Z?aaK9EXj!erxcyPF&m(I@pK|ZaR-mnZ zwf(%(cYKTU#_bEN`#f=H!e&NLB5r;A4%TyWzQH7aT^SVaFk$u$w#7w>en>12ds|{_ zNJvkel24I&a8KE3aOsnW8kOPSZES4rmX?gTyJ9C>tM_{I@CgWF zV`D$IDSw&g>wVGCUx&yiOY=BB+$JlIiij9vJiQu?JBOrF4XoeAuRmvY>m%irD{li_ zr#~9~bEdbwePMO19)(_O{CLZV8-+oyPd~Y{o|>M1_WAqxcndy$Ufv5C>oc839M3*x ze(m{m$B}V!Dheg;w5nro&re~yG|0cd+NjfVrcc*K(8=^sBk~#aN9&8^@fySN1u820 z#eUvs9*5BY^j$7PGMh&D$B*auvNg*J z6BBh-{#HqcTov;;s_aFV)vqDmB?nT{(Co#I9K2lkopZ-w(P^b>Tc?h?T6KD#>0NU6 z?;oG;41F^dbXe>+A1XEDN%NO-aOuS}kX_@vXFHBxq zedm4qk00D@Y%Q&=##tWBAtPFqPP|J9!I^-t1YUGXhV<hA&^% zITJZdf`7gx8#9TLK3p|e9+4Yw}aE?O|J|i!~A^ zmr&Hl$=5Ll`y6eudlbkuo=8UXRon;Uo!5s|A3qi_aD(JG&r|}D48Z9#nVNoMdYZU zc^!$fcJ!QYgwr(aXkn+0!{g!tr;?KyUDr}!nANy%-V9Uf_*BP%iEeM$vHSO`nWN)= z{SY#m+mC85=f@!o?lXMbMzUgGEHFRoI_Z?8D(D<>%_DOXNy;)K^VU-emJx%&8Mcv$n= zm&{D!8**6;uKX0ZKCer03JMD5X_Sy>dF&63gzqNjSGRKWn)UNc#Os`64@`S&eH)ek zr?azj=Xh2?Gjn~nP`{ackph7@GwV&xqnm9X`ryDqQW75;yR$C!>Q}PVxR_xUnzyXl zI_fMLS>sB)m}^Q94D9v2e3q0~{E?j((4H{E?B=>yvUbLOu7#+||LWP(Dc^sSyr&0G0xzr=JNB>IgyF7_9c3b1>K_70U;9*=sTPhMePHypvZ&8D{OE)IOX zv8&gbDt@#xq4w(4s~T*^v$Xu-T&+92p9xiucbX-NT{@(+tM?t+k$hdK0ZD=cpck6{`ApV1(OyQqGPCi%UQ?s|ykE0#9aE)~(S1->ALq>SOvWp40iH$csF6^V1!% zjq{I@RXy|kv9U(duTNR=3TIIN@OQh6)HW*%FrcU=DdcgFvf)Ino+^2V>^izpk;S8X zwCX1@nz03wR(CTvr@MjeJHNMS4WCU-VT*J*M;uL-lDugI(s4QXuH#%6VYnyZ^%0A) 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 SI+mR@;jUQy>4|`-J^ujg$d%^+ literal 62879 zcmZ^L1yqz<+cqW&Dk_2s0tPY?f`Wq7U?4CEj11k1bV^Bs9UveKh~x+iUD91DogyLK z4bm{w@ZS%5-tRr%`u?-lS)Sv9XZExA9oKc;*PeIx|w#`ed7_f-tQ*5k}Jf^>dPg4zu3;bvW_zuoEP9DlmuB7Gw3lSo}b-2imCE5 zR=)YwJ%gk2x_h~&CNaJL__-K&P5bBkNB50x;wD~B1tjS4%Bx!+9vDAh6!wmUv3~Tx zVeeJ>yynl2*13&?v5Fg9^oxA7Ree9%{S~cZl*)G5W#ybsj8ybHa%_NY?4~5M`pwWp zj%UAxhOT5#`kyJ7i>0O5l9sN<9qE}cqoVc>lJ5UF6pCYP|jyRR9%|)*?Rdzk(}E}DUo(}6l`8N zZ%*PfIoVkard=b{e_Y^*vD3POp^=`)%X4%m8}fFPCQIMoqPuD_O=jH^y6bDQm}Zb1 z^e(1Pv^TSZ^-#wJzP(-!n(_n-LBVm)s73Ate#RxI?Q2~di)pm=oHD_ig7;SpinPBf z^XfPxb-7hm%eKETQ+d|w?wQ$q=#}3x>xYrEWinJJl9*54O59WB@pHO^JN2GA^G$Bf z3)5N{fw$6kr=J>heU`X!`bmWOUc-cA;p&g9dDn#ZIlXi8)ysJ!F{@9RKG&>n8qt$T zI3rBo?`NJ^v~S(2Uj3sSZpf>d&m-p$*!8Ixu)uu0+ z^mJ#dqzD!f4s+f!?KoK6{A#e+s*Kn0xI5SA%|Ltv@9keKN4{dW%EnoBv$q7UoGN>K zh51m$K;Ppc)fYWthviZNZLTCVU(LxUO@-|a-QV|%?}gGu`4cXOp4g>phu*eTVjPf0 zQzv&DUL!pznLC>+e=2~AiL{wyj|shy^6q3ek4uwtqm>NnyMfd>k!jJ_-}=2fgPra- z-_5&P<$1O4Rg(6L0;4gjHX6yTTb8zMQRq?i?`19SE$?KKj$1Q-D>uKku{^7@m7z^q z+f=w>$eV2BQBkUYC4%PiWc?{O@iKQ-rC0S5(@Xsn6i+EI;aP{3GKyx~SiZmsXH$FCFpis@^^P;bb+PiC5bpl;`cwM8s~M~`Bhm5_#|&)J z3y)67KVcoIi0mFd$&A(MMxkn7n`OfxzPi%7F9!7+Ta|@vNrv_L zBBse>j=dS-%PuJ!r=iZkKc%iI7pVXICk0{epZ{<<{^!p*%%49?QM>*>fARnO4T}Ho zBmVbq{QE;oX>M2#SG{3zQ`OLLUY=CKVJ^0o;CAi&()eDYdH#d4=i#Ex+N;z8-H*S8 z3fgdM<%@H~6HP?ub{6}z!N0n1o4ne5TUJKqT1Y*M8)-m?yb#E)5Mw*htUp}kD=RBY z^pj37P>`42S>tCuwJ#5zT~q3Fo)ryQqobp1exSts`hzq_e5XtppW%^Lr_cxC<(rI` zraSnqUQKHyzN1A+-&vWjWI+We;|WWMcenalUK zWR2*oU;?4fWHR!uC+l@l(J)0Eru9KWxvp7Xwr56{6 zo9&ttZXk$Rii$Mup%Iyz$?5aE{O9LRT3w@{p&;?V8}5H`>-)lRHQul>yvSkx{48z| zj~s7!Wi^EeMSqFRrz_`JSzit~{yBb2E7-NEl!68`emot8(4EuaV_pKiaR~{!4$M0{ zSh{$FrFRd-W?)T?bY8(pZcSBXWj1NYx&B+P$I5oLLF8Z~YS)rSwVq#rFbWl$nQ2?; z&2;_xbrd%I`_2!P;^)Y$Pr0u$c%3_UZmK<%hl9guwZ}d&A>ma1=|7ut<^3$xXE82% z&$0(^y}Ww9Jp5MS#n|VzF++nU^(UqlfBpNQxOvpj&`?HtxIr@0!XYU~r2tznz9 z3TkRW;o%n781AxNIDg*5({mp4XY~b|`L@;CmZe>~6IP72u7`j8_z@8iffm+gvGl%o zFXGFWFPWJwX8b?<=>9B(esZz;`}glBbzFSM@Xz}3X!6k0ZFZN}-6hs1Q5P@TO*CH= z5$V?BHSf;#op<=Vhh-c1;DzeLjQrx_;(IA6S0{cNwx?*BOn!Yu?YuJ0z6eWh3=I?$}rZ4e*!9D>=bvA(_>HNR6aQs)NVut&sK`PCmyX5X*tO!oIZ_%aPDl;=X&Obe~No7sWfTIX+nCPcoE+v}viI9mUE z&5!QYt5?y{(FPeoIW zaD$ya{$3#Wz`y_--gv0O>wV>mzwg&eXnBTR;Cad&#H(Kex#}>{9Q|Z7J2LX?gDViZ zC+O&o@$CH@%k;6mrc59Raw3vA)eSj_2k!qT-*K0l6nV^9AOr;)-yJq z*fzrD(9eCVUdX<@K)*8G@rZA`^x2*n#>VOkQ8xwEsy|(Qq&~x&@G2s_vK*y^P6k|eygSDM!uCJ}X5!)=w zgHS70+UGP^MmbYoNyi6yapdsf>((wV@;!Qr=$mzP(3q5L2X zv>ppbEa4wWPkf`k@GOjw>(4&|EHK@O}uJo6lBJcGGw&I5dSw$;=6SGPh;wO^GOZ zbf5dStKFz1iz{oxsK@C}o}oR26Z;P{*pAknh5)7)<5Or^qDhfSXfd;m@|#b)MrayA7<2hmHwE3c6~C_8O_QhXe~~> zbnV);OP8e3kns9_Ez5>_dYbNA%Q?MHiMbzb2;F%$v8q8yYoz(1&PG!%R7> zDS$hb-t#X1IZprgJ-JpxP&&5CC|O*VTUAIz##N=K-v@lVqL?BOl0$K#g@kYRYpAIi z)RNv>Bl0RNRoo?Hs`a8L#y$of*S}318T<+KwpVL-u_@vX zKYC&?rFiZu^}=$!K%f6aySB6JutIT)R)O-``sQL|PF`aTm0^8wv})Wk8!M`OYg%}O zD*dk7X!%JU+vr+TeSLi=r~CuSRYX$WCw_E?8{trB^A*4ZJ%8SZNcV8N<$&!dUaR1V zT`j70M5C2&wXX@NsxfP>- zm^V2|vWK%H(?zL1L6)$Yr~m!4c9GLNC1n*AjYfMb6}m2$r5}>|LL3|%7cSf>KEi_5 zTuVSJ*rhjmd%N})I;P8ypjKMi+da=zR8|%=kF>5!(BGlocRi$j$-W%b^O8#J@EVcqvM`leq70dWY`HNb(9C@tQHPW%PqcAH;RR7zF?lx)4Gq(h1mWMgcO1tJ zw{6;8agNq;3F?m@e_5Zqa_yR;Uy5el;72UgHmOqVcitGYRpFEA1c%@{U*%Oi7R|1s zfW!HhJ*xC#jO8BEBZQsV=q+*{c`MGY!K7~kB{TikV7aFj%Scva_@t=@OxcO@k&D=M<{6AB9pm7_DU1T?RX zS!KYMJbC(lzF+Zbud}p|$VFq=!G-0KKplFU5K-ssDKvYiu)SR97p8)Q;<$LOB#~jo zzDP40LRdkjnbhf{Rod(Us0vz?oUN&UJ5=Ucs(J%H^u$#HboWL;a{!lHvrJX;){}X> z(9iCV;bxX{UVcrXR#kwxZ~6-?;-Xj56=%*NBk&XsOV6CRt4+*?pV*V*lav z@Dlr3ZGBq>$uRZg5)U=ZhO+uB?-&HE4(xswtw+uNefz3o7Yw4N&}%le!rvyZ2&T-M zC@A1v=r9WYSjB8;#D42rt9^-y*6EGr=n&WKtyTT4M-}hWRBCj#CRGM=QfwWS(p0(9 z7rMJOU7Gix*-_3l{_NIH3yX^Z_U3oll2ap|G?g8Q4PVCO-54J4Jh`*_FUQiK^BquU zz!paa+II?_$cE7=#7g@81I8xcMAnWp%2(ahW?c63RFNTA_|px->iOMLBoA z7wR-|QIcf(oP5VWJI_E8Jt2~mu*{|?7bX-feme-+z)A9bKKred*HU zkqO^&-K(b#rKP0-;`sHytEY-fdVO3}a)pZd;;T+V&pSht#n$BmU)q*ty4uT|uE7&2 zs7yAiNhDHI5?3Acck@V*uwZ`kV0^4(KtRC6!~`@eJ>+FSen*K8;icW%FGzZ47HbDa zdFM~zXEYWYf4@}9GWZdB_xJ0aSIxDZ@Z0hfd5V1=lk2o>Vq`RK>9!am8^Ukilb@BJ z-${0rnqtiR^5s6U6v4->d(c4yLh8$(Ms|+v|9WOjN=nM#(W6OHr5*$mH;XzgjSndAEWcEuimKr(smD>L&><~zrDT0F5s$)lu3{9!`ScA8 z2Js}Kv&do{-=8Rjm5t#^dX>f|CY?0B;-|OE$%Tgc`s_@{@3PNm{m|pB<`onaL=ac^?$$J5=moCaU@<9}O0 zy|L)Y7o_->|r&Y>&lJ%cVI_FhUvI2)1fDhe;+t~ z-NfI{h~ZdmPk}vc8f9nY+TvJz@-7P7_y0lH0`qg#D{I86M*X>K1eb+(v;-GuT;`f- z>FKCZ|eYY>i7$kiUOR^u55jxyOJWsiCTLp{sMNDVUQYL3=2Xni#s2 zd5nUBBwQ%=4iSmM!3_-N(xs$~auQVI#B3{{FQ2YUnkw3}JElriC#TsIA}CdLenl&F>W` z9YD|_5A8JPG4oh8HUWW?@Z!t+Z+UETX8w4-j~dqTc-J0^@q=T@Yzbyw?JXpt4oUCH zla=7>ru3K9haVA=l9Ci3DI(E$#MSTYUGnixDG!b*GM#2A1%=bswMERIUt?`Vg^`F* zg!Qehtxx$-5?W%hxN(xD(FIUiXytkd#F{n-Sud5C2eD2QIf z{3{MgtVW#3=8hjk4Hf)-BS-W^oUcKe>Y@F2%*>MNX%K!%c-cT>4*HhUZ7;FYEG&e8 z0R5ilk$*mBflQRWko>P`I&t#k5FX(eGn;$rC?mXp0I(pmM~E{neJdJv%@J+onp&=m#r&W6?hD5 z*6vIwV~e2WFX|iMa5$iaU%!2mWy5ooI*1^j$}kg50|)+e6}6QEAGT-shpjM!@s4(L zj*TrQF77Qr5%{^zN#y68oep-@+`MCX48z#zVK5t#WLRmykR+R-&u z*@A{5ht77X3~0qN78cwPB+!fAHylQ`$jxg}nN}&g!qfz|&9+%8F)7JkpZ5<Rj|M%8oKJn!4Bb?`-<^p1wEyK-NVgDhTg=d>U(N&Xb8O zt;pkyZAAY?uMgyXnA>%S5Hd3((CpGThZz@{tHRTU6 z#|eMxYBL=tb#~_T$j$$!3MejoMA$d_UbRf3IxMKP-dEXhhsztGS^C>mk$n!{5C9(E{7T16I6sS!t&#@jehZy(8 znf9~2krI|U;PWK+ zEQ5LmwY8_a_DD-hXAb?lBn;x8?wsRUGAla%7~zDt+qA-+J9kE;!nQSwy&0}g0x|dQ zC4^pi_*J0KG}CUhicHv zu-^0q4U4?!q@+4J8nd5=T4O(3tPwY*XLcp7B+xy7{ycMyOnkr_q!$z#dZuEi474gl z*Dlon=@A{jk02v0jnlEn#P(#Hhg{OHbX@@M%5>xLgG9{v6li9oxq(U_mK?F8YF$Bm zhW-|?)<0vhgloorv||3UsvCMf%%V@Ge+}eg8-n=8 zTH+NzDm`kNMUF^P;7Ww;A`f`GQQ?yjKznHeCe0xI^h$~H=aeCQ6mFUzIfI3i?IApaJO_KxW0sM;&1j(%a$;k=3*~?I&p^7g8To zZ=i z2Nnm|E|pwb_6pE3y_oS>P(|6uiusOeI z`s}G9yip>FMd3N$yND5%p;=U%!^ts$Rr2-M(PHNt!P!D>KMA4cK8G zV<}2FL2M}4_+1d)md5g>Hkq8lZ{RTA6rqd{-CL9u{^SH-J6e+mDH2R&jPt0j0lfEQ z{fg4tK@Zr%nyKIg_MdH8n_Big`j;cQEgF6>cb@T7T!M6g>?uV>{A4r+z3HPb_3@pL z()Ok0FMJ2_P3&HoFyK%F4obW8QC{oA zP+Ex=yqC&!M;E7pE{lF4naRVl5~QHZ?XB>J2hOaKS~P9k-U- z-B&scadhS#>30nhu7AL*SN`*6i0(djgW27^nN7P2ia!}752T7$vbZbKKW2Vk{DN)} zq||B2Kv_AtkNGmmo&kZZ(Kw(}n!@~Jtdm9SSLg{#XPJz*Z3jtfVZBm(|BPZ#+6N$A zO z#l~U7BBxv_ImJ6dx5m?3TU*FSm`P)pj01oJhI(Zc*ni-qSg3cer&Baw$SN9#Y)q>JEOz+fTl{8yBLnS)t z)zsH(N>uBsvUUXdFu2}er=0w4$t`2}ckkZeMyJSY#2XS04i2C*RUsqDmK>pZ%=Q9C zvg~%)_jL=Mae2#^L7;67T)|mC(E8p5msc z-UC}RFC^6O-hH*hOW8R%&hKpfP6O@zIy6+U8$GdyV0(hA1>^F=`wjDQ>JG>o$*tP~ z`TunEIf+epv}^xAkDhOeBDek<=>2K^k9$obYbsFJr~dtu|3-T|4Q+QLWOB(IhS7g5 zc|Ud0^o_RFks~_`2>ve&52HL}s9*`RE|T5@{+Ugor8rKnYyS(a{|~-7^dTlJ{I*Ya z2eP>RFV6Avw~>R9w(N2onVQX=`j48?Q;7sZwsCB1Qk-_ z<24BWQya98@g?NtR2J7I-%1tEvE8to1A55mZ&@=aWw$*>Q26GArH!a+U)6t4U5IGc z?9>kzXpxP{~Zh-ATc4Mwe*&$1?`%OiZ7u@?>!5QXK9r~iWn9o+kwC+Qz> z&nV{R0+Ea;6!RF-3o6;&6rkb-e6@;l*S*G|lB7CNdzn1uP9B4a>j~%g9v;V^LChP# z*m_~47CJ+_@AX=>4?7=KTaAZjP*C&?iI9mUDjm36_pTSoOa5y4O9R+cJh||H=0i>r z+*`rH40)80x4kP^HknUzlZmP84pEI_+l7}#K&)qR-<&5BS$|0RBq=vM52Y5oV69F3 zj?9y7eWsGx+>_Fy>CkSQq#V1KvcMDv@p3zXkuGCkzy<|tG6>|6npU26dpo;jN^&){ zu@Sb_c?p;g!rUO*B}mMj?04~=o{+F`!BApA#@Vsk4R1pO0U#jdJHK*y3{O_o9~&D3 z@pfwu(?bOa9TE{Q@!tX8!iPHnW&~yK-!L&t-K4#mf8Df=YVDN01V^MBBg^r6=z88RWrfL<8`YsQ_VxK+51N~!)r-hG6oc$Tg zL|~PR=ehH*#B!5)fDkWpd3ANfjKCgZVP@ukJ|-_uE$nAg(}(cz@X%0~!S4r=bl1G~ z4?~WQrB}Rl>)Ex+c}yRyg#?jmW0HjmT>dJ->#Wb1whWILqh{&zyM z8U=%;B04tKzOAkNR0(O2dSeZsbVF5@6b55#WV8d^5AF=&#n~5^Tb}zRJf7_>A`yA! zJ#R$w6fOCYh<)X3b{C~`|4)AQ*?9OjG{UygV)NcYK_Jw*xw)=?mV5JN@~Ap zG@^g#1tBUs{9ZDj?f8A{tMNHzNgw90tB)j1OeQCO#v;jj;H5I&PVsWXXG>&tW8uyg zzrH+-lJLU$y`<< zF&`bME#yuXK_2Mj>W-`3GwRsQ3F^|)x!h7f6!C@pc}~ldS07a%8~_MbpdidYR#Nx) z^7R`0@7xJ%X=&NoSl#$>L_k0QlDV?HJd42;nSCF)v?NrVs92A?Mfn!{-1E?BUh(NP zK=HhykNa=_xEz!VQ$>W`eCT8HhOGZbzvO?$iS(z;s(U;|na-;g?ny>>*&*+oM{4L0 zw(&rI2n(~fd(U`YxrZ>RZtjlH9&IF~Zu~@{?ztLd=~esfqoyxBaKvHY)yeOSAZB7l zOl@tKpgILifEx(>BWpd!lGU?ngf2yE9)d>4hQ}~C{8ytp=yp?EyLV-#Yb*1(Gzd>J zG9pe(myiNqCC$#<45WD^;!Tu^3(c zx#Eidnncvq2MQo*oCK3djEAikv#7c(%nBh9$#&xWx7Y(r?;JRI@ST?zT@csh%K<}? zC7h7%hBKX6IEuAdvge_D9eSzXK`Xs^Qy$DJ?qnC#w?)Ke;wNO-a7H#dCK{|y90BbR zBtq$m>OzI(p+TtMu z^FbsCcUw2!uICK-><=u++PsP`9Yhpg%$g#z953PoQJ4JPtfJ%Eno2Y9{T(e%+tAQZ zqjCZ<1MT*BK}n;mq+~bS+w)^ir1?XDQ<)A<_Oh*By?u{eAQ4R_4UzpAakAShIVFb= zA7&DMVwQOI&@2c=Ar|@X6knCSpxo{%+0u}adB4rzMQq*Aj3tP zPbpKu!aT1Gah5!nkaub!ZOCgDJdyQ2f9~8ZCXE9-VKzB66>YvgjK_A;G%mVt&u-Ht z^PW1?tZpZem6zxE_2t2t4f7CF7=&8;gS^-bV-S120$v!z^@jHtUbpKnea5VlVI%>F z8m6Q!9``gPc|=@DU*0N6NxhXyH&obe++Gudk&(8#jf+d6KwA=#JgHK0eAgmQ`>wb+ z=|R28eS~3*gjGZ2U7D5gkl|p`wV(cV4j5uK7gqx!{C&Q=^1CO**EfPu;A6A2^m!zb zn?5MVXWpPWbK?q>4)*Q4$BCP%adE2IW*sn7VYe};`y`LU@q$6=`Z5f-6Y(ubV1bLx z3Va8h^IhJlYsEnNCSpQ_(tBdVooZfeB$c!@h?X#kMIr`0V`F0>VM*}4)D(OMwZlZ9 z9Zstz`LX~BI>Sziy={T@$14R9x~Fk=8(xXH$<%!YLmTWU|Y1jnMH*h7xYA#et^lZEk#QhUK=Lm z%*e^!OiM~lO-)JB&N68QZCp7?HKOpXzQwKJgb;Purl4yN!(zcKlsH2+r)=lS`emA0 z6cg{-relH%@AFP|ByX-&S68nKOY&~_*!Kk(F!I1B_6vlHxuap!>|U7#MQ&rZM8`w@ z&uJLM@rq#8)YOcP<1=-lxj!_5t;jgRVrySeo7p5tFue{#hqpToajrqZfq?=R-8ZzD zXTjNEVq)U(5ITVSAlNWk`fgRro&dK96VqjBH5?8xMD0D&d`Ri_*s3?xTZ0paJuUblMOB-9(drl*M}EVUD;YI<3T22 z6n>J#?GyOJehxDycKrBp1_lP(34F~49y)pR+hZ5b+|30GjuuBWJPj~rw4(`AX#XIO z5r0;8wz6Vb$x4tSOKiNv z+KA3R&r&J~ID16L8fmMtAMkhuZS53if?_WLGT$6ZIcx!XudFqAS1m9U2OdB9-5nGb zCX0S*W4l&-ydU2mc;du~x3>GeHi2Z>!TryD~bs z3{ilg6A}`#v$Mef3!Vu*YKpxK`DfZGa^=9?+p_>BlX1&4f+{Y!TlA*{t%+ob+-1q& z;9yV`FI>0)d`6rd$`;S!%lTzYJvu zO%72!lK^pCON(iZqTy=R&A&TcUtjMdx}th^gNSIVV7n9G;xdDXE$+rzWrLL%^oh;Q zO_&tdsQ6;!Yvh)yfwpL>!C0osnUcHm6a#~am^+L|(uTyym90(NR#Sp-MO$1N}=-`ePIWJI&8U3FeDQ>#c+ zc@@=j!+9m?*D_=T_*Y@yAi2jEs3@LQ0AB(=AORsmMNO@etbTXrMU0-qU;)CIG6~Bp z=C%p;tz?ztSG9e&@fz+5{Xqhrk?xwA#5TH4gZ)?W9yO<6Y;6w zg>NhNeJhKz*>1S%7ziy>hp4Fh8o?h4Mz!?Ti{`}!|6?kX3br9+Hc+We zFE)zh$hiX8(Gx9lT2_aDQ{9$1rr^qj1h>NcGZnqERz60(M$}27Vk8ter9MUwuB% zn534`^5!EyRi?wd?wY!)YA%dOgB(CP3s|%7wt7P=uXv2lR@a`t%fs9IW2%RrlA>Z& zW>k1un@)geU2QFX0#0lUZH%YGqSuJ^zBRF1PG|I=r@x=p14mq3s?5FY%=~X{g$p&D zHF;6Up`HadbI1*=^J^65>EJD#o;F+91%kybT4GGhBo}K6xMjddn*mHxB}H>BPv7MS z^bqYcv#>c>DX9;Aou)qZp@_-0m?IUfzTvk(k}LM85C}zZ>e0Mo%;R*G1{VCvRQqgp zmj%MIcfx&RW#-QDQaanyvxgz*RhCX3Z?_{rP1+VC&(ka?#)U1soQWm47EWbV(lob^>zkIPX3l}}Ah`G847xxfc%V8gnp zkx`rsCzdJ9GZdfnAr*`S_L*)o_}|vEM32r z-?I12Q*<7XO;|zT_R zK7M4Q9v&U#zkYqIyV+GAH1A{!)3WFLCO7Z^Aj4%6&5!6`)P3rgU@N2Ke#3R$HVCoA z-K7qArlCQX$DeV#<2&F1HnsWEJv6GRmGW#i<}v!HR;dvFmey9xBtY2-B(e9uc=Fs6 zX)JIY!+Sv1c&==c3dMc@XY3JVFldk&X#%7#VYiZ!QpfwIM@5Cd{Kq2~&fsM8fyw!| zGmY4E=UO$q5f*yf|1?(A(T43-Bw*B2L*g*-C z*ki{Y^1V%DEw8B9F?{iA%O#>a43yvgu6b7FB+h%6NxyhHc~R!^ARZW#((5|AC4XKn zE}2j~cPhnEqoayPH6>keGXi@Nc_6GB$ci2RaApCB`{FO}pN@EwI5MUqu?Oee<+Nwy z;WVfN$h+>k3*{b0N>!AP_ZbI)UG)Ra&aeBBd)wx6u~c4OUVxYUFnj{H`(QKe94FWI&{Sk zOl1q}8^C3NlY{fJE?{WJ1Sh9_>((yh#~(|{$;G(sI{61hoZHDaAp$hXi)?Iz_>>H& z3qa(5cjfqaRytpb+668uFJ!lF0GFPZIFl>&klGb=H?t`#8<`5 zjr=D3GhDp@*${d{;IjJx?VInl8dTKPJK<&uz}IuSdBA9)1usdfsdYe;furzEqOYP9 z8{Ax5B(aWvgg1CHe0=pJ;RL?0A!_fweLXgfqKM+U4YgxL2yUYsIeO+y(*A{gMtEWM zvn-o$y;YeJj%Il7{h2jAN5{N6a2>a=I(UQZhwwpKHW>%VmzAfIzO=xQ8yL2YcHcaE z)=pVh7pf_@Ur2~BG{1#19#ycwqYny68t$oa9p73-nO_N#0Xtg<+@icG40lm#s;bVi zl-~Wrc45UP!Q>GhcR>Jwz^cJkHEwa@8qjt*kiWtuIG2mCR1mpz(f(}|fwbTlpzJp% z4wSJRCAVI41;34nscE!4hw{XqU-DLbfb$x_GZ9l#(}eUgas~NPR`2K6rgVc^pdyoql+}%mSUEZ92g8%QnN+-rQ@;TGv@D#jfJ+8eR5#rV zmzwVh+y7=|p&+!RVLPwM!Y|X{p1~M(2+y2jYrGMD!|FNpp*alpA%BQ|0ZtogQY+kT zYHu$PChmG_fRU9=j*mYFrv%Us5wQL?NvVA?u`2o32k{(4nO2r;B9ylkLp8x|t%7UTZ|KrEsomo@;WiO!LxFW2% zVn3z*_KOc}XLgdYX)P3(vC*|bd@qa+jDdC5e!N^@JwnyaINprXaa+^ZzhY}?xej-` zbdxVgyuZZF&CSUPlq8~-bDZKTW}W+a!4;XECh)rHXI~`45+))qPf%3Uwc`Eh!H=4j zpT!98-@g4I2q?`iv6e;9`a1~8uU{i2+|bNy8cecl+mluFaPpLg8CWCZ>VW(H3-K9d zWCPY!!POfq7R(lFC@I|yRB77vPtPp9Oq4G>b$Cbey_Ik^zHSbadcFdf%7th#EAgA| zX!giUHfJug0Jn@UcrxQR;o(6Qq+IZo11)IU#3pCIxzzz=(s?FxD#743*Ha~<#=n)4mV;!P-c?*9o+bmVI@gVQ8$gc?B+ zih%9?XEztCf#^z40vTAcsSWwad)X$*wb^L^K~$rccplb#XR_~ps6$!3Ng_Gq7e2RW z>L_*SXM|B^ZEY=_cskw{H~FCaAb=L+Y87+?xNHD!Nhr}E#iJLhE|hHa%zkx)OL4Okfi#j8&-p01-clb4b5{};^D&q;C`%kyw3>U)+p;juwP8=%L-TArOTJS zpoIHSjaQ=r$SF9@9PMLdtnsx|hXx7WF3mCN_X9IybV}TEdY*(RY9PV$IpbziKjXr} z0yHT(fbzw2)u|=q{Pc93ro4zb9QSz-9Btp@Z->2QQbEv-bOfh}yP{&t_>U;O`$M=` zL#4o>wyUAU>xqJcX0qlD4GZ(>)9hVEBOf)+%>07AIp<6Vht|pd z)tEm-j7)lDEHPJ_F*F`u`*Ot<_kn&61`Z>?`#blzl87zTHD3~wxl=`~6~nPqFz26C zkaQ;avyQA0@l2|Kwzw|PK|j33;vX0Ym$g37^wR@_3Xr%CDd6-Ag$)zBsC;&kG@a21 zDye69N5P=$YR@Xm_B_iV&3QXWC3?;-WPH7~QL?#ETTxL_+u0A+qI5CJBWFJBx0jdy z0aK@PF?|9`HkkFJsf}h$l-zus&Ln;cbzJAr)_{^+BJVeps|?VK zOBW;9Z?T}+&ww2nZipas_VHun0FX;Y`egWDK3-nE;!Wfh37GN+wsj;s*NBn+Aab)@ zGrFG`PX(?CAXHE@NIY%d5>nN(qy@6BkPb1M4&j*=s^L1$lX4F_WK_6}F=>OT(mSXfcUs=@0itte}y- zOxw={0}}agNBCKP?$rKR)sls?%B7x<`?;aH%3`r7KtZbsV{od{1@7P99`m>Z^uQ8? zIC{?!C@=+wlC9tx>Sc}Wx^U4APzx2hd0{33ycyI8syWVr`1tD1Q|9Es1elEV9s9Of ziwdyu_4DI}?gk@|`=c-qZ$pc$4dWqa#O_Y2#YZR-^ghcy4usu^jfwH2fh$L?HuCbm zyvnlM0@xd!<}(eL>W>f#fKFGv+}lzY*=%x_-Qc zVk2(wl$Qji2rT4ae!BYY$O4RoF>=ywNHsw$Jy2oxWE`|!Uowub)`ogLbv`~OX0(ic z$^NH_*mqNHZEf(@F|;yK5Nzd4OcJIgc3@^1_B6lQC&gS~Z)x70S+*0nAaKv!BtNx5 z%IAjW?*q;i$KP!ph0WoDcwq6L>>-V?Td> zTKA0Kw{17Dl6*x`6-C8MQIU_HGNVLAwHBVhHi6_J0@~>VbX=&AUBUr9P^Ui)1FDn& z`tj?LPZ-*jfiTSlh;3*GPs^-PlZZAc+C`mkv7&EtQ~<6SbmrTc+Sz6K4~H_%T?h^d zK^k~m+@<*EFtHHHe+7;F>TSl{+kmBHWDY@v$<`7TTW(R{6%!OZy|s7)eD#5avKUN! zLIN&;w~2cmHs(FUb?J#AA-7G3wG-c3Mr5aW(CpJ)IargoFplytayUlr_*OOgKQC%z5aM|+C zC|vwpY8;X}$L%N#9ssaOO-`B!WYNm z%YKMKTxLOtVs0<^{z*NvZn26WozfhkroMnx+>EaIEiQg^Gaq{zJpoqnQO-L@z+!9I zM}R(3)1g#h4Sn)(T`>;SF9k9^ojBK)D>!dR#IprVw6u4`d7I9{SlU`(=bnNPV_uoQ zfS$X9-yS!QYhsa+k^USGaLXHkci5MAJ?RJVDIY$3`0U+EL(*eJuNCxddCzN!qKl`% z>^5uzzW&Jq0f?t(juGzb6S2B}z(Jw=W@=limn>oipbc4BSzWqlw8$M5PuYLWpg#B_ zub}St1M9?JCwSf0YIWqoMI50`J!SGJk&TfHOL!T&%O%~M0NV_id7i9SIfUh4>xh!1 zss08v^2yxv;ao2ciRbYZj^z6tPP| z*^pAz7)*fXvKJP({Y<+E;w5P@gd2Q1s~dEFIqNHMrSSYKaB+mluu1D3xJg*@T=>b7 z#v_t-^FjD;IOuqH7_OAj87Wk80siNh2eA8;X&_P-#(+G@YJhodzoC?hp}E1F1N@dS z>Az%gnx3BDxEV>=wCB_(x%VdBM7jsTZGB?0H8FbY@?i1}qDwDa1%*o+&F0h(XOxI-VAJOe#ico@(B_L_$f{fQIEtrZ9t$gu3wj%y#m!V6f2&r0Ci7A`yfCetctjDptfn5oVPKsj2a6 zFb@8BapbY42tCUN(LZA8=E_-Q(0X%?n1Bosp?F5vzJ9uZsll?G2=AQ7$O))CsO;+7 zbsG>I(0kzE&3JSNhpzsDjjxP6ukm|tCShe48CP#ZpBu5`9q_#hN9pLU!_}N&`r$X! zZnu>6*Y|mrrx#pi9g^9`gSl7@YhVzQq^wqAc8Ql4_e3V!V#b4C_z4*PI15x)-BwT2y$j!tu)MLp6W)WbK*{~={}hXJaLS$o z9aC84EPCQR6oQ&jzdXK`_)t{pV5Zm+`^D((p3mF?{|m!t_!Je?Z_u;Q!ob>Fv*n{{Zk8 z1mXu<#cpRwfN(%S0mlX!oBR#Vh}? zup%f|J0m=-NH@5FbxlA(^6p*VzmGfL z=enwe!Blgm7M>TpKmc9>=<$-iv^13!`z`_z*$+x&YlZxR^me1;?Fh*2-gyj%R5;dz z1z+tS9`23XjmAI}fB|pMn}0N(QdHIyUSD4y4EW$bK7z*VoA9D~w~7+21wj+1pfEwA zBC)G=Y;zJda6xd5`W zB-S|>+$t+8YbX$U$kpqUcqlrj;QLKv5SxUDLr4p9aU5){Fc7qHb1R;Xu=CSX2LBEG zn8Wn1t9CJ53MwiB5R_?^@bwwyUD@%AN^{kv5ou}iyhI{V{P2T|f$Jza5EGesPLDy@_eBhO)b^Om@#wuS#|!2E zbEIGpQOvV~l*f6Z*;7t|DfKt%Cs>XFZWjeRwQJeK4S>%|=MK@(Y>$Rm`s+Zx);k3O ze9l^@b>S`D9<`-jJBXg3a&&Y=!sSGG%1PKiqcN)QC@s{53sDIP9+e{sS1JF4u0yZ0czA;21@Y=<`XlUQ^ACrA@hz(|h>dL|91 zkdP2So6hjn0(K2R`cly;h20Ah(d%OPx=i}71K-}Th`DvUg^^h4SkWEqtuk_QN08*} zhua>L#}%lN-4rBrgOz(};-|R*w6S~KmwryQB`awA`A?q`un5gMs;sC8XRm7SZT=JG zyfhAM>X)u5^;Nt_`|5Xz&@2%OI7t# zP|!Op4m!Si%pakpP|tuK>Th9LjI#-G|@)^@wB_1(T0ZPx{% zN>YJsJY_Rn@iE>XXL-(Uvq?zQW)9PcQ&9=@^Sk}0=B?ixLdVO^{z`i^*Ix#er^5;c z-Z1vQFMVJyL6Hr@IQks9MFA@tCUpG+b=^()hKoIK%$6U+cNtM;bcldKspkwEdcrF> zY-h6TlJ4d7gRLJ073|5E!E?%Vd*5Ocz7j)Fd@G9Vsyj>sY9Xn-7zI+mdf7PfX05@BnQp`=V3l_)e|8$&xqLNXx&218`# zT~E&UobUU4e}BE7&-w41vpvuK-1l`~*SgkP*F+fKy*ok??~ztWn>`;aU8!1rb$!8T znmVt|{^69{sN^6F^LzXaZU3Yju?Mv6|f$IXOvGOdQeK@9dSZ#m z!i8N7HBf6klkk<1aKxR3fPjtMIz%=!I!(|s_#@4?!Q8>KHqQr{RKxrSb|i-W_HM$21r zCjDzi2PUed4r(@e7vJ8b>JTebsEBGNMIty=^k@#n>8W)lV2Q;u$96sn1^XS?k%+|R zRdi~laJFNdUes5kiaskTdDp??->FY)?l(nzIf*oj@hMBeQ{Q&a%J-@AQ{A6PVG9#z zEGKGXCV(d7e_;^ptF7mLmV?Uh5tr{*G#3z;P}Cr}o1)mIo+h_E1+5y|A3C*a<`@AQ z&B`#}duOC1pKe>MW6Rl6DV6hyYGm|P)H>f~z+q0#Z{`T;aA-~1W5)WQkrDdRI8ul{ z5@GLv;8w7`dr5(V1}^5m`%cGg_#bhI%Oz*S`+3x-lbO@(qRgjJ-Ccd(%hz_fxAk}Z zCVyzvmo|F8LE$W&8com=aaxI|T*0J&PtAGkQRlkkh~{P_vg5NoXp3}pcQ@UWg!&47 z_U8{aIkZ00)0s2+@EfK3fA_?n8nv0=FNc-M|2(V- zlY1s>R*_a71fJJuCtZo2*@TmmO~zV&QksA0(2_4%Z&l7`Udn%v)m*6axxaj&zR~wb z%YWCTFY($@a0aS_9e#~{SFbL~{vODYDHW~gSR11NMPJ_8&Jvu4#sx!J!xsO(^ns$;V7M)Ke=^O(073!5+p45*m#15p^lH+>ZD+t-j0X=b>d!Ke^ zM%r?sw8ctZyC9*gsA!tJP0ZTnusu&|fu!o<7Se{;0{QGpfeR@!Hr-DsmC|S+RCFSq zTo-ROd|xS$>vRIv6(1a!Pyc=t;KWUhjYKGyq08;wZLGtimGn~}wyZ!X)9u5j3EQKw ze_HTzJq(?;!9>*=a?O^6ujv*0J8P8)eRLMIkx~2`H#%6H_V27}VPCN#%T1%fZRys+ zkhN%79+9Up{(hR9S#~XQpv=IH2)Ey&LaijAFlgHASLhtb!- zgJ>bjEo@c1+Trgm>J*0zL(jFCy4X(QKyy>-8+2zZeFNT<4-0|0|Wbpo{Ac(mkXhlO?3oZr+v#;s-{^ zyhmEaO1YL?p(uKb-lSaFiKOxXnJgQnOIy0+Ii>%350o1{Y+_G{#p}V!M2>nWUnR?M|wR)Oh)EyMFr7+-89kjC4ApE(38)u zfT!Dg`Q}@7CU)k+j)y1AOzacc4-g&F<8MKOUctJaE&coV zvbE7N$EJhLjhfK{Jf;(>DO?E&xDp6jC;pdUYziA1tvXieh)6jH@q+ru@z>G7CopV4 z(~q8s{8?UJe#efd^B2b)t|cX>oT`tLe}10X#uqBOVdL$lPQ}lyJBVeFK!JmB43e0I z@PE&>>Oq|=kW4(+k06SEw7k^LoulC81VbJ(=LO~jR)0foD{7V6j~`9%Qry$8NJ>l3 z3Ka#VeDIKnn0`g_e2v~4wAMLxEow%46JcCY_4c;PWO<5kNY2D}i#hRY9Ze;NlvI+k z`<;-agv>E7MWi^=Gq)m9oo5QrraXs1qtnsX{}YjT#hysOj!g==E~eL?)Ye$LX3f^0 z;+c!+1B?zI;&)hy0wW(C!YD~>r5*;-kO{OTVkD0f>CNUf^7Y2^^~=HM|JE<2O$TI~ zL5L%A4J0V<$?}0MLKPW^=I@Sg6FvVKoX3{m_wH@}zTwRIhF=>4dFYw{H-XU4IPRgl zR;XKm0}i5l{_W)kX>suqECF^6gcUo+%h{4OnQeLwVNBqcZ>y{6U!zW9>%|Yc^~POm zeEl6JY12=ZXIr8T)W5nyokpV}Q4JM46d!*KqM|n|C{kvov4@XUhB3mzcA^zP5br@I z$a;SlnHSO1@|F>(>6%7`(k2YB;o(C+BucFV43r}P_-|2dI2a;&MA*j@ZeHWW{+?6* zc+modZ4(ptC3>rUPOg<-R}l4DENU{aEvU13zZf8E6ATkY5-zVksziPxc<4ur>ITpFQ7r1< z^a%->>9^!YldhnCUv2?`qsGR)A0lpspL0eT{3|3j_L#Xj(M{VhOeQ5!*XeXqq97o8 z9vbqV^J4EiL9NMe+Wo)szDu8xa&99DtXpT+Xa1cg{8NcXZz5jwsog1*NmM_FMn3d2 zHbUe3=19I(8Jo#Zu($>i+QMT(r$f!cUhHujMpkX#^O~%y+w;;pS+l)e&pw=4|I4C) zK7r*bTcvq(T|#9K?o|4rfXc5g^1Q_Ko3uhfJOaH1(3Lq@;3#z!&0TGO%Kf&vAa>+d z&riU3cRl}@r4EjiV!-44=!-+fJH9Y7{Q-Bk7yq%*^>^S;CwNoY@v)f` zb@-6e;b9=OS>y35(Z2+UE+{Ij(%1bn&)BPSlqduN4^Q?Sb$t0HnrbNj?Wdk@l$^%1 zEyBrlr>46%&55#nPa?jINupsy2orX4F+HhIA8$kj15RphcV?Z4K7nf;gY8pbU?99#gJ)hOs4ceSW*BM<#OIYeUK6^46V-e&sb)^bMk(-Y{r`Ry-oK@Gi~A@YoVh4wc=%Vdn@E5aPS*=12=o&z07#{P z#WYUF?vHZoELl3`!U^Rwb{HesRF^ueru<&94^SvN7a{Q>AzXDD-~QGAW~Tp0jZcQ? zPwO_Sn%!>R^=;PYu0Hj%kv(T>5pG1aA|z=6wS**XhhONA!nv7&mpQZGGOFKES-~SY z_`_D1B&;fYMA$Jba0zo{A@EQX!ofzoI{uW&8}d~Y*%Ww4jeh?iVX>Au4IJ9jD4>$Z zGd&DoW0jw}2hG$p2)rgKDXE{Xok`hM)o+>-dg9!NrXgz#S?^%+#P&~S-RAkv3z{kr z83IwY992HorA`h>=|L26w$cLY8*QXwDRnKaqY_aqb_MW-1Dt@KPmg+@*$CP4pR8zY zcvzUpwXs#(3AK;Ul`A+s7jRg=sfc+rz4V1+CDO0k$fUF63ZFf@h|Y}uYnKaS#-e_AA82lXA z`{2b3vpWq$hL!Fft{Hv(emsHKqfH#K^EL7?&z7|D-Uqh{dH;k03V54$@;VZ}5X+su z>EDT}qVjOO@-`x;P*oG}f*=}qvfTrBp5J{bWL+~Nt0B^-pP(C~fJ2}O< z?}f7mXfUHPrc+gHG7#)II3u5YC@J?KcKOEBg*rZWS+qjq2hKu|M(_?z;rdWyTojxsFaH z{6{qUxpPoRMDI9F1>25*T02X8t1btW|E0J>&jH6dFdHN|PjBO+P5P7W2GOU|cwPyW zmF}|t7_Sl&ddLH&$;5+ptLAVg(EAc1lm#v>f>Ewe&XmgDXhv34 zR^EXrEVR6NE(2a|GM~Wx$4f3NB_sB?=NRl?%xVoiTuMG)a}=|Hb~UF z!jVx_Vb!{Iimrnl&{9A@kK;NwxwJ`WmtFI{@_5=qS`zIsEo~W#?wByDa-RI(Uw9Iv zBfcI7=Z+-|SCk}zz5iyMJU@gFv8^7brIsgOd<9=$2pd{di>`u%TT%8PseIrsBnFsD zDo1&~smnw}V;zu~{f({2!^>Oc5Os8w5|`HG+qvntxc5!6PL#cw^ZxPD+nF)iHmCaG z!k!H&7p`pSTK4JQZ_RV%4|8}VpDtmgacDZNxo&iG#Nftz2LIztixYBhE(r|2XX$Gi zuK3YP#U}$YhbvqrK(=RK#AuX&f+PWW%Ol0+5=#vu0B#7KXo@uBM zsb{Cxhum`A8FKGk-DSD-5KxGY~0Xjn=NaoGfvCg%!rT2(bzBA z!8n(YltkVVY*2T?l4dNttomgdEJMb_-W=5mi-?fmX6rw`xwVKi!6L^+mwWs*%I;cB zB2-OTSy|ZL85zM({Q2#F2P7N`@7+AX^)e6qQ0imoR8;>oTO$oJ&!kmQyeAtr&Gc%u zuUMq;4b=Nzg>$p0olXQX43Z36XD^poB~7!8%+{>g$7?e2Ht6ZQsTXK|jy$WLLIPY0 zL6cCxNMFH|-YPUOQ6lK)r9)01#Kn-`-mm>((0h*G;xaoQzkAq|fJNGt@yDs!f{cn` za@w^Z>g%5OTgPc!mhuU&!YcLy?0GELN!atLS{x*``_WxQzOTg#-D|Jnoa3tA0~5(xf9uT@C@p<8RVHS@EzP#h z_ZEM6{PE2565n2-gQ8WZAAaqXZ77#Yl-VO$hE4)n?#q|b<(>ttKDhNTm0F^<>3A+5 zETjyQz_f@zeqPek#b3Q7*~6@pF<23~l~J*5*|JqE5=Y<8|XwNMDs}C%d+r+r(#Y10y3N#nsNW=9}mP>anW|gEb3RKtPoP@)T${ zeKVTXyGGY)yR)z@_w~I}dP>MQ@X9jA`jy+=V;)mZhK5WAGNvzZs6;u7c35TXZVriF zpu)d2G`5`O(WvD_YV_l^i%(eJn4ENvYn#$(kNCJK*R1yIo|_%SmTyXJ4!%@ADyN){vt7EYd!t7$#TTpq7wY7OdTsWEkMbp~Alg)qJ_{BaxHd zv}Va=4mr`bM`HC!F-l!8tUpQ?w(+X4GH-<>L{VIgh{V zb+shQby{=X8;k9p(87!ZDQ<`wSsLZoFA>De#tUkuhzodmn1dwm~R70t3fl8t})GS-Q6=aO>yLGon-=*f>+yjrNjpY^qcA~Wam zfBdlrw|D}5Lx*pBPveCJgX)kfTV|Uwy!dG$cVzD$(is=>xOas=#U}jH0?!bWy&5S^ z^^2dV`>nBpr>+pAqB(c31AFxL4CCssjCJEebU6{AYbfcra^m>Nzx{V{Z|=BNcp^=4 zBcNayQWUHAR2<*24i!=JSz*RO{E?J<>bmc@P%bs7w6wM!sI``l&fVF5Tf%B@=V4K9 z_m_9QRma}#s_%2wNeJ0*ojyn&r|F7HNR(Q+4p#KITqJSVzFJeKZ4K4csA{^ndm|tF zvOkjEZzSIyGB)Px<6sPupN;-B6klt)dz_}edp&2cw)$bkZ(B+QW$$G|wW#*FEYYpD zC&(!`#=YrNUIQ5B_*{}v*-NheVkj5cW`F(m&AO6jC4Km#YWO$7gd3-Df-2;37UHR= zJv@}u7BbmkYA(X4KwQY# zW1K}KA6ej;1_pb5V;c&WIT^A)I!^6T$O~J}IFE!nSE+UB-#5OP-*$Z4X3jyf^F`}r zJEaVCyvBtdgc@r2H~11lT(zkMrQ2oGjV0N9?)E^t z%F4Pm3$sn8n}yi_zIOHG>-pbq-lfneZIzv}Ezb7(jKIaUyu81yH9qjN1j z(Hm*s>IfX_-Qf(|GZHj`aiIhE`a^YC<~NVIju@_r)oP-y9gGjJy_Ak58NwV(rQ(>P zqGCKXsr9F(=#pMW=r5fFU~xCR8LM4ImLatb)fU28=MXEiV^jN3>cfgtF)lRBHN2f3 z;$e^(Ozq;ye`j#LS=sV+L}#ZZwek|fx|3lR_vFc~_$v}7oUf9LxA1oqUx_K{s}7z% zWRqf)!pIk3@yv5)|K-58h~*MRPx|>1_<)f8{BdzxOYi}?L(^)%k%)Hmk838_0k^Fi?|Y2p;KmN?`vsKc`n!`i7g@>_Xw&7^UqHO38+rMoWX3v;o09sa_k z$W1~kza>a`%hk=xz+6BEhRe}wX?RjS*r7}1a-j{u8ZPs>d@{dpvuY{H1URyumzT=> z@SbZFxtWzoh98@nmNI)pLoddM(m#IOsM2>aKj{w4A9-s-4wS6+`TF%lur~MYm$7}Z zN#d&--G2}}1mw0dNA8Z)mdih)bubPd&oJ&9l2{2zl>q``Qj@LS zk+b%8`!2FAU0DE9vBX2&s71^khv3FjJ48dw#>nC>RvFEmXO~OKsjXRjBK!e0DP}rk ziBQ5v*(Kv!W$0Wg{1T#O=u6BW=c{UMMz$|U-1khT{rve8o&d_@TL6_$H9;{7bN*72 zLV$ka_UM}Hi(NeHvFf@Pot>9&lPlp~oYJINVXxug;qk=@M6=Z=j|$_ik4i<=KJo~ zk*j>WWA&2^O+ky%I8dn@X=iW0*6#8Ze17X*#&mC$Aih&BOSU&c-J(qiJvEM|x(BJ^ zVOANDl~VMOKz4R^gLdw~yXR|3;K`x#@qOX^EU4^KeXL2NB7 z6(#OsBSh1inm)UWh%d%jT3RX|7NEHQ%FVr?h1Z?xITYtPJMj&G6Si>&!z?~AQEN-^ zai%fngwWy5#+|wyR(ripZR0DM)+_#bQ6fyIf6D4GtYq=KgVs{)JtxP7a>A(65i9I> zs>v9oz|;Q9N#8%Y-|jxRTY~{fyxg+VRynOn+V|5cyqH3P@A{na1+*ovFSN{Rp~;!_ zwu!a2whD9k-}@7?@7ZOF$b!U%Bx(|pOB{lVS6`g`%d9N*KP9jC@%9ZDe@_g@$&(bd zGP{}=4Ruk*$D0dYm6A}ui`~QrmUq9*=PDQ_bd3GalqBoPix=fbAFUEa+@#rcPMUX> zza1ps-ODPeo6wU&urTeL(~GS#?p;5}%@qbL?A7I1V*BG;!?Q|s&8y?|LZdi?NL`Wk zP3uIN?^ffmG&K0E5=2>Jed|p(P}P0wI~ct`GBw0}pPeWC)tYR-*u?$p@5kqY0fng5 zdixDx6XWN$%fG#&`SqD*aqa3$e7fa>gX#K-4~ARY+r4W}7H?F%)j?EB!LdQiPR8^R zoj55On14>liVLK_%WM0ELSn9mq`Gf)_bONcZMp@1+(DfJU=#FmC6Jmc>!^PKi@M^N zG{0ZECzmw|x}HBoD>QPOzujV{?t+$cMuj{+_FO0LR0hg*!WT{mNBSRq;zhfau$qGV=w6hHk4Ry*Iuw8ZZAI4Yk??T zzl1tma%3L#VD{;V1$kz;4ltaj{RAA03+M_Tg&c%!P@irO&4ET*rvQn$*9~1Pd0mc2 zPq5bR2x-o1)_CNeKc~_+FIyw0sAwx6oj^T(;6R|6^jNe$Ymj{hL-rcmy7Si*K;C(_ zmn_YwxN0Eh+ret_tp%rzPJ1fK~nQ!DV9DM&9Bq+6)TmRbSNb z$0CDfXT8F7G?u=1+?xsHF3FH8t#+Mwo1wiA>XzGU;I5^I2p9~*GwAnGu_oIKy+0G( zx*0bZ2BeKT6&lR=0jVlRd6PzN%zM!25xncRolx$py>T@SA35_6XuipqVMlui7^u)GgGgs`>hB zI7RME^K=J`>Uf!q*EzIZAhctGkY^uJV@8W4slsU3u%Vp9Buay%c697baw~aFQ7ZgHSIL|>_@rpc0xum_SPF8(<{Rj zXRP<{*Gb^IAdPk0NQYP+En|#WTWn&(J@CTmOC?MhKi%3WurA%)X0#KO!@>7 z|DX%PKp&&F;r=1l@(ZKxFWb3y?=W8FGsMoxncQSsxvldpM;yDRO5fO-rI{R;DEfYt zvP7slE>mrL;l+AHYd?+7DWQEbh+0pnF~7<6(FFVXwSXfX1!ZKy)~Ex{U#?iDPfL+4+1lcu>`Q={pzaD zSntZN55_QrhVJli9(P@>MdB8{j2wu_D4?-%eVy=f^ukW6Yk*6bWb=nl*=Kke)+QIK zB(^dd>$He-vFcjsJ~@S`wIzKU)N#G=CR`;hJZ+5lFi+D$7`^m!+a9UAdN0>JrkD%l zE5xU#ugbSOUq+hI#~X;t$ZVRUy1OfGm;g}MD?5|$##yL&>gw(Zu(U=CxXYsFews zgvGZH>-cJjhv~bv3KHkY+q+OF0aW+E)Tz3B6gMeNwgWf6m&^Bhmy)(x9Y4NTgdvEM zt%%>YQNh%8S8`uN#J#bJO3tEu0i%M?%!%w0}vZftB7_E`(=V zXmikZs%z}L_}V4?t0=P@O>t+(F93^%$6uXD;?A;y5M-b;9=~fRva#R{g>Q`N^%~ix zKQ~Chvn|;u7W?C|HnkYtcLxqV)E$}`S@mt|SP$`$bf3i+ z5;VT>e1eS~9{uX5c@o!(Lh=1R!Zca!8$S+;-D4LETI-Ion%pCb>?Zyl#NP47l3yof zk_O3sxXJp(S^JG7I463p_Btz~2rrYVVs{0?y8Uy`i)AtWughuUVSMW(i!XLRx-R_V zgkfcnp}-1D(w>poPDX~!P*<6;)hc{zEiEmU#1C2twF^f@IvMPLRfWcv&ae*`k&>1k zPBH{0w|*v*=&-(UbVEkI$-+}Zh;b_Ys@SqdU5r{2cFkCy)0%1^sw=XlPzhu&BFm`> zm?5&9;<}F?#UrYwrlxXThv;`+?r~$jkpCl~Jok)m?G=h#lmb=BiWS?0K>W=d-4gT_ zM$PtYyq%^?En9oK)Nk2=Q&vH*YLrFu!$U(K8H#TlyG<0d&G!w(SED-V5U+mBmD9?> z#N<7>QZwUUVSy?YySYX^6+TQErZkOsO7R?+c5qAR+Pmk&lPA&xhnxmP^ERAHUn0Eh zVt(u}tC$T}S{yFNID_YA(lmjQ!s4rVpMy%*k)kd$E~>j;6t)h(wvhHM z#sDuN?FyLCFTOOpO1n$aje_csRyV+K%d#5JX zf(5XFVk`zzp6{K)psZ1n>O=+H$xMzN^MslSenc=jcr!S&XX8p%p;5C6@gy_t1nR(E z9A~R-cTqLlR*B|}f8HEx7NdXCY)p(uY=;+Ps}x!x5&JE<`w{O^L&H~s2|!ncMg=+; z-FWGBH zh_<(7e=b-$PT*I8)E4)4<POhi>ues$vCZC%l9 zB^R~Ll0}7e_sX3`$e+?aoG;to!4SL`kBDF1M4mYI)q`P;ZWj)?)iduUgdY+JmUDYI z_X-azi#Au|=GkF9&Exau&&4D|oiLJ;%-imvQ#kqI{t+1061sto z?I7-BQYcs}N%8SZRQOk&u-|y1l3!a0n<2ywpf*XQ7~L!40z~ey#_Uq2gr~)1m+Ynq zyVIvlApK=(68{35LO!}?pa+&)y>utc4BFp`Nim02{1-OG$VEx!`DgpvS)wGmZ+r)n zRhit2aDWW=61Q{5;vPZUZ1-sg#~(kQpLgaRUxG`|`*BTanUtt&P$(m9EnN4^(|BFC zA>To1{U8|K18~PZz%caT7}yr1!6D64yZ{xM$kGcR04ij=J#yduli17)9{k!ZzZ_&ds!VQrdQe1_ zn%`TTLp}x}(SSH$=G~ayw&3=B7srz)NozM{>J-KvP@BSx!=$~MBB(KYrwFB|@~Xu- zt$HF!4Smmd$^_fG25Zx@(46b_Ufk}q&xW7a-vwee$V;PTLT>P?@au=7P@H(We2JE4 zNM&Qi@`<#$Ng03juCu%K*+QN@lkZQTg?*EXbML$HBX4d~<$~KYtXbAFkqe3M^HPeq z`iH!4C(S9BNbkp?<$Zlx_MXtgMc|&lDa(FiCLKXdqIK5qBSKJSN5g$SA_NV(EMsA@ zk>ek=G4b%F-^tsciV*Z{*{)xL7+!&6?#xB$%M^Ez z6Yi%y{_q`N1KkTsVTReC{p9NDk0ot}d|(@kioUbW{?YPr3BI!Kmj6J(L5`eOsfa3+ zlPzv;Vb(^>M+4U$I+mvUyMauQp6+E52xA;&Cv z%#UKT)8%xr5b9gvD%Y7P=BkhtN2DUxL*#ex6qEh^utq)Yo&B z(n}-*7_V+jVz?7wmInK}_)u<&lVjnRz~+kb^6TblA5QI9zoB6TdMFCnSusbIIEai_ z3Y8$t%!$wJrIxFO*3?;9MF5Yp8a~Ed*k$1|R8ekv|J7oW?+-#^bLz~Q-^biZekv1S z4D5jf5PXCaSrgCzj)c-eLPLaA$VZTe%2ups&T-sy>=MfO7CY{@UBx9oM$doB;|V65ifE$?!Ll6e_f&h0H% zXUjg25VhKxn|t#|^w~X!vp<+yKeT!+!_<9EvX*;BUcJ@Q92MjFxoy6|Uvggj`SYjp zi&JHJUIv&c_qH;@aViqRc!3-0Td=O$dU^?kP}V>QnUvP| z5(JPDPITBnk|KCpW1EmzfcNH*`-O+P+k!Omdw*e~V(c%FaYe?gIVbJ6@_!XZ7qbl_ z{}GV_i=Mk4zoNh}hgoeIx|?A&IWItae8(HV9jfQ`WqFY zAMp- zXF*^Y5#JgBp3F$IhlJ!-#}Vh_$1mNuwUHRTP<KR^TTX-CII@#w%R))JDNf}Db-Zlw3nZ$hH%L$A+y zAyiGm7Vm3<+Va=OX59lQ7wBJp62mH8hw^9swDx5jX)YP5K(f{}Wgh(7L1b8u2-K{dHJDRjQ@J4nEbb_LDZox;xVVXnCoH|E!L zoi2Be^whZ`V2Fk>+Zj_!M+uPHqN#&25RjIry?c@bKyk7dEURkpreKDkM z#3#H`WR(#_#k7uMl0rX&7Gj$E?8S@qQT!_YLlr}BFt#b{&*>teKLXp~lJ@&^Z%%uX zlH!9k@?F4uU$)oFk8&j)VH&nNtOY0Q$GTB{#^>ZjDB4G+fSWu{E9wSo!5tcJRJmVK z%LHqWV7~5U1utCb$uqsXXFd?D+e%)8&1p9UT;ZN~fH<>=p@SZMAW^x27=!-!bhy{c zKerECdaev{kPXaszkSu4+#Q9PVP(Hzmm`_`B`ky@!pN|d8tMUR9yR6FBEKJX7W0OJ z2*W?N@GgOg?|U)ms!T)c$dbO5w~(!IjQ2bA(w`d}$wTBTD%x=d<2SeGBTsc4j}Q7r z8Q$0~SAde|#N65esWf%!lgj)_{d&@&bdG=;|I=FpJ;^DsD?%tZHpKE65A*Lq3YIDfuijqmL8&z7J?P91FXoBm-zSd<-S@hvYJ)^f%?uD!AOe>o;03VHL}g*qS9YDYWn0Z)sg`06_sljv^0H3drdw2%-dm z(D8sJ%`dulp~i90U`940S{A3gP>j`2_THAaRs{VGE`K4D-v?$gYqB#El6#BM?+@NF zSW&Tmbwi&sQA{p374!6d_7{-3u-dv7Ypdiwha(yLv9Y*#-$NNSElHjgb8Pk{0eqhK zsYxTQ%FYY9btePOIG8uaL~YdH6#@WWzFocg`NEl+>T23us>Yr2!{%a9k3<|D9krTP ztAy@t>=*uWM77Xv|B+$b?Oi18aoXd1H{y*_h#u`K>BWl|qh*+DA2L({sYY41Pr2Kp zg;nU&R#v*>7#bm^w<-&OYRzd+?$(E@Qhh{gtzGn}g0{a1(n@L}Okb z01jmch_qLuhu+)YFD@ym4j5+dJCtM@#^63xPkW)X?%%Djv1RM;)i7b$F{9U2ql!j` zW>wVb(^voYR}-I9VBjs;Nnfj1MntZd1U_`N-%X?z`!62^;~=>D?c0<6m(d1@Kob%c zae!`-;}|mD3_K9K(A)6gvB@C|i#fsT0icQzjxvc22e&aI?Zag(^!YU-$=kf_g~oe? z`UVCfs{S@}mT#!ErMc*Chju1&w^Yz?SPx4Azncif1H+JODEr;({Oh@G4iAs+wG6GG zp-UvtM3b68xMuxBma7eV5AZL$KDu63K{OPMJi)}LW@z|5lL~{NQcL9E?6vm z2K5itJO^}3gm<;32OZy~R785*t=XpbMWfrpB~7RX2CHtr9d#wX zf{ltzQ}W?+cOHo?Vbad#nnBz5{77AtpoNY}&)M<#RrHWd#SQI9HCQ`4H`s6mmcfqX zzQHRG?Qd|m0FllOtb8uzulUayr9UG#&R*M2lbc=roKh zJRl*4HxaV6s1_?06$Q6P0{)YTrdK(4N`a{9^&{gApOj#GgKhTE_~sMggsyu0*r_!Z zui$zItN2Jk9U>IbL&i{;pOAUy>b+|1!VKdd$Cj;s6^=Jxb1ZppqG$Zf^86mcq`bCP zKlx<2c$Cyd0=S&q=#V2oEd56Du8Q*0_V&nrpi1E1snFv^ver9#RN&?I-0$S@)or)4 zgC}Xu{#+tG%5PQYlzk(v(5ZZ{21$STCgWYgVLt4Rse~8IjK5rPwk$RW7=-+bti8^hkXb?>$?uosmc z-RuWq?N87|OKEQCA6-7Z*8<#sVJ^}!X? zr~NTStW0&W{jyqttc$*`e^}p)F-@R+KT7WV@q_lP89+X*SHNRAW|KI2X2QnDd#`q6 z`~E`spH5@a}~{iM>5!5S|1L z$|z;c#a4MVS=Fb6zIyt$)ab=iwj2zJw)Gz&-+qG`51o7T2MVFjAkGLY;DqgO?y+osMt_6Gl`%~(A^0*g(;9PKaCRig2nB2~#=i#k^!m?0d za*%BP_gQHna?7Ibj*X+Aizygi_Apc&;Cu{6raV~H?YTF-Mz>!rrtj=-A+8F-c47DK z=WyTBZb!a<^-y)46`S(6B2xD?^Rzb?ZHT|oBj3oF6;TaAo_$LZQHu~;9j}Xw-*5>tL)vQt} z4pM-EsqW=(s41r5|7N@t&GQZheOy*<%Zp&kSr>@ru5oS{j()I`*<+I|Y=^|P+v|{o zC)4ll)xN169n;l=|Hs2>ec>bSnt`Tyg1&DJ4n+*(vn_pWYSJ3m}AHXyidKd))Oh#kLHZCwaPFRfg=8xH1Eqi1OV2z z*C>mMdH(&`I(R)Tx=o;O^pH}H+vaI7`S3>jj?R_zT97$?(QfT~WXIKacBWuB^O)<& zliy%xGX36#!&L;yU|$b}Vu#+d~L zD#&kk?hKoCfaYGn7Fd=ZqJ`LC%wpyZ3EZjRyUeeHpI!%JnvxA?+~0)v0ZuRcetND_ zph`$iNGR(bde;r}vMQP!e!}FxpFlqp^6&XwS~vK&4f31phv6oo1;-I8uXxt;W>!X8 zz*_w8=6Lfjtq&(qy>u}_ho~o!tKre`;!WQS`Y!0Db?u2uPPYBZH&QgV;l))IMqGZr z2S$5DMMa&+ax3{=Iz$fWfmDURe<}m-c{QUq+GH*mtLUil1gZtcWrx|B- zfxr8CdtWy^Th7A!1qN;J7EiJjKbCnF#d&T2mSDor6wnOpPj8LHE>?5Y7W5@H2IAZ{ zaPYxB`G39LAbAT!gtwG_Z7zoIOw8DLQPHJ=C_q2B>D=fLCLuLil2x$kBn(d<<>$8! ztSj+8sj98>AXJu zWbns`Dsr%*aAhh4$CyYY1G~D;v+75pubroCpv%$WqXS43D+?bh!dg_OV0?>7=dfOj z&_g*@x}1_yG`Doj!B?wJ)<*9dfbRQXVO9jbIhfLZvzhVN-Z(MiZ$C4Z^kV7Le$2L1 zcuCkub+zsZ7T*3xWe=8@lGX&-cf}r#H1e1n>BE{<*VJsV5LiwsEg~@jfl~0X&oA5D z>HdfoI}A}3@mfU5qd0teelXR@8-$UOaWuMsE#324Ob=mEsi<}$y18J!OrKC7rW~QH z?Tm2)S)pTdhJnouOF+444`gD0Wnh)`N&;66X7m?ti>+Nb=!qk02DFCJbcrIh6 zlF`Dp2>rDGTf`#q`?gO$S@>26&9+!&_-2FK(>Wn1{T4=Ky@uvF4#&Vfa6EGN)+=#` zI{WR5nY2X8>zygDXg*tTf}Mt)FI-s5!$VLR7ci$is(OZ8*K@M$bOj8w@Tg>aA_f4% zLIL1Hv98a?L*5t)6tTn4JqOa)QBR4=Un2wfG9%KPxPI(eys_scy*u!%XHz8%Bo>^VJ zp3a1OlYu_Ro&b#*zRHI9B=6bZOmsRsJ^eXV#y*{Q{$!Ogeye0D{p}?d@F#$$aWjvn z`F-ZuV%+>D@r3h~@wf_ScxZQfjQxfhAj|F&{d~vJMkG*Ub=8-3EV6FVvpPZ{XZtn# z3)0MThf~tP8~L7A!RJF>6xD-l5;U-340EfG>g0ZU{7bCLxtYmP!x&6jAw+LNJV%Qs zX=YggV@En50||a{@Sccn`=w}h-*WcPmWKy^X}!W0jMQJW;{0mj24H6gz%?JNy&V-E z-JJO8=&gi((Y`PBc%JK<4K4<^uPI6h((7HywA&+oVXE*6)6F|I5XIu!w|DFl9H(7B zvfpvVYRMnnhqmW=N|ul-ha785Zar*s@IHP68+zxC9XD>>D#0DmX&B~QL!|jEBN6<0 zQRxmbyBzO2u2dRn;{l!qCW+zN1|QZHrs>1_5yjI7>H|}QJmtJh%ErRi6h^oDBsdo7 z`Ki&C4OkB!PHZ5rRDqMKd-X>%Y<{-?fZ*{S)$H{ng`bs9s*MZnbRF5(*bTK8dxJUM zrlNJ#09q#`8u`Z6-nfIGg}L(JL2$* zH~1hyFlo(KKc7CrvHOi&O!eNR^mE;s?t^e=bW1Ev-^N=^Vl4IDu#X6b@!pF&Hmv`B zX5w|u8XT8|XWOVA6*V=ZzihW{EF5D$7xYGjJn+LKFnCiZRy&yDH}R}fNcadngiB)k z_Vc(9AQyS)LX1`+rcE{Up;fdPCst1VHIhG)+oEHd>FfhXYzV2`XOrL_K1-fe1M5sJ zd3F@aCSZdFxJ_f|2`S^Br>Ea$Vg?~BXW*r&*E_=Tyuu(dig0!SOK)_xsJNC`b)H6t z$LT{LSPUL2q`*6GG|BN6rTi# z%HChU{xI0CnBVO3`r!EH;qdk=>uNo2sfwbuz2mKO>#6LcO&w=^EFD&KPBl8p1YG}< zu{=1lI3y&$n*s3^Hxd@Q+;m0+>v!3QT7chm#m$_Ro4@Y4n7;>kjj*uq5})`INAg|P z6?fVXiPt*wV*qd*MHlhvIpwuj$_;OCt~y?GS3&hjF98}O?yOuXp~yVjsaANnt2sOn zNUvIw;TC5iD68*^NKj$zYO5QGR?T%8Ft5h?i7aTJTDDNelgnf&oNpU#cdiwlKk=tP znZEMbLL&5M*s>5xh#UOKU!IWMk zn>ThL@?%T6KvIRf7ybfW#}t=>#djDKO0BdHtv@gR_m>v+pUN|nj;?v%-hNxihe!c3 zIgn;5@S}6TGwHwd#J9OlJp{!bovQ^iLzyw`Vb=dUz1ULmKPi(O`m1ON{Y@aqJCx^> zTOyf4Mzy6Bl`{LX`MX+PlgZV1Va9Pt{)&3A0sMm&&aDCQrTH6? z1wW$rnb=;3l=W7?zH~?4+FfjOJ3deKV1f#~$Tsyw5Jb9RzQhI?2F$1Ha+To^|4D2I z63`SU^r9I21A5P&8JEJG{$IZ^DZw67LSvUP*-5h_Cfv=e3=tU_N8pr5P!CS^;3|#0 z{0_!rw+qzL=YSWX>j=Q$0+T(M@sd)StDzjpXAmKQv4w@MMS(<~0TgRK&v<=p zX^Y50?5iks=v8QP|74%1=pn=PejEa*!eQSBet^>Dt35=A2@|cFfQjnq9o)ur-o2j7 zzXuZBd(UiviQzgJN}T-#|F%c`58rY$Kbewm<*rAH3m5w@kT540UN0euX&@{X?U=(L zX!h|!XYxq@$Luqm3@Cf}6-n2|H6-b>_so?%4^7@vg#$2(%R$f6AcUe~9o`t%fR37i zVgvFCPTy3FD~;+TcY?4tlIqweoE-4ag@61A-(&qjCDrD{@mQW8Taa?Zv1Iu8;x#}2 zea%k}Ifml%B^>`v$_Zd&-WJLj!-3(fFJ!QR-sZ%vcrO5<-9Z-)AH^O}oXzKYeMep|8Sfy_+dm7X7FvU?@>7@)#>o>X_tP<8R zM^e8}e?EKsc#-3h8&T0(8gFyXZ2Ymcw=3%RgOoLDF`K#qubmHGG*WTga^Ew57i=yO zF#lFpZk#{=4=@M$MAKMO`+;K9h_fdoib_FQM?)z^xdPr{F>ci>xNPBDQn(5k=f5f~J zm*#7Ko)Y2@F6~?%UQk?e#cs_2!4H<^c3bkd_O2VrgL6F^J_2Xy!)V?8*3Us>1WyAj zV#2`_+9uL0cLoO0!lz`{YOzn_8mrZ&w+gqQltYI}Vjvj432xUUP!b#bHP~M=MS~ew zj_s%oa6{Buv|kD%Gikmnc-;?t|Hky*csjPj#VegMq$Jg>G5nbzvtvlbT1n6UKB1ZV zmb$^gZS!@~b&q4!(-o*x%<`No9A|ql(>_vxD>NF7Oo7K26#fG^DxeGRU|7B`Oh~Xn zf*6-ChFeyNKE5rhd*o18SQXnrVqT(Lo~>t$c>F2b^At1Nr}w01*=?e@V}Sw zk1%&9wsR+Ah~{c>n;cy!7jY;BoZY-eC`j&)V|wvFGX z#G7(d`S+)NAV!7}F>T<0b1`ujPs%rEFHM<$IsUn_#pVQneLDBivWJm}zF$O^{P<{& zw6if$OM>HEkpNell#~^JAqm=gepR>I*==U%z+OTW52lHJU36L&5Yu_IY{m8F_r0~h z`j(+5#>eL`-*KTl$-%!+%wM>{8w5MH&GX;in0oZ62Y5J%2?;gWa`>j7%dCkT&wu_( z1t7-ym)K5pSRlFTy!`iX61Vq~LGmhoOs;&Pj~V^_*TMfAR{Q__k4V$d zbp7v^?qodH&vbQsgCXF5|L=n!({n6a{J$?_T<-r|#yEw@J-iZOI~^|j zFj81|^Yt#{1&8Xgh^^IeFdfH-rjhVLSLJXYA~hK{rzRfd*ccZFWJa zil{obDBBSW3kzksoW8#r@8G-rD-WKGT{LxT@cNFXuAhwdDHd=|a&vQIV`Co==h1%d z4B7VgZ=2nPp*7q;%*xAR9!QOmxStTTzE+ZTh$y}|cQwlN@@kELQGA?M zOtcfE%bBQDJWH0zg~_{&k`jKW?TXab_4cxh)%wQq=rd3vXuRfB1!}#<2UqsMz+alP zqk(=uS_YC$&CTOX$qtOcR5{yG zeco(0M5$7iDiuNSobdGoU<3xh0K+b2GNcyr?=jR?tj{9PKP_Wlp76jlRWtmSDDkHk zf6=v7B#uOf06f1Es~b^nzJLFxUGg~cFLZ5Y9mDq($E`YePt+_z#^P2EMKe~^d%ScL z2;moMP*F(+Yoi-iJKlq;5FE4FqsP^Q|Nk@$ytXmpyCJheZew^6)NGV`UoKNJQ~M7n zHomN_t$ipdSZ!5X$MgSG_U7?awSU{NN&|_KijpGPNJ52VD#WI2WQdZACQ*nCnUX>& zMM6TPkSR&#Awp8-A(=8yWuC|9SexrN-0%JQyw7v}Rb6|pz1F$T^E)2L@x3Ye%tdoV z3|v_i#b?xNFD7UwC2d-Kd)3{TZxAK@x36!;VDli<7JvR7U%1G|!cM#%7xt zBNmTgzwFtI2#Y+WG#CtQJ2pjEBSo@%s`SN^QbD({M<`4w$#sgmi2EXgh}6IkQ~@wY z;M;8;hlc+Mgk+~k{$uyVD_hKzxV=$&yI^K!hS=w6R^q@AG{sJezAZ-iV?h$?&@d+T z@1#ZFgX!{(lq*2&fx<)t!^(7xnyGdh&@-W>6KQ?QzyNoEZn4o` zz~%f4=iX{hORtlntDLx+GZoO1ee-?WnV}-UFPV;KYI@R(KST*q7 zDuh;jaBjef+L;AH=IG}`*D)mu(}mAK*>jCT6->~hYU9|*NLivV|7A;4!|4zKf>{Z) zH2VJED-loiMlmrl?Yd@OObXC_j9?+vvd6nwpb(Ka$KhK7FZX+KNTJ`P>{qN1e;p}K z?g+>?D55l-cY{oW3807z&hKpjOVNorMTp>VvNUjE%T_R&`!j%M;#eO5*1}q=!N@>2 z!jd^tKKvp@9U6^}QMbNXP72UEg@Twcy13BmZ*GH~!T^xr)6EBe2e?-Dg!@2_=X#PJ zNiswXdY_^Jo-R;6BF53UuW%z2;9eZO7I|eCOG0A_i!|G5&{05XBizc)EY?Q_=#Wkt zS$<4TF7B|%F8>O8U;CctliW%flnXvZyf#qIL$w$i>F8%VBI-x$auV6TJnx09AU>eE zGFcWWfXm$XWduYA*@Rf8H;M$rL`1AkU4vY6)a3-vYq?N0wFf&HU;YN$tF*LKk-as6 z`0gjQbO5CZ8-fiZI^B+)3m6o@r61sH79Zp>xUYDY!$GR9E*WdSYrf?8M~?wu zMV3rNS%eFsvG;x?O+JD$?$28U<(f7j48uNlBr;9n?LE?tR$xC>zpyIn-=#TO`<5Si z6&_H9AHVhP6|{MuI1#w~^?SgtZ?{M-Y&*hxYOU4`sF8Ksp^J!qk~Ce;!deZ{?Xz-2AKCz{K=d`7S{)rRoT66RuS6^gw6kF)bc^jKk+|1O2`P=8ua8XzoS9J9Q z_>6Wz1WX~k-b4r4-rIC^V&Y-`1(S2domFh??D8O$v%47ICc`F2$CM4Os_+0X5}I(TMFeTxx2m=6@Atpr5b{Z*Nu0fHYh8-%ddE- z?Eoqc(o?8z63Q&tK%rbHK{I7$W@<#S%*lL0o9(F}_@rF_NN;e{n7fMI-k4ucirRKW zq^_yyu_SK0a<&LUe6g5yaw+jMsSfeb@4^rD_AXnMW3twUv~$tlTfRpLISbBdGYJqo zNTluEyjFz4-rK2fNG(b?%O2nfH_8WBU_K#fu_?Sx?)L3;ji@ij@%9y+=+W(_o;@@P z{`iOn#-a@uL^sK8P-~6p_Bnv~T?zgFfbaBHKP}oB{T>SVF|64O+;fOy(3ks_ zZVfHh9w-+K8xEPO%5CNID=w$aA*DX7Gx=0!LIhJZlSUQx8n4SSUZ;H{7-@rpgQNvC zu~$HHh^QpKDcQD@pOHh7)oh^%b6gr4&iE9e7AVN!<;LHIfZmnM1Gf{a{SbB`Rb{dB zi)&ige+Ugd5~X?Jj?HrjaG^eko;>=WMm51PyUY7l#9tk7}m=gtA zs-28QpfSkv`;L!}?qnGx_Y;JpYFg~^FPWK`mvEmdzi`3KIY}DkluRZB;VMF#?_LS* zR%hVzW+_E1GqX7i`lIW%i6Y=`Za!IdZg-aBTfH^XKJp=5h4bGB$(+av!fX4eXM3pQ z&T0;bumam6;8&c$z4e-dH*@mq*LO8i6B34Gj(k)b<-N~Be$WLMJV&ptqEZaBg8hV` z++ANAv+Ro&7dd43fYV~E9-KW@u!A_>gXG6k+*ZHeGp`DC7)B@JSr#lJR$yUW*u5}C z;cz7l$$bV{?C;mB8@Sq4qZEGSz4!7&4u<=@8!6E6(1W%*)4!ofF!)cE4oFHegp7hv zyb0X!3%Q0N+*4q&0K*D{Bog~M-qv{3*UT2<;hSL^t$roQ1 z3D~?>Pu}UN%Esg1x+%ZmN|5JemUVJ+f`I-iSgcuR3DIXEi^jExmXMD3P>qm_*qe$L zwXNd!TS!6y*IZb^3Cx*IwR6LJgv9PJP*13~U!f$YiWIZca+5Aue_1_>>qjJ|Hn_g)0 z=if?k-#^{FQ)4pW_$nfUIR(;e=jZP)$w1KF-8fxnvLeuh+hOC=bL%#1ZLRwIw)Otd ztZQgx4~T~A-E73%*FYHK^4AWF7F?Xdv8-kY%HQOc2OQJ-^GN(`VZ3Re_8;=`lz_izk?MOsr3$eP0q;M5s57ZrS;(*L zu}tonxxyO_&Wu6wJm6(j<>k~amHUm=Z&Fir-%A5L^1hy%#^>t$$QX6biYo_D2~~fv z=llBIz0o!~A6K%zbfpY4{VPeNiM-O<>Qglb{SALnx*YCpF(}A<_#gsYRRzlp-jbgm zc6SOTazwVs!r*szVmHzZ7MVFPSMn@#_oawH-sWL`>+rC}A0B2IJQm+qR{F<#tJY(R zqhSqtqJD(7i!ua|VFk|EQ@<4pMDDf`K>Di=7IH}q-R%`*P%n6bs0Frmr3%0%Z!8#= z3|b+R&eLh`wB>m_*CUkjb(^C?0%e?2`+HR}4{HY{1Z@6`KJ!oI)wNJ6+um%uNM zURZncA&7Gbz2`01Rx#qc@v zT}VD(327v+CO*7gk=GCZt^GoR0Fsrd`3epix)E}2+mQe9w;(@St}|S7pO@tI`CN{J zGmcSZbv5RbQVC%0yJk~1aIuH5b8tK_?noJ9bf%r*mb-Q>`jo&Yy;#;r&rRs*gDhq4 z&S<<#=_@!`(A{Ylqe3bfmhSTo#W+b%zphL-UySBdYe-Se07?l9Eth3>@WzPpI}&0)4G!}F42HY@!l~h(%0z4< z`enUSl7l%bh;?)puYfmzY8FZ@cdM*e5g}Ntcw$K~?rx`oX(=G^2g%<#5}g7eC+aRI z4{Pk%2c&c!ps7Q*C5S5=-M+zC*Y=MOkcdp&%yf9~IUK|Vd{V28H+jh4O5dIk!~hE^ z8JV}Sp_k8B*)qPxC-(@yFCzm3wMPIIjp_Cb+Y$7G9=NV@){-vzZIPpT1z z=)B1i}Pu5HPMq%n(mwIHPk>!ZH=?RX)uR810M_h+0A;>#^^s|XYpR3d0#_fLyw*9 zsa|Zs3ElD|p?UuGu1}6>h{_dimFCNtURDQLC0X{C!UFlWwWikA)=)Hru&CM>nseC{ zW~42b5_P$r{=VG{LosCfVK_45OO9F@JS7zPY(M1YfuLSG|Vz&Fa9tYZDiKu&-7}*Zn1U zz`JKhKz(*;ScCX{Ie7Z0ola?9nfO~zdaC4K&8Yr`=J!jA$V*9E4Jd9W=~bxE${hdd zCnV1n^JZ6F0khxBaimfjo(GN<)(GbwN__L?-cgX3o^FGKmoLC5sGPu~w}0m>^CnOC z)vpP4UzT$ygjD@K%SVk^#zImE za-GQ)SDN3CVBykr6QKP~BCS4sAtxv-2h6}9MA*l7itE=^d+_!M{{?&+W>k>*FhIy* zs8!($2(dY|XvoJ6OdY$|ttd0S!D9$Y-yX--&;(C$->vM?DYd#oj-gptGZbX|XPOtJ z;deL*ZvP$S=xTmnWz6?^dY}#crqKRfp9*t+ho5s8o*ZgJHO=T6s%k3F1+Ic5Ce=l- zT;HEtmMMYp*bIuadMcQ5BQW$8;DFcfefW?x^xB z;RHF#l_GLkFSZ6Mtz~yncd4292(bg6pBJjKYRNUzNd9e;JJvLl0w@vL8_z}z_&NN( zv)VpeK0INL> z$b!Ow)goW$T4vfIn4OoQ+Pp8_W~j9XR^ekz_KkO+4rX7AYHaY+)FHCxJ+RiUvtOKto2<-}tkX+o@9_3dK zoH-*<-eP7ma1n*&okk%>7z@7sVLsRjXPVW-wAb8nXEsb6EFuH|MkmpXI5}CMFiiH! zOj2}TxNv+lhHgZ{Aw4wuAJ(50tm@Q27c~3ixy&Vko{Ao&p~;%&KDi&QgL_BItE=yEr;Ccp^W~hE;AHxq zOA082AV6p#4q`y7l2CM~t56&+Ti#ZC_V!<3+-H8=aNL+%323gAltsJJto?_`NcUjC z-|ovlPW@ax!#`57?M6mA#IA68KP7I~mnn?`?+a55%QC^nkyy#d*Yp;cVmVW%qo86PGVXf2Yr-KgOg7=$31aE(KS?T!jhX$iT z`mMeCgyhj7Q}+{!M<=Oj2MjtD26kc^mCib2`xb`x86~E4Y=Wizg3z?Ow)SSXOO#Qs zr9`tq1FT36$$@R_){4Hd_D#T5{-#sBv^!FY6)+7nH|Pbj9hikC_pV`C-g{6XA)R^C zZo36QF_vCJ%GmA{i=^+2%N+9<;RT#2k0Zd)1#D z9l|vf4`ptT3lwoDT7Om9m&x*&?U;GUQE;BG+XB9sBL*QH7f^&$1Lb$oA}ct+;S~`R zbDW#`sT$p_q*5OeMT6GWY~lt`5JVQ05fK{NGCwmwQBg4zO<>O!DFeFN&{Bqi<0zmRb1w_!QzO!dJWT7fL~5A$89 zc6RVH(Qlc3dm?PZa^yEW2v)Gep)F`kw~SF!Brbn|QyJUw_GOUYB5$i*N<7pnUk7R( z^^w-of9pmCQ6T%Pb0tstnq`Ql*Suq+r>94d7XDYla&0cdZ=VK;>VhQCowh&>LHM7F z1G|C|?av}07YH!}o$aDX-Q-mt>UvYl$ta?!s;WYU%9>)vnRBC!Id@&A%U>tR=FV6) zn20*93G1-Vo(a;3^6Yoo%}kf?gwiXJr>Jl6B$z-UL1~V4>sFj!Xng`LVp?$_LB5=b zJOH?GFYbT}i+6Dcej_(^&Np7}8KERtWW@RsQd-_*N2cSC`A)tSIQ5>!+|ijaqk%JSCW-VvncnDB;G8?XG}zSck|Mm@S({m(s`lvbZ-DVFwx5`gSS z_Cpp%Xib#Q?b}VeGDKt9-6zZV_U^@=FpGu49Zq9VZKs)dZryQI5V0{NTt9^>fqq?j zt4bU?9SGR|ToO7uNX96F!oRmv-e`Q`betN8DV1`XpGD;Y1&8k@QRv;}x@jefhGmhtAQA%s0nh~h9%{}5GBB-?S%szcAr}j=g$cq^UKGYWWf4%EuM?EBy5e;cP z-W_f9N%IaC%xIYI@-ObKoOnQCmc8#}AqQ%i?s(H3GVDzZ>c9TpLhbc4`TR6dR3fhz zZTDG|%S|Uv4lD{GL|WMEA)yuUyiNn^o309QYv<2%*2jG_PEr*&MH-HW`~=cnkZOb) zZ)9JN;myv%^}DYk{LbvT>tAA)c=8dGkfx>$Vg=TOzH36JIC6&UNEhoX8flC7X(+g z`ccLiaZ4j3*LS+AUVnyu?{W5@G3NCr>KRJ@6b{TyjE&v<-O8G1{stT{5e-y+ zLx1xZ8oEfdmpitHJy76g9{Mw`b7`1UF~Vc`hOZuK=tGw$dP^Y1>qD8vM{nUQU zAH@1_cC@#@Z&9YI-FOstJg|nvai$!j9pkr@*nWs1q^1W)F8tYn(wTO6ki1j;=leYytZE}KZ*0`uO|2Nvmi_t#_9sfMQCY2@ zYtP^3JtTJIV|KQIz~X}U0ufR{q`LJO^3 zK15J9W#r$4M8(ArEc&9qtm>u5qco2-MquyKgdFF-o_spG`km%GrJ2&o~KG^+N4~R{yv@^bMQPpPZ2lY?qOEqeF#Z=bg}`szJriwJmg*- z=0bhsKX-a}K=riz3QywqQ?0^VAH@VIT!mU<(9s$~5b_8MbjLKLqhe#n`en#F>>oRHe(J=)pLdDI)Af7cLsm}@h%;P&V|SA{yhJQulDulX}88yf^Tp|kW= zhd4j(ExaAB#G*MvtOb-`SBEnJcjGAg+J@%9Z??=37WU_!iqE?(kpT}K!sUUiGFxOo+} z;rC_fM|4`SU)aNNt4MBG9{g;2wSllbP^&tw-SoI9A7RG0S06kX z0X(@=76s;2e7x60gqp6m@|&R)Q#!(|dhbzNx!b1*2_vd^b+-42waRf z03xwn#SV4leH$rT$@-Dvv0SiP;1qEp-N)QyC_8Iq2xQEG_6C&^9>q*@k zU^6C+Pf4jOs>Ri&RS2-(|o&DXFpU}k)O3eaRT zFuO+Y+9>l2ztU$SBl}k{wbktwNW1^KM(v`^RJb-QRDA zDYv&!A?$pHDLKHH^^K*ZU;S|tV~>Btt3LMh#1PsWep`A9DkS3R?Yx8Kou|Mow`+AS zM*ycyX}V*&!H)SAX~r9)V~HGl#Le)50D6%n>_I=b(q*QE?CmBG^mH6TZxO>(Q6-2= zN~(TwtfuZ`JAiwp*RG{obUaY)cGT_CY>Vpq&Ij}t+@3^e-74tEsOtFc=H>?Sjw}}L zp48-8Q;pIk=%CM0Rsi8Ejx-jp?Pq>^T8xjc=)8JkicP{>kVueYy%o&es}C0?Frh>Iw(J|SH9Fna~9 z-LN)hGJeU=$NW`I(wT9iHuntrwMm56itjq7w&`fRnzd>BgCd=1X{As2>&ED0iu z-Oyy$PFm++hBe6Z>j1TUms}~hF}mx;O|WGEAWcvmYRO80wWyI9>;F5l&_F9?ySlxz z^D&3iuZ!k~?0-~DN8AkT5rkrt48*&6dB57l<@uf}rKh74o_KGSoERybE&BJEC+qp- zdXTPSYuozYZr@xH_|Veu>fCJL%Tey%X%jK|n0(g*=%w!J$@+%RJt(#~p*S;pYyEOr z%BuDG;R!R*!xfE^QWN*&GJ_=8)AHP!lan2*kCJ23dzcK^S4!2b>nt4<4_7~PhFGRX z2kD;Kz-UzM3YrpYZX%TavCr6e=7*iOy?uu5oW5gK)E*rTLaUb)mMmIc0I9I1OCR&6kL-*|25;vcWB3d;Hs&gn3eiuKBDKiDg(L8%FDi6pi zi^=(ROn8Qqf0-@QV-~bx6@$1%yZhNTyF=Y)_Ge;ryJ!4%;~;Jl>GZx+`uedaQV7&( zrO!9XO+m#g&8#aq>?+(;R45fbYm`Eq=2OpZJN?NdVH_!VV_Q&~LsG$l(d|i!C2>;9 zr2xlpgv1x1D1To*1`X1#Px{s$|M=_0X)9uz)JZ2^gsaO%K;*K&KKKl#l5Y48#k}VKP_ew9h8C-2Q0QV*GyUn+lW*akun zfMti9uFMaguMRYHbab4E8w83PXD)J!=36mewvzX{i7=sATb7Ns_CS*s zX3Ug=_9*Ja`aGDnn`}egoq9fRl~zZ8W4f0A^nOv%@ghE_Nupc$x5;Ww5ynnT>O(S?PYf@>Db)$ayV8CXF4A9{M^5 zZHJB8jCW)gB}-lXv-7n^^||oBq8ul2vS_8q6s{+;^Ap)l5Pb05ASEjL2}Nk__!hij z!1)>=F13Z1u#6wPFxShu$<4s7XSbnKInMITpyH=7p7=cnqyzqh20w$QA!r00qowrrYJgN zRCMQRU$C3A98Nc3-0#rCc`5*zE9?UQG8d8FZ=;mo_T;D(2DV7~dT4v|{)TJe-xnO&l52MKb{g@bd zWgdEuvH6(C@L{t)%v4ZM5jYPL$E#Qf%V^M~j5gCetHE2Vqd1A&tbDUK8-K$8$oq>7uXNqIzAGugDeDi)%p$vqr%7A z)2z>*KaaDkFPLGz$4>U3FfXrra5(9+_R-QTODd==uXdJq1WMq>%_)Xt% zh}hpIyKMkM9F9I6{_vDR8un#eOib0o_i=Hm@p(WP5sS#iSJp>eM0z1#-=}9$WhwLc zTc3}0zm*+pl7LM<5)y7L~tS0RY^Ao32MnRUs~Q`Dot9(roK`2 z*T-~=%Zl&GIL%AWq*wTy+w-~JuX(Sq&OP%w@F8(04<68GG9sKu-F$KLc8{5_9t~G% zX=#B|>}$@>9$H`lHGT~vqqKSJ7ebbU%`&31G-L;+1+lQSpP<}#b@fmGq&LC%_XEk- zY}YZ*vYS5}hN|s0+}NB~{IHubEnP`$b1?OtV3Jd^~VJp+oyp0jmSAF{4o`jgw1k^rA=OtYv^z|AlNDt zmu}xvfn!~ybf;;%wJas=qtk-pUQ=LL4(;Bzk8%NSl$Mvb_n*q zi;hOO=ltwBW4k2p%YYk& z0zHU>4~ab}q%r!2y3F6+p*RJR*6NoPzkXpw-}MxPK;!-H;ht1XJgs>DUZ_;cHgiPB z)bq8XUPL|BNn2+R{Oa)I$kb)nw~q@Ef`_}iyQin7=wbG+UpjItVMu=}{~jSb2h;9a z8A5Rhc72U`?-SL@TZrA)pbp)@t7LLf^TJ$nUsI1kPz}rC$t@Qn%!d&7?%mZjHF%tn z5xB7)>Xsdx;-*<8aE!iSJ7GL(k#rJ6K#v}UrP7W$b-uCIZ}Ij1%^IAX=a!|oJcT9~ zESh<8W@Z^S-JMd>GQu$0OOs)_Xr$;qG0&a_WxZb$t8L3JBvtCv@wB+jnTW^eJ_XL1 z;l0xle&#e?=3sWdei$qqvx(TuLdsp5zeWAqqgtN}z6n5Z<2aLiJ9-f%qr|-hkB$&a?-qdlJp%{cUosJH-ik&W z)*S|@FEki-CMHk5Z4FkExYgyIZ&a2xq&2Z2&v!!UgrDzrG-(}VBBiQ`sp;?H;zT1f zF%Ob{ahusO)2!IMYh@dwRF9&RE#$O=nvel8jfJc30FqZ)It%)K0PLi3_Pqp;aEI`P zFUTELl$AYIl0JUC@-69F?RQ%YNRY6f90U*HP_sPdjH9+#TwkrbA;>cK{LsrARpDBp z4?VYu^&|h%Z#uXaZL3JJgli%0qL?r_k6eHSSE{T{)Z zsXd=G4S>!C_%y}#dBsJr2!V-%eZaiOd$v)2299-l9?C&z`C5w+x`bFM_?*TnOZ^c< z#+FB|6N|TP+S`r7x_ zP-wh3YvG*+@A7*hdm)qQHn5DJJ9IJUQ`)Nc*@*7DXG3j;ft7`iwske^?fm|r2*~)u z$2~nfgk_FbGQbP!H$}oDvt8y3iAn7+OJ`FJ+v9=k&3l-HYcCyJEiCvfAaFV$$o?aJ~KLESsQbO{SzB zE^aAyt4VBaYkTDKE-I=#*CxvYm}A3%6F0;14xU~ZHG9p;$=O+&oL2v+Qa{+%>0ynG zRADgl^)K!lFzgDZRwA22{GKIV|K{rAP5u2`ji*RbLnl;;<6h>I@r4G_!SYd~SL5Mt z2O3gOa*AQzZd>9v1jmvfOi9)HVAt=@-elWb#n-0`Dv193b9WLRUV^NMpZ?`PW5%kQ zE-wy$maymUZaHSv{6od|W^IgQLzr7)+U%gv;lroNQHq7O^N22b8{pyxbW+kEVt#F4 zc211@*rg!i{I%3H(Wi@@mYrR-h1gz$f0bJD#GIJw-IEPbG43C4y(#@JxU}%Yb@p`T z%-u-C#X}#t26q=_WMt@^wN+U5x+_qm^n8gMhn-TqV)Zkz_$=U9B4xH`$=HJJ3AUJ9 zyntOwIhX-Z`teVH-&9c(o?I#uWy|S;oV|FPO>nDJSL)Vh^#e#Sv`lN!d<}w!=@4mDw-MG$ZvbK&j$@ z901e%`d2qqRD^Yf=AA!kw%QXJf3v7EGPYT57q?wg{)vr-E%xgKnI_xppZ0NhskM)^ z%~Ipu;J%{k?A!a(W_GnrwY!_4vw<=6kwaE|jRw{e`F5I8cEf@_MX*_0(?20IAp-2= zshK$GmSG{zZo0*^sIxEwNDU$*v2XV2f85^I!Lgm=@rL${k<9d6EK{@MGK6t%6;-77 zOSA9FuFe}eMxGD4a z_kp1>MF5X+VDGuQV#&ILx(J2ffB)W`7;Q$Xd)L2zD_3XNy@`$K=Hgj>S>jv`i2As8 z?Xud?<|1J__h_9p@jEm2#AGfE=S@et-x0`^eVc*E>au#sH%fX7mN_*z801fta!fny zBwT2@oK)ST*Lk+$^bLP^Y!EzrxW474X5YRb%eGp@?xL)6pI|o&J7#K97DmdHJfndRv$~A|5${Y+jp-$@V`X5MI^KXMVxZ?;bKxHiyN0Y zu?to_+QmY3m_!9LWJ|iw?&NxI3 z8G>nLDIyxq_|D8H4vDyJHyDYy2FV-|NW4O!fmll7A9`S6jlyYg9W3QfW&GBn16%w= z-hS$r?3g`Shy;_k%f)~2*4aq-=Wg;~rz|D6`4cmS=Q!QlSBWErV8fGT{!l+6C|oty z>l5>D#>J}+<{@;zW$c(8Ltnpc6B9#I+_Wj<5GZtzgOgE+$jv=UJPI2hGAh%hD8{up zq0R)dg+#l0@S+|`Z@-=laP-7-iD)1aB(f1aixge9VRHSLNpxgnnb3G?3i+B`pZv9B zeGs$oSX|L#56OzD3f zU;ucyo~TR7^`@frPQQ6#*Trs(Ws3~~FXf+mrAFFB#O2yt{;Im6A*MVUu=#AWf3E9w zw&H)>ZupKdBW>-PeKZZLGTpn$zZp$>h0iS;^a*{5MVng4+fNhnigYKqpG@=5zd50t zW-=fDeR`}9iVX{^WUUG9YR2qA)Pk~domb|>-?y->`i5S31WVfrCi>BQA<3!4V<8Kg}5{rM+B=4qJ`J8EY4g z?Tl~H(7eLOsi86rerfS+EvLbqQV#&}bGXbuU!q<7^LODzwzch$2l;`%viyhfvdwR**M<=@J-bMrjo7jDWsC%Uk z-VhDVn#s@%40YbthnVP6;@s-AuwbLzmR`Z<>|XQ~y^T17Nib4$Lg?{1JMZ|Gbg>zm z{KPCqCM_J*sq%?&?JwVWeDN;R9p?}P6Q88HKb-#1&fE8QeHUrzUlz81OZ@P`aoG;y zhV2zfk5dyv|=vh8FhQn(9jA!ma#1qJ+gESV(U=fC}9(^hlrhq7?3ci*h0i!TD&y&OW>=e zOAy1hSM5bjtr+oQ`;Q-A(ww|Ic?|GFz$0ViK+}F?d=Y#-Ll0S zA-3sz&T=bCFc9P?Id1O$$a*Rc3dr!4{=c^z;6)t}*|4SY^6tS|Tg z4}Vd{AF|t2P-#Urs!PYh=MqLPtD=3i?>6%&8W~=wgT1tG(B+tTW)S{Q4PA1xMiBKB|kU%|;DBs44SyvRiOWbo4e!oA)JJQF<~b$1D`0^tAjL_Zp9l z*WcK7e)FmHj3vwd^U2^R(#*twlZw>Ul7SDTM_#pn}p zu6e9}na64lo1B}J`3a|m8JH4=jUGS(A&{_dLeZLh6#r^#ve*ZN@k!^<^11QNBKhU zB!G9+PO%x=8z$+=S8oGX-x+Q2f%;wN1PLUEh)VR4g-bA!s4~Q9!rCd~*BKyI z-GXPMv!|m`SM2!`r=NbHbfi?KIdhX$>leDQ*y8X5(m!8H-M;l-zi&m^dQ?u%$HxZ{ zs`mA+C8!NKq7i2QeC*8b4u4_8`Qd#F6%v_fJf4iX9c|Z5_N|9k$eyl_-#Qb8_4)NO zHYrJqZyPELPLD!n&6Cg3m!l}rx7kVxn0D}0yPF<(qetj4*JofI@XTQ5SM9TJ%RUzG zSv26z>pZgFbDCokUtb7gA(W14WuUCkcjs$K3FG2vCu|oE;ucYEM7Pkb&>wF{El}&q zyweu+D8g2^-rI6^&HwzU^up}TgYBTTu-jP-G-XOSKmx}(WeZw=yA&TY{MV-yMY&P8 z@O@km@$4Aomx*W)Azrb(Lr_5XgS?jlCRfQ)j~l}ZL>lWnq(5+~Yip0&kaoYB*E+81 zFLZ>2w)0kA>(I0`J+FKucq=aW>DV!!W5sQoD2)cK^8lIvxb{mG;pBosZwbsA+$vk_ zgnC6;SsAJUu$B4{S9uF;=t zn#hW9Gej$#h)-@-Yr zs<_-Alx_Nlnsm|7^xr}kHA|FxzGcUJi(1HF>%)Cpr4C*E_6((7+J@oiZ=TO0?p+|N0hqjJRgvp3on>##Y9#$HLuvV0t#Y#Dy(7H#Htf;hf0fP|= zL5hd2y)i)A=vz8LS0Kc$Hwt!^`$Hu-K4zd!*iBU)yt``Mu%l|*G^&Cv5fVRwrB1JX zkKSu_5Ww!C7o+3iq|pO^K8~4ZnPaR#_&|4-xBkzqbQyL<*I?Qu7cG^-8G2zBFi=$8 zeT~5M=>3S!Af%v<&vgni&W!b8`w3gLfA#nGholD3y5W|}u;!SA_y^UEw?PJg%vj~@ z+Iu%o0dOwQYbc8cm~X_;k5|xy#xzhVy}(PU@EE<1 zk?J;XUJ830jN}NRb08#8B|QajKgeaoqn*#Yulrg(q7at(+{sUSvB5e0KnM>14eb_t zAYbqT`{ujg!Sp^safUzO<40nPS>*r4{b>dRAdv936Ve6|n;o!_P%FuTyGRPLn1(W0 z_m~9&ZX(*t@e<{hhWP!12M;hA2&NcX|IH>uk3f3-ry;+fRV`@CKyUT+^Ul(z8I9H&|mQ#>?_wlpO~1K z^sehiU@bJFo0|I7e3vpK+KT(~R#r(vp=_DfmjzCLI=(04)hk)8&j0+t7cAESDX2bH zr0*9s{T03``1$io)QPXxxe=>VqxU<>oC#Ka+9xPzkc{?w0swRMUiR=eLeN@SIVr$a z7APm=Z8S8?ugc~63j)t#?=;M4?rxL`<4A$gkt$KKfi6C^M*(cw`Ysc zYHgc@LjF_idA;iXVb6bd!XL7s+wqkc1UHe1Huu$5VtZD8;|!{=s5r`QmwGaKs|bm7 z8G2s)Hw_J1{ry=-a)OsuZ1i*RfhZ2VY9MJ)HXBIo0a{A+#0f3F?b7!CUV^dc-68>e zfx#bJwnVHE)AqYy0V>uDJeg5Yz|sv6X+E}OyK125ULKx;7HLBLnCRSiLBy-_rSa93;eP3~$U%t& z#msL!Tm`yACReTqDVv})jsC{a(2$qZxPO?6t*zG{NV-*~!i#$L9e|^FrixBxfEa4U zSG)Z9l$mTab#;5poj}M`npEH#l@tt8{TYua@{R7+riA-Kj`^K^B0@c6s&;!EfGih>MjAS@Xf4Lg2)m+^amqQ_DR zWnrJNFvcQQ>m2^ulzA8_$p7~P;^IG<>c2N?j14r05MQKP&3$$jy;V{08Zciam-+Ui zF9~;JO$0`xK(%684E%g&dpqk%OSu}h7^khpZ}-sA)AtU)klI%*wYl?)i;Ii9szi%W zV%}d27*SAEjMfag42VV{unM`kxt3n+{VIMBkvI@%GDrWHkMSoI>esOfodaqU%;rwZ zl?DF^D!iZ?9~%oc=XB$MW5ikl`Q0?y4*oRkc6!F88_&PB+_watGN`_$<`$GIgVZ+I zrZj>y0}+m(GLr}AZ>DV(;p8NUrfHlCF=IR#x|&OtqrW2f0Ib(PU_wv6f6#)}&yun@ z97f9l$#r{pyT1r7`*f+5^^Yb2-@JACU%vS7*ajnsT%acJZ`$cxqE(exY4?2MKcDJ8 zCea{1+!i5+csxiYHvL`N?4{RT!Iu7YjR$WhYBJV#z89*I3A&+^Z~#CK;A9&k$IiIz zNeH%5`PX$C>wRi=WO#Uafg%+%!ayKF%v)WN6vf9+H2Cz#)djw>NcohKB0lo&rA&xr ziZ_<|(&fwBZkOtnK6?CEG&|Dv@2`V>y2&5^b%^z)4gq@rb%VCA?0Cz_?1bNfYEtSi z+y+fa#*u->bTKR!i~&TGbkT_vF8{e(OITY&Bg|xE={L8E7%6FK`Q}ZKlDN^bENRA1 zmjFx(sE+(vfcxTZ{!qiDuKj2Uy--$F{dBf0P+_UK@G9O^>7}^&(Mmr7-PTl>CJeen z9P;Yb&V!*JzO|5SJ;g1rBqDFv&BJ3nPqgkH(3?VTAI)J+RrJmh<|yjZryB8~ zkdTnzU`LfLIv$U<9#q~lg%}aFzqd<{cECglx^Oq}2rn+fo?vRIjLqOVjHzL}%FoB= z0muj)9Y6;}%h#{kVYIs(zMg5D=U}3g&L6RCd^g*zulM)zK9HdwM1ehMoz zN4W=ytG(q_tKoKXehX?K(A%R^NJ&UTB8P#OO`k1t+zJB^ldHYJQ=_4J9aP>Rpx)o1Gs6Kk|K*W#?F}Am7HO@nH9O(*+%$oqJ8%My%`Xh?+OQ;7G~fPG{v^=;pXMNf%83Dn27{~Y{wTO2#YU{spHj}n8-+>+={Q^i0npQi{71YKtzjPeG-OZ0X<*~+%PIV z1gvcewIcoUHyIg)z5tJ*`0*P9vtA0FcWK?v$$7ph!xs8h_KR~#&tnB)MCAS|gy5Tfa51)-gax>W#t>Cg$Vn0EK2E=oty@A6vu;Lg% zQ2kEG`uxGZa@(XU`aJ?a&KZT*gg-a9rq!o??Np!cwR%tVeHPc=Q7%`{ms)6MJ0GQa zaziFa;WdvnL{g9Q?Wukn*8fPXX>!QRX*O@iyjp%_*lj2`_yS>^5XAEQe9(OcHVX=q z8vE8-8T5k7lri>HC+60Zk>|8cAxUK z<9$EAVoStQ{C30fQUk>1x+g;KK42#>6oaw1Ngb170fvdSjVdq`JiezismD9PI`XI` zq7`I_r*BLXBKZPd2kr`9D1jAN7zvat3{@$~ztW-RrN>m`T%}i$R}l;I7nVG~^E$e06*Z@aazuHXNF`-Mem(j6?c4OqSznIrOK)q|Qf~RY z*HW0YU@p}&u#~iyZo?a{%6Low?c_}EX68*NErLTj$ zvWP&WR^eq@>)Xt7l4t*>@GMo}kyM%0X8Rv9VL?j1ygSk#NP0ygSD>LSQy1;M1& diff --git a/docs/assets/deploymentModel-full.puml b/docs/assets/deploymentModel-full.puml index 40db288f10..5294cc9283 100644 --- a/docs/assets/deploymentModel-full.puml +++ b/docs/assets/deploymentModel-full.puml @@ -17,55 +17,21 @@ cloud "The Internet" { } package "Aries Cloud Agent" as vx { - package Core as core { - component "Transport Plugins" as tp - package "Conductor" as cond { - component "Msg Receiver\nGet Thread State" as mr - component "Msg Sender\nPut Thread State" as ms - } - component "Dispatcher" as disp - component "Handler API" as hapi - } - package "Protocols" as prot { + component "Core Capabilities" as core + package "DIDcomm Protocols" as prot { component "Protocol 1\nProtocol 2\n.\n.\nProtocol n" as protos } component "Controller\nREST API" as rest - package "Handler Plugins" as inthand { - component "Storage Manager\nWallet Manager\nIndy Node Pool Manager\n.\n.\n." as intmgrs - } - package "DID Method SDK" as is { - database "Secure Storage" as wallet - } - intmgrs --> wallet - } package "Controller" as per { - component "HTTP Handler" as http - database "Agent\nController\nConfiguration" as config - component "Initiator" as init - component "Responder" as resp - '' database "Configuration\nFiles" as configdb - component "Business\nLogic" as si - '' config --> configdb - si --> init - resp --> si - config --> si + component "Application \nBusiness\nLogic" as bl } -others -down-> tp -tp --> mr -ms --> tp -mr --> disp -hapi -> intmgrs -ms --> hapi -mr --> hapi -protos --> hapi +others -down-> core +protos -up-> core rest -right-> protos -protos --> http -disp <--> protos -disp --> ms -intmgrs -up-> DL -http --> resp -resp --> rest -init --> rest +protos --> bl +bl --> rest +core <--> protos +core -up-> SN From 554dc1bae6e4a0e1932facd18c3db612ea38e1cf Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Sun, 30 Jun 2019 15:28:38 +0000 Subject: [PATCH 4/7] OpenAPI Demo and initial Getting Started guide Signed-off-by: Stephen Curran --- demo/AriesOpenAPIDemo.md | 255 ++++++++++++++++++ .../AriesAgentArchitecture.md | 15 ++ docs/GettingStartedAriesDev/AriesBasics.md | 16 ++ .../GettingStartedAriesDev/AriesBigPicture.md | 28 ++ .../AriesDeveloperDemos.md | 0 docs/GettingStartedAriesDev/AriesMessaging.md | 17 ++ .../DecentralizedIdentityDemos.md | 31 +++ docs/GettingStartedAriesDev/IndyBasics.md | 15 ++ docs/GettingStartedAriesDev/README.md | 21 ++ 9 files changed, 398 insertions(+) create mode 100644 demo/AriesOpenAPIDemo.md create mode 100644 docs/GettingStartedAriesDev/AriesAgentArchitecture.md create mode 100644 docs/GettingStartedAriesDev/AriesBasics.md create mode 100644 docs/GettingStartedAriesDev/AriesBigPicture.md create mode 100644 docs/GettingStartedAriesDev/AriesDeveloperDemos.md create mode 100644 docs/GettingStartedAriesDev/AriesMessaging.md create mode 100644 docs/GettingStartedAriesDev/DecentralizedIdentityDemos.md create mode 100644 docs/GettingStartedAriesDev/IndyBasics.md create mode 100644 docs/GettingStartedAriesDev/README.md diff --git a/demo/AriesOpenAPIDemo.md b/demo/AriesOpenAPIDemo.md new file mode 100644 index 0000000000..654a7d577a --- /dev/null +++ b/demo/AriesOpenAPIDemo.md @@ -0,0 +1,255 @@ + + +# Aries OpenAPI Demo + +This demo is for developers that are comfortable with playing around with APIs using the OpenAPI (Swagger) user interface and JSON. The controller for each of the two agent instances in the demo is you. You drive the API exposed by the agent instances to respond to events received by each agent. The demo covers two agents, Alice and one representing the Government Driver’s Licence program. The two agents connect, and then the Government’s Department of Motor Vehicles (DMV) agent issues a Driver Licence credential to Alice, and then asks Alice to prove she possesses the credential. Who knows why the DMV Agent needs to get the proof, but it lets us show off more protocols. + +- [Aries OpenAPI Demo](#Aries-OpenAPI-Demo) + - [Prerequisites](#Prerequisites) + - [Starting Up](#Starting-Up) + - [Start the VON Network](#Start-the-VON-Network) + - [Running the DMV Agent](#Running-the-DMV-Agent) + - [Running Alice’s Agent](#Running-Alices-Agent) + - [Restarting the Demo](#Restarting-the-Demo) + - [Running the Demo Steps](#Running-the-Demo-Steps) + - [Establishing a Connection](#Establishing-a-Connection) + - [Notes](#Notes) + - [Preparing to Issue a Credential](#Preparing-to-Issue-a-Credential) + - [Register the DMV DID](#Register-the-DMV-DID) + - [Publish the Schema](#Publish-the-Schema) + - [Publishing a Credential Definition](#Publishing-a-Credential-Definition) + - [Notes](#Notes-1) + - [Issuing a Credential](#Issuing-a-Credential) + - [Notes](#Notes-2) + - [Bonus Points](#Bonus-Points) + - [Requesting/Presenting a Proof](#RequestingPresenting-a-Proof) + - [Notes](#Notes-3) + - [Conclusion](#Conclusion) + +## Prerequisites + +To run the demo, you must have a system capable of running docker to run containers, and terminal windows running bash. On Windows systems, we highly recommend using git-bash, the Windows Subsystem for Linux (WSL) or a comparable facility. The demo will not work using PowerShell. + +You can also run the agents using Python on your native system, but docker development is sooo much nicer. + +Before beginning, clone, or fork and clone this repo and the von-network repo. + +## Starting Up + +To begin the demo, open up three terminal windows, one for each agent - one to run a local Indy network (using the VON network) and one each for the DMV’s Agent and Alice’s Agent. You’ll also open up three browser tabs, one to allow browsing the Indy network ledger, and one for the OpenAPI user interface for each of the agents. + +### Start the VON Network + +In one of the terminal windows, follow the instructions [here](https://github.com/bcgov/von-network#running-the-network-locally) to start (but don’t stop) a local four-node Indy network. In one of the browser tabs, navigate to [http://localhost:9000](http://localhost:9000) to see the ledger browser user interface and to verify the Indy network is running. + +> NOTE: The use of localhost for the Web interfaces is assumed in this tutorial. If your docker setup is atypical, you may use a different address for your docker host. + +### Running the DMV Agent + +To start the DMV agent, open up a second terminal window and in it change directory to the root of your clone of this repo and execute the following command: + +```bash +PORTS="5000:5000 10000:10000" ./scripts/run_docker -it http 0.0.0.0 10000 -ot http --admin 0.0.0.0 5000 -e http://`docker run --net=host codenvy/che-ip`:10000 --genesis-url http://`docker run --net=host codenvy/che-ip`:9000/genesis --seed 00000000000000000000000000000000 --auto-ping-connection --accept-invites --accept-requests --auto-verify-presentation --wallet-type indy --label "DMV Agent" +``` + +If all goes well, the agent will show a message indicating it is running. Use the second of the browser tabs to navigate to [http://localhost:5000](http://localhost:5000). You should see an OpenAPI user interface with a (long-ish) list of API endpoints. These are the endpoints exposed by the DMV Agent. + +The `run_docker` script provides a lot of options for configuring your agent. We’ll cover the meaning of some of those options as we go. One thing you may find odd right off is this - `docker run --net=host codenvy/che-ip`. It is an inline command that invokes docker to run a container that returns the IP of the Docker Host. It’s the most portable way we know to get that information for docker running on MacOS, Windows or Linux. + +### Running Alice’s Agent + +To start Alice’s agent, open up a third terminal window and in it change directory to the root of your clone of this repo and execute the following command: + +``` bash +PORTS="5001:5001 10001:10001" ./scripts/run_docker -it http 0.0.0.0 10001 -ot http --admin 0.0.0.0 5001 -e http://`docker run --net=host codenvy/che-ip`:10001 --genesis-url http://`docker run --net=host codenvy/che-ip`:9000/genesis --seed 00000000000000000000000000000001 --auto-ping-connection --accept-invites --accept-requests --auto-verify-presentation --auto-respond-credential-offer --auto-respond-presentation-request --wallet-type indy --label "Alice Agent" +``` + +If all goes well, the agent will show a message indicating it is running. Use the third tab to navigate to [http://localhost:5001](http://localhost:5001). Again, you should see an OpenAPI user interface with a list of API endpoints, this time the endpoints for Alice’s agent. + +### Restarting the Demo + +When you are done, or to stop the demo so you can restart it, carry out the following steps: + +1. In the DMV and Alice agent terminal windows, hit Ctrl-C to terminate the agents. +2. In the von-network terminal window, hit Ctrl-C to stop the logging, and then run the command `./manage down` to both stop the network and remove the data on the ledger. + +### Running the Demo Steps + +The demo is run entirely in the Browser tabs - DMV ([http://localhost:5000](http://localhost:5000)), Alice ([http://localhost:5001](http://localhost:5001)) and the Indy public ledger ([http://localhost:9000](http://localhost:9000)). The agent terminal windows will only show messages if an error occurs in using the REST API. The Indy public ledger window will display a log of messages from the four nodes of the Indy network. In the instructions that follow, we’ll let you know if you need to be in the DMV, Alice or Indy browser tab. We’ll leave it to you to track which is which. + +Using the OpenAPI user interface is pretty simple. In the steps below, we’ll indicate what API endpoint you need use, such as `POST /connections/create-invitation`. That means you must: + +1. Scroll to and find that endpoint. +2. Click on the endpoint name to expand its section of the UI. +3. Click on the `Try it now` button. +4. Fill in any data necessary to run the command. +5. Click `Execute` +6. Check the response to see if the request worked. + +So, the mechanical steps are easy. It’s fourth step from the list above that can be tricky. Supplying the right data and, where JSON is involved, getting the syntax correct - braces and quotes. When steps don’t work, start your debugging by looking at your JSON. + +Enough with the preliminaries, let’s get started! + +## Establishing a Connection + +We’ll start the demo by establishing a connection between Alice’s and the DMV’s agents. We’re starting there to demonstrate that you can use agents without having a ledger. We won’t be using the Indy public ledger at all for this step. Since the agents communicate using DIDcomm messaging and connect by exchanging pairwise DIDs and DIDDocs based on the `did:peer` DID method, a public ledger is not needed. + +In the DMV browser tab, execute the **`POST /connections/create-invitation`**. No data is needed to be added for this call. If successful, you should see a connection ID, an invitation, and the invitation URL. The IDs will be different on each run. + +Copy the entire block of the `invitation` object, from the curly brackets {}, excluding the trailing comma. + +Switch to the Alice browser tab and get ready to execute the **`POST /connections/receive-invitation`** section. Erase the pre-populated text and paste the invitation object from the DMV tab. When you click `Execute` a you should get back a connection ID, an invitation key, and the state of the connection, which should be `requested`. + +Scroll to and execute **`GET /connections`** to see a list of the connections, and the information tracked about each connection. You should see the one connection Alice’s agent has, that it is with the DMV agent, and that its status is `active`. + +You are connected! Switch to the DMV agent browser tab and run the same **`GET /connections`** endpoint to see the DMV view. Hint - note the `connection_id`. You’ll need ot later in the tutorial. + +### Notes + +For those familiar with the `Establish Connection` DIDcomm protocol, you might wonder why there was not an `accept-request` sent by the DMV agent. That is because in the start up parameters for the DMV agent, we used the options `--accept-invites --accept-requests`. With those set, the DMV agent accepts invites and requests automatically, without notifying the controller or waiting on an API call from the controller before proceeding. Had those not been set, the DMV controller (in this case - you), would have had to dig through the protocol state, requested a new connection be created (generating a new pairwise DID in the process) and constructed a response to the request to accept the invitation. Easily done with a controller program, but a bit of a pain when the controller is a person. Alice’s agent used similar settings to simplify the process on her side. + +## Preparing to Issue a Credential + +The next thing we want to do in the demo is have the DMV agent issue a credential to Alice’s agent. To this point, we have not used the Indy ledger at all. The connection and all the messaging is done with pairwise DIDs based on the `did:peer` method. Verifiable credentials must be rooted in a public DID ledger to enable the presentation of proofs. + +Before the DMV agent can issue a credential, it must register a DID on the Indy public ledger, publish a schema, and create a credential definition. In the “real world”, the DMV agent would do this before connecting with any other agents, but we’ll do those steps now, Of course in the “real world”, we don’t have controller’s that are people running agents using an OpenAPI user interface. + +### Register the DMV DID + +To write transactions to the Indy ledger, we have to first register a DID for the DMV agent. + +In the startup parameters for the DMV agent, we specified a seed for the DMV agent, so we need to use that exact string to register the DID on the ledger. We’ll use the Indy ledger browser user interface to do that: + +1. On the ledger browser tab, go to the section “Authenticate a New DID”. +2. Choose the “Register from seed” option. Paste a seed of 32 zeros (00000000000000000000000000000000) into the “Wallet seed” text area. That matches the seed parameter we used in starting the DMV agent. + 1. Note: If you chose the Register from DID option, would need both DID and Verkey which is annoying, so just use the seed option. +3. Click **Register DID. **If successful, should see the following: + +**Identity successfully registered:** +``` +Seed: 00000000000000000000000000000000 +DID: 4QxzWk3ajdnEA37NdNU5Kt +Verkey: 2ru5PcgeQzxF7QZYwQgDkG2K13PRqyigVw99zMYg8eML +``` + +### Publish the Schema + +To publish that schema, go to the DMV browser and get ready to execute the **`POST /schemas`** endpoint. Fill in the text box with the following JSON that defines the schema we’ll use: + +``` JSONC +{ + + "schema_name": "drivers-licence", + "attributes": [ + "age" , "hair_colour" + ], + "schema_version": "1.0" +} +``` + +Click `Execute`. If successful, you should see a `schema_id` in the response, most likely: `4QxzWk3ajdnEA37NdNU5Kt:2:drivers-licence:1.0`. This ID will be used for later steps in the tutorial. + +To confirm the schema was published, let’s check the Indy network transactions. Go to the Indy ledger browser tab and click the **Domain **button, bottom left. Scroll to the bottom of the page. The last entry (#7) should be the published schema. + +Schema published! + +### Publishing a Credential Definition + +Next up, we’ll publish the Credential Definition on the ledger. On the DMV browser tab get ready to execute the `POST credential-definition` endpoint. Fill in the text box with the schema ID from early (or just copy the text below) in the following JSON: + +``` JSONC +{ + "schema_id": "4QxzWk3ajdnEA37NdNU5Kt:2:drivers-licence:1.0" +} +``` + +Click `Execute`. This step will take a bit of time as there is a lot going on in the indy-sdk to create and publish a credential definition - lots of keys being generated. Fortunately, this is not a step that happens very often, and not with real-time response requirements. You might want to open the `von-network` terminal window to see the Indy ledger nodes messaging one another. Once execution completes you should see the resulting credential definition ID, specifically: `4QxzWk3ajdnEA37NdNU5Kt:3:CL:7:default`. + +You can confirm the credential definition was published by going back to the Indy ledger browser tab, where you should still be on the `Domain` page. Refresh, scroll to the bottom and you should see transaction #8 - the new credential definition. + +### Notes + +OK, we have the one time setup work for issuing a credential complete. We can now issue 1 or a million credentials without having to do those steps again. Astute readers might note that we did not setup a revocation registry, so we cannot revoke the credentials we issue with that credential definition. You can’t have everything (and we’re still working on enabling that). + +## Issuing a Credential + +Issuing a credential from the DMV agent to Alice’s agent is easy. In the DMV browser tab, scroll down to the `**POST /credential_exchange/send`** and get ready to (but don’t yet) execute the request. Before execution, you need to find some other data to complete the JSON. + +First, scroll back up to the `GET /connections` API endpoint and execute it. From the result, find the the `connection_id` and copy the value. Go back to the `/credential_exchange/send` section and paste it as the value for the `connection_id` + +Next, scroll down to the `POST /credential-definitions` section that was executed in the previous step. Expand it (if necessary) and find and copy the value of the `credential_definition_id`. You could also get it from the Indy Ledger browser tab, or from earlier in this tutorial. Go back to the `POST /credential_exchange/send` section and paste it as the value for the `credential_defintion_id`. + +Finally, for the credential values, put the following between the curly brackets: + +``` +"age" : "19", "hair_colour" : "brown" +``` + +Ok, finally, you are ready to click `Execute`. The request should work, but if it doesn’t - check your JSON! Did you get all the quotes and commas right? + +To confirm the issuance worked, scroll up to the top of the credential_exchange section and excute the **`GET /credential_exchange`** endpoint. You should see a lot of information about the exchange, including the state - `issued`. + +Let’s look at it from Alice’s side. Switch to the Alice’s agent browser tab, find the `Credentials` section and within that, execute the **`GET /credentials`** endpoint. There should be a list of credentials held by Alice, with just a single entry, the credential issued from the DMV agent. + +You’ve done it, issued a credential! W00t! + +### Notes + +Those that know something about the Indy process for issuing a credential and the DIDcomm `Issue Credential` protocol know that there a multiple steps to issuing credentials, a back and forth between the Issuer and the Holder to (at least) offer, request and issue the credential. All of those messages happened, but the two agents took care of those details rather than bothering the controller (you, in this case) with managing the back and forth. + +* On the DMV agent side, this is because we used the **`POST /credential_exchange/send`** administrative message, which handles the back and forth for the issuer automatically. We could have used the other `/credential_exchange/` endpoint to allow the controller to handle each step of the protocol. +* On Alice's agent side, this is because in the startup options for the agent, we used the `--auto-respond-credential-offer` parameter. + +### Bonus Points + +If you would like to manually perform all of the issuance steps on the DMV agent side, use a sequence of the other `/credential_exchange/` messages. Use the **`GET /credential_exchange`** to both check the credential exchange state as you progress through the protocol and to find some of the data you’ll need in executing the sequence of requests. If you want to run both the DMV and Alice sides in sequence, you’ll have to rerun the tutorial with Alice’s agent started without the `--auto-respond-credential-offer` parameter set. + +## Requesting/Presenting a Proof + +Alice now has her DMV credential. Let’s have the DMV agent send a request for a presentation (a proof) using that credential. This should be pretty easy for you at this point. + +From the DMV browser tab, get ready to execute the **`POST /presentation_exchange/send_request`** endpoint. Replace the pre-populated text with the following. In doing so, use the techniques we used in issuing the credential to replace the `string` values for each instance of `cred_def_id` (there are two) and `connection_id`. + +``` JSONC +{ + "requested_predicates": [ + { + "name": "age", + "p_type": ">=", + "restrictions": [ + {"cred_def_id" : "string"} + ], + "p_value": 18 + } + ], + "requested_attributes": [ + { + "name": "hair_colour", + "restrictions": [ + {"cred_def_id" : “string"} + ] + } + ], + "name": "bar-checks", + "version": "1.0", + "connection_id": "string" +} +``` + +Notice that the proof request is using a predicate to check if Alice is older than 18 without asking for her age. Click `Execute` and cross your fingers. If the request fails check your JSON! + +Note that in the response, the state is `request_sent`. That is because when the HTTP response was generated (immediately after sending the request), Alice’s agent had not yet responded to the request. We’ll have to do another request to verify the presentation worked. Copy the value of the `presentation_exchange_id` field from the response and use it in executing the **`GET /presentation_exchange/{id}`** endpoint. That should return a result showing a status of `verified`. Proof positive! + +### Notes + +As with the issue credential process, the agents handled some of the presentation steps without bothering the controller. In this case, Alice’s agent processed the presentation request automatically because it was started with the `--auto-respond-presentation-request` parameter set, and her wallet contained exactly one credential that satisfied the presentation-request from the DMV agent. Similarly, the DMV agent was started with the `--auto-verify-presentation` parameter and so on receipt of the presentation, it verified the presentation and updated the status accordingly. + +## Conclusion + +That’s the OpenAPI-based tutorial. Feel free to play with the API and learn how it works. More importantly, as you implement a controller, use the OpenAPI user interface to test out the calls you will be using as you go. + + diff --git a/docs/GettingStartedAriesDev/AriesAgentArchitecture.md b/docs/GettingStartedAriesDev/AriesAgentArchitecture.md new file mode 100644 index 0000000000..398b74373e --- /dev/null +++ b/docs/GettingStartedAriesDev/AriesAgentArchitecture.md @@ -0,0 +1,15 @@ +# Aries Cloud Agent Internals: Agent and Controller + +This section talks in particular about the architecture of this Aries cloud agent implementation. An instance of an Aries agent is actually made up of to two parts - the agent itself and a controller. + +![ACA-Py Deployment Overview](../assets/deploymentModel-full.png "ACA-Py Deployment Overview") + +The agent handles all of the core Aries functionality such as interacting with other agents, managing secure storage, sending event notifications to, and receiving directions from, the controller. The controller provides the business logic that defines how that particular agent instance behaves--how to respond to events in the agent, and when to trigger the agent to initiate events. The controller might be a web or native user interface for a person or it might be coded business rules driven by an enterprise system. + +Between the two is a simple interface. The agent sends event notifications to the controller and the controller sends administrator messages to the agent. The controller registers a webhook with the agent, and the event notifications are HTTP callbacks, and the agent exposes a REST API to the controller for all of the administrative messages it is configured to handle. Each of the DIDcomm protocols supported by the agent adds a set of administrative messages for the controller to use in responding to events. The Aries cloud agent includes an [OpenAPI](https://swagger.io/tools/swagger-ui/) (aka Swagger) user interface for a developer to use to explore the API for a specific agent. + +As such, the agent is just a configured dependency in an Aries cloud agent deployment. Thus, the vast majority of Aries developers will focus on building controllers (business logic) and perhaps some custom plugins (protocols, as we'll discuss soon) for the agent. Only a relatively small group of Aries cloud agent maintainers will focus on adding and maintaining the agent dependency. + +Want more details about the agent and controller internals? Take a look at the [Aries cloud agent deployment model](../deploymentModel.md) document. + +> Back to the [Aries Developer - Getting Started Guide](README.md). \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/AriesBasics.md b/docs/GettingStartedAriesDev/AriesBasics.md new file mode 100644 index 0000000000..2f0904e907 --- /dev/null +++ b/docs/GettingStartedAriesDev/AriesBasics.md @@ -0,0 +1,16 @@ +# What is Aries? + +[Hyperledger Aries](https://www.hyperledger.org/projects/aries) provides a shared, reusable, interoperable tool kit designed for initiatives and solutions focused on creating, transmitting and storing verifiable digital credentials. It is infrastructure for blockchain-rooted, peer-to-peer interactions. It includes a shared cryptographic wallet for blockchain clients as well as a communications protocol for allowing off-ledger interaction between those clients. + +A Hyperledger Aries agent (such as the one in this repository): + +* enables establishing connections with other DIDcomm-based agents (using DIDcomm encryption envelopes), +* exchanges messages between connected agents to execute message protocols (using DIDcomm protocols) +* sends notifications about protocol events to a controller, and +* exposes an API for responses from the controller with direction in handling protocol events. + +The concepts and features that make up the Aries project are documented in the [aries-rfcs](https://github.com/hyperledger/aries-rfcs) - but **don't** dive in there yet! We'll get to the features and concepts to be found there with a guided tour of the key RFCs. The [Aries Working Group](https://wiki.hyperledger.org/display/ARIES/Aries+Working+Group) meets weekly to expand the design and components of Aries. + +The Aries Cloud Agent Python currently only supports Hyperledger Indy-based verifiable credentials and public ledger. Longer term (as we'll see later in this guide) protocols will be extended or added to support other verifiable credential implementations and public ledgers. + +> Back to the [Aries Developer - Getting Started Guide](README.md). \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/AriesBigPicture.md b/docs/GettingStartedAriesDev/AriesBigPicture.md new file mode 100644 index 0000000000..7c3099f277 --- /dev/null +++ b/docs/GettingStartedAriesDev/AriesBigPicture.md @@ -0,0 +1,28 @@ +# Aries Agents in context: The Big Picture + +Aries agents can be used in a lot of places. This classic Indy Architecture picture shows five agents - the four around the outside (on a phone, a tablet, a laptop and an enterprise server) are referred to as "edge agents", and many cloud agents in the blue circle. + +![image](https://cryptocalibur.com/wp-content/uploads/2018/06/sovrin-ico-3-600x402.png) + +The agents in the picture shares many attributes: + +- The have some sort of storage for keys and other data related to their role as an agent +- They interact with other agents using secure. peer-to-peer messaging protocols +- They have some associated mechanism to provide "business rules" to control the behaviour of the agent + - That is often a person for phone, tablet, laptop, etc. based agents + - That is often backend enterprise systems for enterprise agents + - Business rules for cloud agents are often about the routing of messages to and from edge agents + +While there can be many other agent setups, the picture above shows the most common ones - edge agents for people, edge agents for organizations and cloud agents for routing messages (although cloud agents could be edge agents. Sigh...). A signifcant emerging use case missing from that picture are agents embedded within/associated with IoT devices. In the common IoT case, IoT device agents are just varients of other edge agents, connected to the rest of the ecosystem through a cloud agent. All the same principles apply. + +Misleading in the picture is that (almost) all agents connect directly to the Ledger network. In this picture it's the Sovrin ledger, but that could be any Indy network (e.g. set of nodes running indy-node software) and in future, ledgers from other providers. That implies most agents embed the ledger SDK (e.g. indy-sdk) and makes calls to the ledger SDK to interact with the ledger and other SDK controlled resources (e.g. secure storage). Thus, unlike what is implied in the picture, edge agents (commonly) do not call an cloud agent to interact with the ledger - they do it directly. Super small IoT devices are an instance of an exception to that - lacking compute/storage resources and/or connectivity, they might communicate with a cloud agent that would communicate with the ledger. + +While current Aries agents currently only support Indy-based ledgers, the intention is to add support for other ledgers. + +The (most common) purpose of cloud agents is to enable secure and privacy preserving routing of messages between edge agents. Rather than messages going directly from edge agent to edge agent (which is often impossible - for example sending to a mobile agent), messages sent from edge agent to edge agent are routed through a sequence of cloud agents. Some of those cloud agents might be controlled by the sender, some by the receiver and others might be gateways owned by agent vendors (called "Agencies"). In all cases, an edge agent tells routing agents "here's how to send messages to me", so a routing agent sending a message only has to know how to send a peer-to-peer message. While quite complicated, the protocols used by the agents largely take care of this complexity, and most developers don't have to know much about it. + +Note the many caveats in this section - "most common", "commonly", etc. There are many small building blocks available in Aries and underlying components that can be combined in infinite ways. We recommend not worrying about the alternate use cases for now. Focus on understanding the common use cases while remembering that other configurations are possible. + +We also recommend **not** digging into all the layers described here. Just as you don't have to know how TCP/IP works to write an web app, you don't need to know how indy-node or indy-sdk work to be able to build your first Aries-based application. Later in this guide we'll covering the starting point you do need to know. + +> Back to the [Aries Developer - Getting Started Guide](README.md). \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/AriesDeveloperDemos.md b/docs/GettingStartedAriesDev/AriesDeveloperDemos.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/GettingStartedAriesDev/AriesMessaging.md b/docs/GettingStartedAriesDev/AriesMessaging.md new file mode 100644 index 0000000000..8095dfc236 --- /dev/null +++ b/docs/GettingStartedAriesDev/AriesMessaging.md @@ -0,0 +1,17 @@ +# An overview of Aries messaging + +Aries Agents communicate with other agents via a message mechanism called DIDcomm (DID Communication). DIDcomm enables secure, asynchronous, end-to-end encrypted messaging between agents, with messages (usually) routed through some configuration of intermediary agents. Aries agents use (an early instance of) the [did:peer DID method](https://dhh1128.github.io/peer-did-method-spec/index.html), which uses DIDs that are not published to a public ledger, but shared privately between the communicating parties - usually just two agents. + +Given the underlying secure messaging layer (routing and encryption covered later in the "Deeper Dive" sections), DIDcomm protocols define standard sets of messages to accomplish a task. For example: + +* The "establish connection" protocol enables two agents to establish a connection through a series of messages - an invitation, a connection request and a connection response. +* The "issue credential" protocol enables an agent to issue a credential to another agent. +* The "present proof" protocol enables an agent to request and receive a proof from another agent. + +Each protocol has a specification that defines the protocol's messages, one or more roles for the different participants, and a state machine that defines the state transitions triggered by the messages. For example, in the connection protocol, the message are "invitation", "connectionRequest" and "connectionResponse", the roles are "inviter" and "invitee", and the states are "invited", "requested" and "connected". Each participant in an instance of a protocol tracks the state based on the messages they've seen. + +Code for protocols are implemented as externalized modules from the core agent code so that they can be included (or not) in an agent deployment. The protocol code must include the definition of a state object for the protocol, handlers for the protocol messages, and the events and administrative messages that are available to the controller to inject business logic into the running of the protocol. Each administrative message becomes part of the REST API exposed by the agent instance. + +Developers building Aries agents for a particular use case will generally focus on building controllers. They must understand the protocols that they are going to need, including the events the controller will receive, and the protocol's administrative messages exposed via the REST API. From time to time, such Aries agent developers might need to implement their own protocols. + +> Back to the [Aries Developer - Getting Started Guide](README.md). \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/DecentralizedIdentityDemos.md b/docs/GettingStartedAriesDev/DecentralizedIdentityDemos.md new file mode 100644 index 0000000000..a82fcd9444 --- /dev/null +++ b/docs/GettingStartedAriesDev/DecentralizedIdentityDemos.md @@ -0,0 +1,31 @@ +# Decentralized Identity Use Case Demos + +The following are some demos that you can go through to see verifiable credentials in action. For each of the demos, we've included some guidance on what you should get out of the demo - and where you should **stop** exploring the demos. Later on in this guide we have some command line demos built on current generation code for developers wanting to look at what's going on under the hood. + +### Alice and Faber - edX Version + +The Hyperledger Indy community is littered with "Alice and Faber" demos. Alice is a former student of [Faber College](https://en.wikipedia.org/wiki/Animal_House) (motto: Knowledge is Good), and is offered from Faber a verifiable credential that she can use to prove her educational accomplishments. Alice proves the claims in the credential to get a job at ACME Corp, and then uses a credential about her job at ACME Corp. to get a loan from Thrift Bank. + +The edX course version of the Alie/Faber story is good if you are new to Indy because in going through the story you get a web interface to see the interactions/technical steps in establishing connections between agents and the process for issuing and verifying credentials. **DO NOT** look into the underlying code because it is not maintained, and it is out-of-date. + +We recommend using the "In Browser" steps to run the demo vs. getting things running on your local machine. + +Link: [Alice and Faber - edX Version](https://github.com/hyperledger/education/blob/master/LFS171x/indy-material/nodejs/README.md) + +### BC Gov's OrgBook and Greenlight + +BC Gov's Verifiable Organizations Network (VON) project implemented the first production Indy app (TheOrgBook, and now just "OrgBook") that exists to bootstrap verifiable credentials ecosystems. The Greenlight use case is a demo showing how verifiable credentials can be used for reducing the red tape businesses face in trying to get a government permit (for example, to open a restaurant). The business challenge addressed by Greenlight is figuring out what other permits and licenses need to be in place before a business can get the permit it actually wants. The demo simulates a business identifying their goal permit, seeing a roadmap of the prerequisite credentials already collected and still needed, and using links to get the needed credentials. Since in these early days fo decentralized identity, business don't have their own digital wallet, in applying for each credential, each permitting service is using OrgBook to get proof of the prerequisite credentials, and issuing the new credential back to the OrgBook. + +If you are interested in using/contributing to VON and OrgBook, contact the folks from BC Gov using links on https://vonx.io. + +Link: [Greenlight](https://greenlight.orgbook.gov.bc.ca/) - choose the "City of Surrey - Busienss Licence" +Link: [Information about Verifiable Organizations Network (VON)](https://vonx.io) +Link: [OrgBook BC - Production Instance](https://orgbook.gov.bc.ca/) + +### The IIWBook Mobile Agent Demo + +The IIWBook demo was presented during the [Internet Identity Workshop](https://internetidentityworkshop.com/) (IIW) 28. The demo uses instances of the Aries Cloud Agent - Python-based services interacting with a mobile agent to issue and verify credentials. Follow along with the demo to get a beta Indy mobile agent from Streetcred, use it to get a verifiable credential that you control your email address, and proof the claims from that credential to get a verifiable credential that you attended IIW (or at least added attended a demo presented at IIW). + +Link: [IIWBook Demo](https://vonx.io/how_to/iiwbook) + +> Back to the [Aries Developer - Getting Started Guide](README.md). \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/IndyBasics.md b/docs/GettingStartedAriesDev/IndyBasics.md new file mode 100644 index 0000000000..08eb29f059 --- /dev/null +++ b/docs/GettingStartedAriesDev/IndyBasics.md @@ -0,0 +1,15 @@ +# Indy, Verifiable Credentials and Decentralized Identity Basics + +> **NOTE:** If you are developer building apps on top of Aries and Indy, you **DO NOT** need to know the nuts and bolts of Indy to build applications. You need to know about verifiable credentials and the concepts of self-soveriegn identity. But as an app developer, you don't need to do the Indy getting started pieces. Aries takes care of those details for you. + +If you are new to Indy and verifiable credentials and want to learn the core concepts, this [link](https://github.com/hyperledger/education/blob/master/LFS171x/docs/introduction-to-hyperledger-indy.md) provides a solid foundation into the goals and purpose of Indy including verifiable credentials, DIDs, decentralized/self-sovereign identity, the Sovrin Foundation and more. The document is the content of the Indy chapter of the Hyperledger edX [Blockchain for Business](https://www.edx.org/course/blockchain-for-business-an-introduction-to-hyperledger-technologies) course (which you could also go through). + +Feel free to do the demo that is referenced in the material, but we recommend that you **not** dig into that codebase. It's pretty old now - almost a year! We've got much more relevant examples later in this guide. + +As well, **don't** use the guidance in the course to dive into the content about "Getting Started" with Indy. Come back here as this content is far more relevant to the current state of Indy and Aries. + +## tl;dr + +Indy provides an implementation of the basic functions required to implement a network for self-sovereign identity (SSI) - a ledger, client SDKs for interacting with the ledger, DIDs, and capabilities for issuing, holding and proving verifiable credentials. + +> Back to the [Aries Developer - Getting Started Guide](README.md). \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/README.md b/docs/GettingStartedAriesDev/README.md new file mode 100644 index 0000000000..fc6fddeb5e --- /dev/null +++ b/docs/GettingStartedAriesDev/README.md @@ -0,0 +1,21 @@ +# Becoming an Indy/Aries Developer + +This guide is to get you from (pretty much) zero to developing code for issuing (and verifying) credentials with your own Aries agent. On the way, you'll look at Hyperledger Indy and how it works, find out about the architecture and components of an Aries agent and it's underlying messaging protocols. Scan the list of topics below and jump in as soon as you hit a topic you don't know. + +Note that in the guidance we have here, we include not only the links to look at, but we recommend that you **not** look at certain material to which you might naturally gravitate. That's because the material is out of date and will take you down some unnecessary rabbit holes. Keep your eyes on the goal - developing with Indy and Aries. + +* [I've heard of Indy, but I don't know the basics](IndyBasics.md) +* [I know about Indy, but what is Aries?](AriesBasics.md) +* [Demos - Business Level](DecentralizedIdentityDemos.md) +* [Aries Agents in Context: The Big Picture](AriesBigPicture.md) +* [Aries Internals - Deployment Components](AriesAgentArchitecture.md) +* [An overview of Aries messaging](AriesMessaging.md) +* [Aries Developer Demos](AriesDeveloperDemos.md) +* [Establishing a connection between Aries Agents](Connections.md) +* [Issuing an Indy credential: From Issuer to Holder/Prover](IssuingIndyCredential.md) +* [Presenting an Indy credential: From Holder/Prover to Verifier](PresentingIndyCredential.md) +* [Next steps: Creating your own Aries Agent](YourOwnAriesAgent.md) +* [What should I be developing?](DevOptions.md) +* [Deeper Dive: DIDcomm Message Routing and Encryption](RoutingEncryption.md) +* [Deeper Dive: DIDcomm Messages](DIDcommMsgs.md) +* [Deeper Dive: Running and Connecting to an Indy Network](IndyNetwork.md) \ No newline at end of file From de23271a6b5e08aba797246f9549352c8fc690b6 Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Sun, 30 Jun 2019 20:15:40 +0000 Subject: [PATCH 5/7] Tweaks to tutorial; add rest of the guide sub-docs Signed-off-by: Stephen Curran --- demo/AriesOpenAPIDemo.md | 65 +++++++++---------- .../AgentConnections.md | 3 + .../AriesDeveloperDemos.md | 21 ++++++ docs/GettingStartedAriesDev/AriesMessaging.md | 2 +- .../ConnectIndyNetwork.md | 3 + docs/GettingStartedAriesDev/DIDcommMsgs.md | 11 ++++ .../IndyAriesDevOptions.md | 3 + .../IssuingIndyCredentials.md | 3 + .../PresentingIndyProofs.md | 3 + docs/GettingStartedAriesDev/README.md | 14 ++-- .../RoutingEncryption.md | 37 +++++++++++ .../YourOwnAriesAgent.md | 3 + 12 files changed, 127 insertions(+), 41 deletions(-) create mode 100644 docs/GettingStartedAriesDev/AgentConnections.md create mode 100644 docs/GettingStartedAriesDev/ConnectIndyNetwork.md create mode 100644 docs/GettingStartedAriesDev/DIDcommMsgs.md create mode 100644 docs/GettingStartedAriesDev/IndyAriesDevOptions.md create mode 100644 docs/GettingStartedAriesDev/IssuingIndyCredentials.md create mode 100644 docs/GettingStartedAriesDev/PresentingIndyProofs.md create mode 100644 docs/GettingStartedAriesDev/RoutingEncryption.md create mode 100644 docs/GettingStartedAriesDev/YourOwnAriesAgent.md diff --git a/demo/AriesOpenAPIDemo.md b/demo/AriesOpenAPIDemo.md index 654a7d577a..11468aeedb 100644 --- a/demo/AriesOpenAPIDemo.md +++ b/demo/AriesOpenAPIDemo.md @@ -4,31 +4,30 @@ Source doc: https://docs.google.com/a/cloudcompass.ca/open?id=12N1KDm1l4Az6bSOJ3 -----> -# Aries OpenAPI Demo - -This demo is for developers that are comfortable with playing around with APIs using the OpenAPI (Swagger) user interface and JSON. The controller for each of the two agent instances in the demo is you. You drive the API exposed by the agent instances to respond to events received by each agent. The demo covers two agents, Alice and one representing the Government Driver’s Licence program. The two agents connect, and then the Government’s Department of Motor Vehicles (DMV) agent issues a Driver Licence credential to Alice, and then asks Alice to prove she possesses the credential. Who knows why the DMV Agent needs to get the proof, but it lets us show off more protocols. - -- [Aries OpenAPI Demo](#Aries-OpenAPI-Demo) - - [Prerequisites](#Prerequisites) - - [Starting Up](#Starting-Up) - - [Start the VON Network](#Start-the-VON-Network) - - [Running the DMV Agent](#Running-the-DMV-Agent) - - [Running Alice’s Agent](#Running-Alices-Agent) - - [Restarting the Demo](#Restarting-the-Demo) - - [Running the Demo Steps](#Running-the-Demo-Steps) - - [Establishing a Connection](#Establishing-a-Connection) - - [Notes](#Notes) - - [Preparing to Issue a Credential](#Preparing-to-Issue-a-Credential) - - [Register the DMV DID](#Register-the-DMV-DID) - - [Publish the Schema](#Publish-the-Schema) - - [Publishing a Credential Definition](#Publishing-a-Credential-Definition) - - [Notes](#Notes-1) - - [Issuing a Credential](#Issuing-a-Credential) - - [Notes](#Notes-2) - - [Bonus Points](#Bonus-Points) - - [Requesting/Presenting a Proof](#RequestingPresenting-a-Proof) - - [Notes](#Notes-3) - - [Conclusion](#Conclusion) +# Aries OpenAPI Demo + +This demo is for developers comfortable with playing around with APIs using the OpenAPI (Swagger) user interface and JSON. The controller for each of the two agent instances in the demo is you. You drive the API exposed by the agent instances to respond to events received by each agent. The demo covers two agents, Alice and one representing the Government Driver’s Licence program. The two agents connect, and then the Government’s Department of Motor Vehicles (DMV) agent issues a Driver Licence credential to Alice, and then asks Alice to prove she possesses the credential. Who knows why the DMV Agent needs to get the proof, but it lets us show off more protocols. + +- [Prerequisites](#Prerequisites) +- [Starting Up](#Starting-Up) + - [Start the VON Network](#Start-the-VON-Network) + - [Running the DMV Agent](#Running-the-DMV-Agent) + - [Running Alice’s Agent](#Running-Alices-Agent) + - [Restarting the Demo](#Restarting-the-Demo) + - [Running the Demo Steps](#Running-the-Demo-Steps) +- [Establishing a Connection](#Establishing-a-Connection) + - [Notes](#Notes) +- [Preparing to Issue a Credential](#Preparing-to-Issue-a-Credential) + - [Register the DMV DID](#Register-the-DMV-DID) + - [Publish the Schema](#Publish-the-Schema) + - [Publishing a Credential Definition](#Publishing-a-Credential-Definition) + - [Notes](#Notes-1) +- [Issuing a Credential](#Issuing-a-Credential) + - [Notes](#Notes-2) + - [Bonus Points](#Bonus-Points) +- [Requesting/Presenting a Proof](#RequestingPresenting-a-Proof) + - [Notes](#Notes-3) +- [Conclusion](#Conclusion) ## Prerequisites @@ -106,7 +105,7 @@ Switch to the Alice browser tab and get ready to execute the **`POST /connection Scroll to and execute **`GET /connections`** to see a list of the connections, and the information tracked about each connection. You should see the one connection Alice’s agent has, that it is with the DMV agent, and that its status is `active`. -You are connected! Switch to the DMV agent browser tab and run the same **`GET /connections`** endpoint to see the DMV view. Hint - note the `connection_id`. You’ll need ot later in the tutorial. +You are connected! Switch to the DMV agent browser tab and run the same **`GET /connections`** endpoint to see the DMV view. Hint - note the `connection_id`. You’ll need it later in the tutorial. ### Notes @@ -116,7 +115,7 @@ For those familiar with the `Establish Connection` DIDcomm protocol, you might w The next thing we want to do in the demo is have the DMV agent issue a credential to Alice’s agent. To this point, we have not used the Indy ledger at all. The connection and all the messaging is done with pairwise DIDs based on the `did:peer` method. Verifiable credentials must be rooted in a public DID ledger to enable the presentation of proofs. -Before the DMV agent can issue a credential, it must register a DID on the Indy public ledger, publish a schema, and create a credential definition. In the “real world”, the DMV agent would do this before connecting with any other agents, but we’ll do those steps now, Of course in the “real world”, we don’t have controller’s that are people running agents using an OpenAPI user interface. +Before the DMV agent can issue a credential, it must register a DID on the Indy public ledger, publish a schema, and create a credential definition. In the “real world”, the DMV agent would do this before connecting with any other agents, but we’ll do those steps now, Of course in the “real world”, we don’t have controllers that are people running agents using an OpenAPI user interface. ### Register the DMV DID @@ -127,7 +126,7 @@ In the startup parameters for the DMV agent, we specified a seed for the DMV age 1. On the ledger browser tab, go to the section “Authenticate a New DID”. 2. Choose the “Register from seed” option. Paste a seed of 32 zeros (00000000000000000000000000000000) into the “Wallet seed” text area. That matches the seed parameter we used in starting the DMV agent. 1. Note: If you chose the Register from DID option, would need both DID and Verkey which is annoying, so just use the seed option. -3. Click **Register DID. **If successful, should see the following: +3. Click `Register DID`. If successful, should see the following: **Identity successfully registered:** ``` @@ -153,7 +152,7 @@ To publish that schema, go to the DMV browser and get ready to execute the **`PO Click `Execute`. If successful, you should see a `schema_id` in the response, most likely: `4QxzWk3ajdnEA37NdNU5Kt:2:drivers-licence:1.0`. This ID will be used for later steps in the tutorial. -To confirm the schema was published, let’s check the Indy network transactions. Go to the Indy ledger browser tab and click the **Domain **button, bottom left. Scroll to the bottom of the page. The last entry (#7) should be the published schema. +To confirm the schema was published, let’s check the Indy network transactions. Go to the Indy ledger browser tab and click the **`Domain`** button, bottom left. Scroll to the bottom of the page. The last entry (#7) should be the published schema. Schema published! @@ -177,11 +176,11 @@ OK, we have the one time setup work for issuing a credential complete. We can no ## Issuing a Credential -Issuing a credential from the DMV agent to Alice’s agent is easy. In the DMV browser tab, scroll down to the `**POST /credential_exchange/send`** and get ready to (but don’t yet) execute the request. Before execution, you need to find some other data to complete the JSON. +Issuing a credential from the DMV agent to Alice’s agent is easy. In the DMV browser tab, scroll down to the **`POST /credential_exchange/send`** and get ready to (but don’t yet) execute the request. Before execution, you need to find some other data to complete the JSON. -First, scroll back up to the `GET /connections` API endpoint and execute it. From the result, find the the `connection_id` and copy the value. Go back to the `/credential_exchange/send` section and paste it as the value for the `connection_id` +First, scroll back up to the **`GET /connections`** API endpoint and execute it. From the result, find the the `connection_id` and copy the value. Go back to the `/credential_exchange/send` section and paste it as the value for the `connection_id` -Next, scroll down to the `POST /credential-definitions` section that was executed in the previous step. Expand it (if necessary) and find and copy the value of the `credential_definition_id`. You could also get it from the Indy Ledger browser tab, or from earlier in this tutorial. Go back to the `POST /credential_exchange/send` section and paste it as the value for the `credential_defintion_id`. +Next, scroll down to the **`POST /credential-definitions`** section that was executed in the previous step. Expand it (if necessary) and find and copy the value of the `credential_definition_id`. You could also get it from the Indy Ledger browser tab, or from earlier in this tutorial. Go back to the **`POST /credential_exchange/send`** section and paste it as the value for the `credential_defintion_id`. Finally, for the credential values, put the following between the curly brackets: @@ -230,7 +229,7 @@ From the DMV browser tab, get ready to execute the **`POST /presentation_exchang { "name": "hair_colour", "restrictions": [ - {"cred_def_id" : “string"} + {"cred_def_id" : "string"} ] } ], diff --git a/docs/GettingStartedAriesDev/AgentConnections.md b/docs/GettingStartedAriesDev/AgentConnections.md new file mode 100644 index 0000000000..01caf020e6 --- /dev/null +++ b/docs/GettingStartedAriesDev/AgentConnections.md @@ -0,0 +1,3 @@ +# Establishing a connection between Aries Agents + +To be completed. \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/AriesDeveloperDemos.md b/docs/GettingStartedAriesDev/AriesDeveloperDemos.md index e69de29bb2..0c16fe77a5 100644 --- a/docs/GettingStartedAriesDev/AriesDeveloperDemos.md +++ b/docs/GettingStartedAriesDev/AriesDeveloperDemos.md @@ -0,0 +1,21 @@ +# Developer Demos and Samples of Aries Agent + +Here are some demos that developers can use to get up to speed on Aries. You don't have to be a developer to use these. If you can use docker and JSON, then that's enough to give these a try. + +## Open API demo + +This demo uses agents (and an Indy ledger), but doesn't implement a controller at all. Instead it uses the OpenAPI (aka Swagger) user interface to let you be the controller to connect agents, issue a credential and then proof that credential. + +[Collaborating Agents OpenAPI Demo](../demo/AriesOpenAPIDemo.md) + +## Python Controller demo + +Run this demo to see a couple of simple Python controllers implementations for Alice and Faber. Like the previous demo, this shows the agents connecting, Faber issuing a credential to Alice and then requesting a proof based on the credential. Running the demo is simple, but there's a lot for a developer to learn from the code. + +[Python-based Alice/Faber Demo](../demo/AliceFaberDemo.md) + +## Web App Sample - Email Verification Service + +This live service implements a real credential issuer - verifying a users email address when connecting to an agent and then issuing a "verified email address" credential. This service is used the [IIWBook Demo](https://vonx.io/how_to/iiwbook). + +[Email Verification Service](https://github.com/bcgov/indy-email-verification) diff --git a/docs/GettingStartedAriesDev/AriesMessaging.md b/docs/GettingStartedAriesDev/AriesMessaging.md index 8095dfc236..0cc453d54c 100644 --- a/docs/GettingStartedAriesDev/AriesMessaging.md +++ b/docs/GettingStartedAriesDev/AriesMessaging.md @@ -1,6 +1,6 @@ # An overview of Aries messaging -Aries Agents communicate with other agents via a message mechanism called DIDcomm (DID Communication). DIDcomm enables secure, asynchronous, end-to-end encrypted messaging between agents, with messages (usually) routed through some configuration of intermediary agents. Aries agents use (an early instance of) the [did:peer DID method](https://dhh1128.github.io/peer-did-method-spec/index.html), which uses DIDs that are not published to a public ledger, but shared privately between the communicating parties - usually just two agents. +Aries Agents communicate with each other via a message mechanism called DIDcomm (DID Communication). DIDcomm enables secure, asynchronous, end-to-end encrypted messaging between agents, with messages (usually) routed through some configuration of intermediary agents. Aries agents use (an early instance of) the [did:peer DID method](https://dhh1128.github.io/peer-did-method-spec/index.html), which uses DIDs that are not published to a public ledger, but only shared privately between the communicating parties - usually just two agents. Given the underlying secure messaging layer (routing and encryption covered later in the "Deeper Dive" sections), DIDcomm protocols define standard sets of messages to accomplish a task. For example: diff --git a/docs/GettingStartedAriesDev/ConnectIndyNetwork.md b/docs/GettingStartedAriesDev/ConnectIndyNetwork.md new file mode 100644 index 0000000000..a4be1650d4 --- /dev/null +++ b/docs/GettingStartedAriesDev/ConnectIndyNetwork.md @@ -0,0 +1,3 @@ +# Connecting to an Indy Network + +To be completed. \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/DIDcommMsgs.md b/docs/GettingStartedAriesDev/DIDcommMsgs.md new file mode 100644 index 0000000000..fa52569d8d --- /dev/null +++ b/docs/GettingStartedAriesDev/DIDcommMsgs.md @@ -0,0 +1,11 @@ +# Deeper Dive: DIDcomm Messaging + +DIDcomm peer-to-peer messages are asynchronous messages that one agent sends to another - for example, Faber would send to Alice. In between, there may be other agents and message processing, but at the edges, Faber appears to be messaging directly with Alice using encryption based on the DIDs and DIDDocs that the two shared when establishing a connection. The messages are JSON-LD-friendly messages with a "type" that defines the namespace, protocol, protocol version and type of the message, an "id" that is GUID for the message, and additional fields as required by the message type. The namespace is currently defined to be a public DID that should be globally resolvable to a protocol specification. Currently, "core" messages use a DID that is not yet globally resolvable - Daniel Hardman has the keys associated with the DID. + +Link: [Message Types](https://github.com/hyperledger/aries-rfcs/blob/master/concepts/0020-message-types/README.md) + +As protocols are executed, the data associated with the protocol is stored in the (currently named) wallet of the agent. The data primarily consists of the state object for that instance of the protocol, and any artifacts of running the protocol. For example, when establishing a connection, the metadata associated with the connection (DIDs, DID Documents and private keys) is stored in the agent's wallet. Likewise, ledger data is cached in the wallet (DIDs, schema, credential definitions, etc.) and credentials. This is taken care of by the Aries agent and the protocols configured into the agent. + +## Message Decorators + +In addition to protocol specific data elements in messages, messages can include "decorators", standardized message elements that define cross-cutting behavior. The most common example is the "thread" decorator, which is used to link the messages in a protocol instance. As messages go back and forth between agents to complete an instance of a protocol (e.g. issuing a credential), the [thread decorator](https://github.com/hyperledger/aries-rfcs/tree/master/concepts/0008-message-id-and-threading) data elements let the agents know to which protocol instance the message belongs. Other currently defined examples of decorators include [attachments](https://github.com/hyperledger/aries-rfcs/tree/master/concepts/0017-attachments), [localization](https://github.com/hyperledger/aries-rfcs/blob/master/features/0043-l10n/README.md), [tracing](https://github.com/hyperledger/aries-rfcs/blob/master/features/0034-message-tracing/README.md) and [timing](https://github.com/hyperledger/aries-rfcs/blob/master/features/0032-message-timing/README.md). Decorators are often processed by the core of the agent, but some are processed by the protocol message handlers. For example, the thread decorator processed to retrieve the protocol state object for that instance (thread) of the protocol before control is passed to the protocol message handler. diff --git a/docs/GettingStartedAriesDev/IndyAriesDevOptions.md b/docs/GettingStartedAriesDev/IndyAriesDevOptions.md new file mode 100644 index 0000000000..7bc282e803 --- /dev/null +++ b/docs/GettingStartedAriesDev/IndyAriesDevOptions.md @@ -0,0 +1,3 @@ +# Options for Aries/Indy Developers + +To be completed. \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/IssuingIndyCredentials.md b/docs/GettingStartedAriesDev/IssuingIndyCredentials.md new file mode 100644 index 0000000000..320985d70f --- /dev/null +++ b/docs/GettingStartedAriesDev/IssuingIndyCredentials.md @@ -0,0 +1,3 @@ +# Issuing Indy Credentials + +To be completed. \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/PresentingIndyProofs.md b/docs/GettingStartedAriesDev/PresentingIndyProofs.md new file mode 100644 index 0000000000..5eee3d5df8 --- /dev/null +++ b/docs/GettingStartedAriesDev/PresentingIndyProofs.md @@ -0,0 +1,3 @@ +# Presenting Indy Proofs + +To be completed. \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/README.md b/docs/GettingStartedAriesDev/README.md index fc6fddeb5e..73e03e4aac 100644 --- a/docs/GettingStartedAriesDev/README.md +++ b/docs/GettingStartedAriesDev/README.md @@ -11,11 +11,11 @@ Note that in the guidance we have here, we include not only the links to look at * [Aries Internals - Deployment Components](AriesAgentArchitecture.md) * [An overview of Aries messaging](AriesMessaging.md) * [Aries Developer Demos](AriesDeveloperDemos.md) -* [Establishing a connection between Aries Agents](Connections.md) -* [Issuing an Indy credential: From Issuer to Holder/Prover](IssuingIndyCredential.md) -* [Presenting an Indy credential: From Holder/Prover to Verifier](PresentingIndyCredential.md) -* [Next steps: Creating your own Aries Agent](YourOwnAriesAgent.md) -* [What should I be developing?](DevOptions.md) -* [Deeper Dive: DIDcomm Message Routing and Encryption](RoutingEncryption.md) +* To Do: [Establishing a connection between Aries Agents](AgentConnections.md) +* To Do: [Issuing an Indy credential: From Issuer to Holder/Prover](IssuingIndyCredentials.md) +* To Do: [Presenting an Indy credential: From Holder/Prover to Verifier](PresentingIndyProofs.md) +* To Do: [Next steps: Creating your own Aries Agent](YourOwnAriesAgent.md) +* To Do: [What should I be developing?](IndyAriesDevOptions.md) * [Deeper Dive: DIDcomm Messages](DIDcommMsgs.md) -* [Deeper Dive: Running and Connecting to an Indy Network](IndyNetwork.md) \ No newline at end of file +* [Deeper Dive: DIDcomm Message Routing and Encryption](RoutingEncryption.md) +* To Do: [Deeper Dive: Running and Connecting to an Indy Network](ConnectIndyNetwork.md) \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/RoutingEncryption.md b/docs/GettingStartedAriesDev/RoutingEncryption.md new file mode 100644 index 0000000000..d3bd73c3f6 --- /dev/null +++ b/docs/GettingStartedAriesDev/RoutingEncryption.md @@ -0,0 +1,37 @@ +# Deeper Dive: DIDcomm Message Routing and Encryption + +Many Aries edge agents do not directly receive messages from a peer edge agent - they have agents in between that route messages to them. This is done for many reasons, such as: + +- The agent is on a mobile device that does not have a persistent connection and so uses a cloud agent. +- The person does not want to allow correlation of their agent across relationships and so they use a shared, common endpoint (e.g. https://agents-R-Us.com) that they are "hidden in a crowd". +- An enterprise wants a single gateway to the many enterprise agents they have in their organization. + +Thus, when a DIDcomm message is sent from one edge agent to another, it is routed per the instructions of the receiver and for the needs of the sender. For example, in the following picture, Alice might be told by Bob to send messages to his phone (agent 4) via agents 9 and 3, and Alice might always send out messages via agent 2. + +![image](https://github.com/hyperledger/aries-rfcs/raw/master/features/0067-didcomm-diddoc-conventions/domains.jpg) + +The following looks at how those requirements are met with mediators (for example, agents 9 and 3) and relays (agent 2). + +## Inbound Routing - Mediators + +To tell a sender how to get a message to it, an agent puts into the DIDDoc for that sender a service endpoint for the recipient (with an encryption key) and an ordered list (possibly empty) of routing keys (called "mediators") to use when sending the message. To send the message, the sender must: + +- Prepare the message to be sent to the recipient +- Successively encrypt and wrap the message for each intermediate mediator in a "forward" message - an envelope. +- Encrypt and send the message to the first agent in the routing + +Note that when an agent uses mediators, it is there responsibility to notify any mediators that need to know of the new relationship that has been formed using the connection protocol and the routing needs of that relationship - where to send messages that arrive destined for a given verkey. Mediator agents have what amounts to a routing table to know when they receive a forward message for a given verkey, where it should go. + +Link: [DIDDoc conventions for inbound routing](https://github.com/hyperledger/aries-rfcs/tree/master/features/0067-didcomm-diddoc-conventions) + +## Relays + +Inbound routing described above covers mediators for the receiver that the sender must know about. In addition, either the sender or the receiver may also have relays they use for outbound messages. Relays are routing agents not known to other parties, but that participate in message routing. For example, an enterprise agent might send all outbound traffic to a single gateway in the organization. When sending to a relay, the sender just wraps the message in another "forward" message envelope. + +Link: [Mediators and Relays](https://github.com/hyperledger/aries-rfcs/tree/master/concepts/0046-mediators-and-relays) + +## Message Encryption + +The DIDcomm encryption handling is handling within the Aries agent, and not really something a developer building applications using an agent needs to worry about. Further, within an Aries agent, the handling of the encryption is left to libraries to handle - ultimately calling dependencies from Hyperledger Ursa. To encrypt a message, the agent code calls a `pack()` function to handle the encryption, and to decrypt a message, the agent code calls a corresponding `unpack()` function. The "wire messages" (as originally called) are described in [detail here](https://github.com/hyperledger/aries-rfcs/blob/master/features/0019-encryption-envelope/README.md), including variations for sender authenticated and anonymous encrypting. Wire messages were meant to indicate the handling of a message from one agent directly to another, versus the higher level concept of routing a message from an edge agent to a peer edge agent. + +Much thought has also gone into repudiable and non-repudiable messaging, as [described here](https://github.com/hyperledger/aries-rfcs/tree/master/concepts/0049-repudiation). diff --git a/docs/GettingStartedAriesDev/YourOwnAriesAgent.md b/docs/GettingStartedAriesDev/YourOwnAriesAgent.md new file mode 100644 index 0000000000..9efb9d52b2 --- /dev/null +++ b/docs/GettingStartedAriesDev/YourOwnAriesAgent.md @@ -0,0 +1,3 @@ +# Starting Your Own Aries Agent + +To be completed. \ No newline at end of file From 1129aef6d4d7daefebea935593b8e7ee68c19342 Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Mon, 1 Jul 2019 00:20:14 +0000 Subject: [PATCH 6/7] Adjustments to the guide, added what a developer should do document Signed-off-by: Stephen Curran --- demo/AriesOpenAPIDemo.md | 4 +- .../AriesDeveloperDemos.md | 2 +- .../IndyAriesDevOptions.md | 50 ++++++++++++++++++- docs/GettingStartedAriesDev/README.md | 2 +- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/demo/AriesOpenAPIDemo.md b/demo/AriesOpenAPIDemo.md index 11468aeedb..9338c8733c 100644 --- a/demo/AriesOpenAPIDemo.md +++ b/demo/AriesOpenAPIDemo.md @@ -80,7 +80,7 @@ When you are done, or to stop the demo so you can restart it, carry out the foll The demo is run entirely in the Browser tabs - DMV ([http://localhost:5000](http://localhost:5000)), Alice ([http://localhost:5001](http://localhost:5001)) and the Indy public ledger ([http://localhost:9000](http://localhost:9000)). The agent terminal windows will only show messages if an error occurs in using the REST API. The Indy public ledger window will display a log of messages from the four nodes of the Indy network. In the instructions that follow, we’ll let you know if you need to be in the DMV, Alice or Indy browser tab. We’ll leave it to you to track which is which. -Using the OpenAPI user interface is pretty simple. In the steps below, we’ll indicate what API endpoint you need use, such as `POST /connections/create-invitation`. That means you must: +Using the OpenAPI user interface is pretty simple. In the steps below, we’ll indicate what API endpoint you need use, such as **`POST /connections/create-invitation`**. That means you must: 1. Scroll to and find that endpoint. 2. Click on the endpoint name to expand its section of the UI. @@ -99,7 +99,7 @@ We’ll start the demo by establishing a connection between Alice’s and the DM In the DMV browser tab, execute the **`POST /connections/create-invitation`**. No data is needed to be added for this call. If successful, you should see a connection ID, an invitation, and the invitation URL. The IDs will be different on each run. -Copy the entire block of the `invitation` object, from the curly brackets {}, excluding the trailing comma. +Copy the entire block of the `invitation` object, from the curly brackets `{}`, excluding the trailing comma. Switch to the Alice browser tab and get ready to execute the **`POST /connections/receive-invitation`** section. Erase the pre-populated text and paste the invitation object from the DMV tab. When you click `Execute` a you should get back a connection ID, an invitation key, and the state of the connection, which should be `requested`. diff --git a/docs/GettingStartedAriesDev/AriesDeveloperDemos.md b/docs/GettingStartedAriesDev/AriesDeveloperDemos.md index 0c16fe77a5..1f72d30330 100644 --- a/docs/GettingStartedAriesDev/AriesDeveloperDemos.md +++ b/docs/GettingStartedAriesDev/AriesDeveloperDemos.md @@ -12,7 +12,7 @@ This demo uses agents (and an Indy ledger), but doesn't implement a controller a Run this demo to see a couple of simple Python controllers implementations for Alice and Faber. Like the previous demo, this shows the agents connecting, Faber issuing a credential to Alice and then requesting a proof based on the credential. Running the demo is simple, but there's a lot for a developer to learn from the code. -[Python-based Alice/Faber Demo](../demo/AliceFaberDemo.md) +[Python-based Alice/Faber Demo](../demo/README.md) ## Web App Sample - Email Verification Service diff --git a/docs/GettingStartedAriesDev/IndyAriesDevOptions.md b/docs/GettingStartedAriesDev/IndyAriesDevOptions.md index 7bc282e803..a992e1f1c6 100644 --- a/docs/GettingStartedAriesDev/IndyAriesDevOptions.md +++ b/docs/GettingStartedAriesDev/IndyAriesDevOptions.md @@ -1,3 +1,49 @@ -# Options for Aries/Indy Developers +# What should I work on? Options for Aries/Indy Developers + +Now that you know the basics of the Indy/Aries eco-system, what do you want to work on? There are many projects at different levels of the eco-system you could choose to work on, and many ways to contribute to the community. + +This is an important summary for newcomers, as often the temptation is to start at a level far below where you plan to focus your attention. Too often people coming into the community start at "the blockchain" - at `indy-node` (the Indy public ledger) or the `indy-sdk`. That is far below where the majority of developers will work and is not really that helpful if you want to build applications. + +We'll go through the layers from top of the stack to the bottom. Our expectation is that the majority of developers will work at the application level, and there will be fewer contributing developers each layer down the stack you go. This is not to dissuade anyone from contributing at the lower levels, but rather to say if you are not going to contribute at the lower levels, you don't need to everything about it. It's much like web development - you don't need to know TCP/IP to build web apps. + +## Building Decentralized Identity Applications + +If you just want to build enterprise applications on top of the decentralized identity-related Hyperledger projects, you can start with building cloud-based controller apps using any language you want, and deploying your code with an instance of the code in this repository ([aries-cloudagent-python](https://github.com/hyperledger/aries-cloudagent-python)). + +If you want to build a mobile agent, there are open source options available, or you can use something like [Streetcred's] [Agent Framework](http://github.com/streetcred-id/agent-framework). + +As a developer building applications that use/embed Aries agents, you should be monitoring the [Aries Working Group](https://wiki.hyperledger.org/display/ARIES/Aries+Working+Group) activities and the [aries-rfcs](https://github.com/hyperledger/aries-rfcs) repo to see what protocols are being added and extended. In some cases, you may need to create your own protocols to be added to this repository, and you should do that in an open way, involving the community. + +Note that if this is what you want to do, you don't need to do a deep dive into the Aries SDK, the Indy SDK or the Indy Node public ledger. + +## Contributing to `aries-cloudagent-python` + +Of course as you build applications using `aries-cloudagent-python`, you will no doubt find deficiencies in the code and things you need added. Contributions to this repo will **always** be welcome. + +## Supporting Additional Ledgers + +`aries-cloudagent-python` currently supports only Hyperledger Indy-based public ledgers and verifiable credentials exchange. A goal of Hyperledger Aries is to be ledger-agnostic, and to support other ledgers. We're experimenting with adding support for other ledgers, and would welcome assistance in doing that. + +## Other Agent Frameworks + +Although controllers for an `aries-cloudagent-python` instance can be written in any language, there is definitely a place for functionality equivalent (and better) to what is in this repo in other languages. Use the example provided by the `aries-cloudagent-python`, evolve that using a different language, and as you discover better ways to do things, discuss and share those improvements in the broader Aries community so that this and other codebases improve. + +## Improving Aries SDK + +This code base and other Aries agent implementations currently embed the `indy-sdk`. However, much of the code in the `indy-sdk` is being migrated into a variety of Aries language specific repositories. How this is migration is to be done is still being decided, but it makes sense that the agent-type things be moved to Aries repositories. A number of [language specific Aries SDK](https://github.com/hyperledger?utf8=%E2%9C%93&q=aries+sdk&type=&language=) repos have been created and are being populated. + +## Improving the Indy SDK + +Dropping down a level from Aries and into Indy, the [indy-sdk](https://github.com/hyperledger/indy-sdk) needs to continue to evolve. The code base is robust, of high quality and well thought out, but it needs to continue to add new capabilities and improve existing features. The `indy-sdk` is implemented in Rust, to produce a C-callable library that can be used by client libraries built in a variety of languages. + +## Improving Indy Node + +If you are interested in getting into the public ledger part of Indy, particularly if you are going to be a Sovrin Steward, you should take a deep look into [indy-node](https://github.com/hyperledger/indy-node). Like the `indy-sdk`, `indy-node` is robust, of high quality and is well thought out. As the network grows, use cases change and new cryptographic primitives move into the mainstream, `indy-node` capabilities will need to evolve. `indy-node` is coded in Python. + +## Working in Cryptography + +Finally, at the deepest level, and core to all of the projects is the cryptography in [Hyperledger Ursa](https://github.com/hyperledger/ursa). If you are a cryptographer, that's where you want to be - and we want you there. + + + -To be completed. \ No newline at end of file diff --git a/docs/GettingStartedAriesDev/README.md b/docs/GettingStartedAriesDev/README.md index 73e03e4aac..2841cfece7 100644 --- a/docs/GettingStartedAriesDev/README.md +++ b/docs/GettingStartedAriesDev/README.md @@ -15,7 +15,7 @@ Note that in the guidance we have here, we include not only the links to look at * To Do: [Issuing an Indy credential: From Issuer to Holder/Prover](IssuingIndyCredentials.md) * To Do: [Presenting an Indy credential: From Holder/Prover to Verifier](PresentingIndyProofs.md) * To Do: [Next steps: Creating your own Aries Agent](YourOwnAriesAgent.md) -* To Do: [What should I be developing?](IndyAriesDevOptions.md) +* [What should I work on? Options for Aries/Indy Developers](IndyAriesDevOptions.md) * [Deeper Dive: DIDcomm Messages](DIDcommMsgs.md) * [Deeper Dive: DIDcomm Message Routing and Encryption](RoutingEncryption.md) * To Do: [Deeper Dive: Running and Connecting to an Indy Network](ConnectIndyNetwork.md) \ No newline at end of file From 60c0313391506e413a251dd660796639fbf83600 Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Mon, 1 Jul 2019 05:25:49 +0000 Subject: [PATCH 7/7] Moved README to DevReadMe and added new top level readme Signed-off-by: Stephen Curran --- DevReadMe.md | 120 +++++++++++++++++++++++++++++++++++++++ README.md | 113 +++++------------------------------- demo/AriesOpenAPIDemo.md | 2 + 3 files changed, 137 insertions(+), 98 deletions(-) create mode 100644 DevReadMe.md diff --git a/DevReadMe.md b/DevReadMe.md new file mode 100644 index 0000000000..523403063c --- /dev/null +++ b/DevReadMe.md @@ -0,0 +1,120 @@ +# Developer's Read Me for Hyperledger Aries Cloud Agent - Python + +See the [README](README.md) for details about this repository and information about how the Aries Cloud Agent - Python fits into the Aries project and relates to Indy. + +## Table of Contents + +- [Introduction](#Introduction) +- [Installing](#Installing) +- [Developer Demos](#Developer-Demos) +- [Running](#Running) +- [Developing](#Developing) + - [Prerequisites](#Prerequisites) + - [Running Locally](#Running-Locally) + - [Running Tests](#Running-Tests) + - [Development Workflow](#Development-Workflow) + - [Dynamic Injection of Services](#Dynamic-Injection-of-Services) + +## Introduction + +Aries Cloud Agent Python (ACA-Py) is a configurable, extensible, non-mobile Aries agent that implements an easy way for developers to build decentralized identity applications that use verifiable credentials. + +The information on this page assumes you are developer with a background in decentralized identity, Indy, Aries and verifiable credentials. If you aren't familiar with those concepts and projects, please use our [Getting Started Guide](docs/gettingStartedAriesDev/README.md) to learn more. + +## Installing + +Instructions forthcoming. `aries_cloudagent` will be made available in the future as a python package at [pypi.org](https://pypi.org). + +## Developer Demos + +To put ACA-Py through its paces at the command line, checkout our [demos](docs/gettingStartedAriesDev/AriesDeveloperDemos.md) page. + +## Running + +After installing the PyPi package, the executable `acagent` should be available in your PATH. + +Find out more about the available command line parameters by running: + +```bash +acagent --help +``` + +Currently you must specify at least one _inbound_ and one _outbound_ transport. + +For example: + +```bash +acagent --inbound-transport http 0.0.0.0 8000 \ + --inbound-transport http 0.0.0.0 8001 \ + --inbound-transport ws 0.0.0.0 8002 \ + --outbound-transport ws \ + --outbound-transport http +``` + +Currently, Aries Cloud Agent Python ships with both inbound and outbound transport drivers for `http` and `websockets`. More information on how to develop your own drivers will be coming soon. + +## Developing + +### Prerequisites + +[Docker](https://www.docker.com) must be installed to run software locally and to run the test suite. + +### Running Locally + +To run the locally, we recommend using the provided Docker images to run the software. + +```bash +./scripts/run_docker +``` + +```bash +./scripts/run_docker --inbound-transport http 0.0.0.0 10000 --outbound-transport http --debug --log-level DEBUG +``` + +To enable the [ptvsd](https://github.com/Microsoft/ptvsd) Python debugger for Visual Studio/VSCode use the `debug` flag + +Publish any ports you will be using from the docker container using the PORTS environment variable. For example: + +```bash +PORTS="5000:5000 8000:8000 1000:1000" ./scripts/run_docker --inbound-transport http 0.0.0.0 10000 --outbound-transport http --debug --log-level DEBUG +``` + +Refer to [the previous section](#Running) for instructions on how to run the software. + +### Running Tests + +To run the test suite, use the following script: + +```bash +./scripts/run_tests +``` + +To run the tests including [Indy SDK](https://github.com/hyperledger/indy-sdk) and related dependencies, run the script: + +```bash +./scripts/run_tests_indy +``` + +### Development Workflow + +We use [Flake8](http://flake8.pycqa.org/en/latest/) to enforce a coding style guide. + +We use [Black](https://black.readthedocs.io/en/stable/) to automatically format code. + +Please write tests for the work that you submit. + +Tests should reside in a directory named `tests` alongside the code under test. Generally, there is one test file for each file module under test. Test files _must_ have a name starting with `test_` to be automatically picked up the test runner. + +There are some good examples of various test scenarios for you to work from including mocking external imports and working with async code so take a look around! + +The test suite also displays the current code coverage after each run so you can see how much of your work is covered by tests. Use your best judgement for how much coverage is sufficient. + +Please also refer to the [contributing guidelines](/CONTRIBUTING.md) and [code of conduct](/CODE_OF_CONDUCT.md). + +### Dynamic Injection of Services + +The Agent employs a dynamic injection system whereby providers of base classes are registered with the `RequestContext` instance, currently within `conductor.py`. Message handlers and services request an instance of the selected implementation using `await context.inject(BaseClass)`; for instance the wallet instance may be injected using `wallet = await context.inject(BaseWallet)`. The `inject` method normally throws an exception if no implementation of the base class is provided, but can be called with `required=False` for optional dependencies (in which case a value of `None` may be returned). + +Providers are registered with either `context.injector.bind_instance(BaseClass, instance)` for previously-constructed (singleton) object instances, or `context.injector.bind_provider(BaseClass, provider)` for dynamic providers. In some cases it may be desirable to write a custom provider which switches implementations based on configuration settings, such as the wallet provider. + +The `BaseProvider` classes in the `config.provider` module include `ClassProvider`, which can perform dynamic module inclusion when given the combined module and class name as a string (for instance `aries_cloudagent.wallet.indy.IndyWallet`). `ClassProvider` accepts additional positional and keyword arguments to be passed into the class constructor. Any of these arguments may be an instance of `ClassProvider.Inject(BaseClass)`, allowing dynamic injection of dependencies when the class instance is instantiated. diff --git a/README.md b/README.md index 09d59166e1..8ec5712201 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,34 @@ -# Hyperledger Aries Python Cloud Agent +# Hyperledger Aries Cloud Agent - Python [![CircleCI](https://circleci.com/gh/bcgov/aries-cloudagent-python.svg?style=shield)](https://circleci.com/gh/bcgov/aries-cloudagent-python) [![codecov](https://codecov.io/gh/bcgov/aries-cloudagent-python/branch/master/graph/badge.svg)](https://codecov.io/gh/bcgov/aries-cloudagent-python) [![Known Vulnerabilities](https://snyk.io/test/github/bcgov/aries-cloudagent-python/badge.svg)](https://snyk.io/test/github/bcgov/aries-cloudagent-python?targetFile=requirements.txt) -![logo](/docs/assets/aries-cloudagent-python-logo-bw.png) + -# Table of Contents +## Table of Contents - [Introduction](#Introduction) -- [Installing](#Installing) -- [Running](#Running) -- [Developing](#Developing) - - [Prerequisites](#Prerequisites) - - [Running Locally](#Running_Locally) - - [Caveats](#Caveats) - - [Running Tests](#Running_Tests) - - [Development Workflow](#Development_Workflow) +- [Resources](#Resources) -# Introduction +## Introduction -Aries Python Cloud Agent is a configurable instance of a "Cloud Agent". +Hyperledger Aries Cloud Agent Python (ACA-Py) is a foundation for building decentralized identity applications and services running in non-mobile environments using DIDcomm messaging, the did:peer method, and verifiable credentials. With ACA-Py, Hyperledger Indy and Aries developers can focus on building applications using familiar web development technologies instead of trying to learn the nuts and bolts of low-level SDKs. -# Installing +The ACA-Py development model is pretty straight forward for those familiar with web development. An ACA-Py instance is always deployed with a paired "controller" application that provides the business logic for that Aries agent. The controller receives webhook event notifications from its instance of ACA-Py and uses an HTTP API exposed by the ACA-Py instance to provide direction on how to respond to those events. The source of the business logic is left to your imagination. An interface to a legacy system? A user interface for a person? Custom code to implement a new service? You can build your controller in any language that supports making and receiving HTTP requests. Wait...that's every language! -Instructions forthcoming. `aries_cloudagent` will be made available in the future as a python package at [pypi.org](https://pypi.org). +ACA-Py currently supports "only" Hyperledger Indy's verifiable credentials scheme (which is pretty powerful). We are experimenting with adding support to ACA-Py for other DID Ledgers and verifiable credential schemes. -# Running +As we create ACA-Py, we're building resources so that developers with a wide-range of backgrounds can get productive with ACA-Py in a hurry. Scan the resources below and jump in. -After installing the package, `acagent` should be available in your PATH. +## Resources -Find out more about the available command line parameters by running: +If you are experienced decentralized identity developer that knows Indy, is already familiar with the concepts behind Aries, and want to play with the code and perhaps start contributing, a traditional "install and go" page for developers can be found [here](DevReadMe.md). -```bash -acagent --help -``` +For everyone else, we've created a [Getting Started Guide](docs/gettingStartedAriesDev/README.md) that will take you from knowing next to nothing about decentralized identity to developing Aries-based business apps and services in a hurry. Along the way, you'll run some early Indy apps, apps built on ACA-Py and developer-oriented demos for interacting with ACA-Py. The guide has a good table of contents so that you can skip the parts you already know. -Currently you must specify at least one _inbound_ and one _outbound_ transport. +We'll soon have a ReadTheDocs site published with docstrings extracted from the ACA-Py code. -For example: +Not sure where your focus should be? Building apps? Aries? Indy? Indy's Blockchain? Ursa? Here is a [document](docs/GettingStartedAriesDev/IndyAriesDevOptions.md) that goes through the technical stack to show how it the projects fit together, so you can decide where you want to focus your efforts. -```bash -acagent --inbound-transport http 0.0.0.0 8000 \ - --inbound-transport http 0.0.0.0 8001 \ - --inbound-transport ws 0.0.0.0 8002 \ - --outbound-transport ws \ - --outbound-transport http -``` - -Currently, Aries Python Cloud Agent ships with both inbound and outbound transport drivers for `http` and `websockets`. More information on how to develop your own drivers will be coming soon. - -# Developing - -## Prerequisites - -[Docker](https://www.docker.com) must be installed to run software locally and to run the test suite. - -## Running Locally - -To run the locally, we recommend using the provided Docker images to run the software. - -``` -./scripts/run_docker -``` - -``` -./scripts/run_docker --inbound-transport http 0.0.0.0 10000 --outbound-transport http --debug --log-level DEBUG -``` - -To enable the [ptvsd](https://github.com/Microsoft/ptvsd) Python debugger for Visual Studio/VSCode use the `debug` flag - -For any ports you will be using, you can publish these ports from the docker container using the PORTS environment variable. For example: - -``` -PORTS="5000:5000 8000:8000 1000:1000" ./scripts/run_docker --inbound-transport http 0.0.0.0 10000 --outbound-transport http --debug --log-level DEBUG -``` - -Refer to [the previous section](#Running) for instructions on how to run the software. - -## Running Tests - -To run the test suite, use the following script: - -```sh -./scripts/run_tests -``` - -To run the test including [Indy SDK](https://github.com/hyperledger/indy-sdk) and related dependencies, run the script: - -```sh -./scripts/run_tests_indy -``` - -## Development Workflow - -We use [Flake8](http://flake8.pycqa.org/en/latest/) to enforce a coding style guide. - -We use [Black](https://black.readthedocs.io/en/stable/) to automatically format code. - -Please write tests for the work that you submit. - -Tests should reside in a directory named `tests` alongside the code under test. Generally, there is one test file for each file module under test. Test files _must_ have a name starting with `test_` to be automatically picked up the test runner. - -There are some good examples of various test scenarios for you to work from including mocking external imports and working with async code so take a look around! - -The test suite also displays the current code coverage after each run so you can see how much of your work is covered by tests. Use your best judgement for how much coverage is sufficient. - -Please also refer to the [contributing guidelines](/CONTRIBUTING.md) and [code of conduct](/CODE_OF_CONDUCT.md). - -## Dynamic Injection of Services - -The Agent employs a dynamic injection system whereby providers of base classes are registered with the `RequestContext` instance, currently within `conductor.py`. Message handlers and services request an instance of the selected implementation using `await context.inject(BaseClass)`; for instance the wallet instance may be injected using `wallet = await context.inject(BaseWallet)`. The `inject` method normally throws an exception if no implementation of the base class is provided, but can be called with `required=False` for optional dependencies (in which case a value of `None` may be returned). - -Providers are registered with either `context.injector.bind_instance(BaseClass, instance)` for previously-constructed (singleton) object instances, or `context.injector.bind_provider(BaseClass, provider)` for dynamic providers. In some cases it may be desirable to write a custom provider which switches implementations based on configuration settings, such as the wallet provider. - -The `BaseProvider` classes in the `config.provider` module include `ClassProvider`, which can perform dynamic module inclusion when given the combined module and class name as a string (for instance `aries_cloudagent.wallet.indy.IndyWallet`). `ClassProvider` accepts additional positional and keyword arguments to be passed into the class constructor. Any of these arguments may be an instance of `ClassProvider.Inject(BaseClass)`, allowing dynamic injection of dependencies when the class instance is instantiated. +The initial implementation of ACA-Py was developed by the Verifiable Organizations Network (VON) team based at the Province of British Columbia. To learn more about VON and what's happening with decentralized identity in British Columbia, please go to [https://vonx.io](https://vonx.io). diff --git a/demo/AriesOpenAPIDemo.md b/demo/AriesOpenAPIDemo.md index 9338c8733c..e452dff9ac 100644 --- a/demo/AriesOpenAPIDemo.md +++ b/demo/AriesOpenAPIDemo.md @@ -8,6 +8,8 @@ Source doc: https://docs.google.com/a/cloudcompass.ca/open?id=12N1KDm1l4Az6bSOJ3 This demo is for developers comfortable with playing around with APIs using the OpenAPI (Swagger) user interface and JSON. The controller for each of the two agent instances in the demo is you. You drive the API exposed by the agent instances to respond to events received by each agent. The demo covers two agents, Alice and one representing the Government Driver’s Licence program. The two agents connect, and then the Government’s Department of Motor Vehicles (DMV) agent issues a Driver Licence credential to Alice, and then asks Alice to prove she possesses the credential. Who knows why the DMV Agent needs to get the proof, but it lets us show off more protocols. +# Table of Contents + - [Prerequisites](#Prerequisites) - [Starting Up](#Starting-Up) - [Start the VON Network](#Start-the-VON-Network)