diff options
author | Christian Grothoff <christian@grothoff.org> | 2015-01-08 18:37:20 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2015-01-08 18:37:20 +0100 |
commit | 57d1f08dbca256f5fe16d57b29bfa523dec8f6c4 (patch) | |
tree | 3b3ee5f3b8c174887217e5c465048dea4e79bae2 |
-initial import for mint
90 files changed, 19997 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6fe514889 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +*~ +*Makefile.in +*Makefile +aclocal.m4 +autom4te.cache +autoscan.log +compile +configure +depcomp +missing +taler_config.h.in +install-sh +config.log +config.status +stamp-h1 +taler_config.h +config.guess +config.sub +libtool +ltmain.sh +test-driver +m4/ +GPATH +GRTAGS +GTAGS +*.swp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..7625aa983 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "doc/api"] + path = doc/api + url = git@git.taler.net:api diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..75e08df1c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Sree Harsha Totakura <sreeharsha@totakura.in> +Florian Dold +Christian Grothoff <christian@grothoff.org> +Benedikt Mueller diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..dba13ed2d --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ChangeLog diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..209984075 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell command `./configure && make && make install' +should configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `<wchar.h>' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..ea5a667ef --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include +SUBDIRS = src doc +ACLOCAL_AMFLAGS = -I m4 diff --git a/bootstrap b/bootstrap new file mode 100755 index 000000000..4e7cc0537 --- /dev/null +++ b/bootstrap @@ -0,0 +1,2 @@ +#!/bin/sh +autoreconf -if diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000..789df8e46 --- /dev/null +++ b/configure.ac @@ -0,0 +1,156 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.69]) +AC_INIT([taler-mint], [0.0.0], [taler-bug@gnunet.org]) +AC_CONFIG_SRCDIR([src/util/json.c]) +AC_CONFIG_HEADERS([taler_config.h]) +# support for non-recursive builds +AM_INIT_AUTOMAKE([subdir-objects]) + +# pretty build rules +AM_SILENT_RULES([yes]) + +AC_CONFIG_MACRO_DIR([m4]) + +LT_INIT + +# Checks for programs. +AC_PROG_CC + +CFLAGS="-Wall $CFLAGS" + +# Checks for header files. +AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h]) + +# Check for GNUnet's libgnunetutil. +libgnunetutil=0 +AC_MSG_CHECKING([for libgnunetutil]) +AC_ARG_WITH(gnunet, + [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])], + [AC_MSG_RESULT([given as $with_gnunet])], + [AC_MSG_RESULT(not given) + with_gnunet=yes]) +AS_CASE([$with_gnunet], + [yes], [], + [no], [AC_MSG_ERROR([--with-gnunet is required])], + [LDFLAGS="-L$with_gnunet/lib $LDFLAGS" + CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"]) +AC_CHECK_HEADERS([gnunet/platform.h gnunet/gnunet_util_lib.h], + [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)], + [], [#ifdef HAVE_GNUNET_PLATFORM_H + #include <gnunet/platform.h> + #endif]) +AS_IF([test $libgnunetutil != 1], + [AC_MSG_ERROR([[ +*** +*** You need libgnunetutil to build this program. +*** This library is part of GNUnet, available at +*** https://gnunet.org +*** ]])]) + + +# check for libmicrohttpd +microhttpd=0 +AC_MSG_CHECKING([for microhttpd]) +AC_ARG_WITH([microhttpd], + [AS_HELP_STRING([--with-microhttpd=PFX], [base of microhttpd installation])], + [AC_MSG_RESULT([given as $with_microhttpd])], + [AC_MSG_RESULT([not given]) + with_microhttpd=yes]) +AS_CASE([$with_microhttpd], + [yes], [], + [no], [AC_MSG_ERROR([--with-microhttpd is required])], + [LDFLAGS="-L$with_microhttpd/lib $LDFLAGS" + CPPFLAGS="-I$with_microhttpd/include $CPPFLAGS"]) +AC_CHECK_LIB(microhttpd,MHD_start_daemon, + [AC_CHECK_HEADER([microhttpd.h],[microhttpd=1])]) +AS_IF([test $microhttpd = 0], + [AC_MSG_ERROR([[ +*** +*** You need libmicrohttpd to build this program. +*** ]])]) + + +# check for libpq (postgresql) +AX_LIB_POSTGRESQL([9.3]) +AS_IF([test ! "$found_postgresql" = "yes"], + [AC_MSG_ERROR([[ +*** +*** You need postgresql / libpq to build this program. +*** ]])]) + + +# check for libjansson (Jansson JSON library) +jansson=0 +AC_MSG_CHECKING([for jansson]) +AC_ARG_WITH([jansson], + [AS_HELP_STRING([--with-jansson=PFX], [base of jansson installation])], + [AC_MSG_RESULT([given as $with_jansson])], + [AC_MSG_RESULT([not given]) + with_jansson=yes]) +AS_CASE([$with_jansson], + [yes], [], + [no], [AC_MSG_ERROR([--with-jansson is required])], + [LDFLAGS="-L$with_jansson/lib $LDFLAGS" + CPPFLAGS="-I$with_jansson/include $CPPFLAGS"]) +AC_CHECK_LIB(jansson,json_pack, + [AC_CHECK_HEADER([jansson.h],[jansson=1])]) +AS_IF([test $jansson = 0], + [AC_MSG_ERROR([[ +*** +*** You need libjansson to build this program. +*** ]])]) + +# check for libgnurl +LIBGNURL_CHECK_CONFIG([], [7.34.0], [gnurl=1], [gnurl=0]) +if test "$gnurl" = 1 +then + AM_CONDITIONAL(HAVE_LIBGNURL, true) + AC_DEFINE([HAVE_LIBGNURL],[1],[Have libgnurl]) +else + AM_CONDITIONAL(HAVE_LIBGNURL, false) +fi +AS_IF([test $gnurl = 0], + [AC_MSG_ERROR([[ +*** +*** You need libgnurl to build this program. +*** ]])]) + +# Require minimum libgcrypt version +need_libgcrypt_version=1.6.1 +AC_DEFINE_UNQUOTED([NEED_LIBGCRYPT_VERSION], ["$need_libgcrypt_version"], + [minimum version of libgcrypt required]) +AM_PATH_LIBGCRYPT([$need_libgcrypt_version]) + +# logging +extra_logging=0 +AC_ARG_ENABLE([logging], + AS_HELP_STRING([--enable-logging@<:@=value@:>@],[Enable logging calls. Possible values: yes,no,verbose ('yes' is the default)]), + [AS_IF([test "x$enableval" = "xyes"], [], + [test "x$enableval" = "xno"], [AC_DEFINE([GNUNET_CULL_LOGGING],[],[Define to cull all logging calls])], + [test "x$enableval" = "xverbose"], [extra_logging=1] + [test "x$enableval" = "xveryverbose"], [extra_logging=2]) + ], []) +AC_DEFINE_UNQUOTED([GNUNET_EXTRA_LOGGING],[$extra_logging],[1 if extra logging is enabled, 2 for very verbose extra logging, 0 otherwise]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_INTMAX_T +AC_TYPE_UINTMAX_T + +# Checks for library functions. +AC_CHECK_FUNCS([strdup]) + +AC_CONFIG_FILES([Makefile + doc/Makefile + src/Makefile + src/include/Makefile + src/util/Makefile + src/mint/Makefile + ]) +AC_OUTPUT diff --git a/contrib/mint-template/README b/contrib/mint-template/README new file mode 100644 index 000000000..fce5e0180 --- /dev/null +++ b/contrib/mint-template/README @@ -0,0 +1 @@ +This directory is a template for the mint directory. diff --git a/contrib/mint-template/config/mint-common.conf b/contrib/mint-template/config/mint-common.conf new file mode 100644 index 000000000..becf42435 --- /dev/null +++ b/contrib/mint-template/config/mint-common.conf @@ -0,0 +1,6 @@ +[mint] +db = postgres:///taler +port = 4241 +master_pub = ... +refresh_security_parameter = 3 + diff --git a/contrib/mint-template/config/mint-keyup.conf b/contrib/mint-template/config/mint-keyup.conf new file mode 100644 index 000000000..1542d1a63 --- /dev/null +++ b/contrib/mint-template/config/mint-keyup.conf @@ -0,0 +1,79 @@ +[mint_keys] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long do we generate denomination and signing keys +# ahead of time? +lookahead_sign = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# what coin types do we have available? +coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000 + + + +[mint_denom_duration_overlap] +default_eur_ct_10 = 5 minutes +default_eur_5 = 5 minutes +default_eur_10 = 5 minutes +default_eur_1000 = 5 minutes + + + +[mint_denom_value] +default_eur_ct_10 = EUR:0.10 +default_eur_5 = EUR:5 +default_eur_10 = EUR:10 +default_eur_1000 = EUR:1000 + + + +[mint_denom_duration_withdraw] +default_eur_ct_10 = 7 days +default_eur_5 = 7 days +default_eur_10 = 7 days +default_eur_1000 = 1 day + + + +[mint_denom_duration_spend] +default_eur_ct_10 = 30 days +default_eur_5 = 30 days +default_eur_10 = 30 days +default_eur_1000 = 30 day + + + +[mint_denom_fee_withdraw] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + +[mint_denom_fee_deposit] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_fee_refresh] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_kappa] +default_eur_ct_10 = 3 +default_eur_5 = 3 +default_eur_10 = 3 +default_eur_1000 = 5 + diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 000000000..8fd4c0911 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,12 @@ +*.aux +*.dvi +*.log +*.pdf +*.out +*.snm +*.toc +*.vrb +*.nav +*/auto +api-sphinx/_build +api-sphinx/*.html diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 000000000..4af5c665c --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,5 @@ +EXTRA_DIST = \ + paper/taler.tex + paper/ref.bib \ + paper/Makefile + diff --git a/doc/logos/ai/logotalerv2.ai b/doc/logos/ai/logotalerv2.ai new file mode 100644 index 000000000..d474079d3 --- /dev/null +++ b/doc/logos/ai/logotalerv2.ai @@ -0,0 +1,3474 @@ +%PDF-1.5
%
+1 0 obj
<</Metadata 2 0 R/OCProperties<</D<</ON[5 0 R 248 0 R 489 0 R 730 0 R]/Order 731 0 R/RBGroups[]>>/OCGs[5 0 R 248 0 R 489 0 R 730 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<</Length 50398/Subtype/XML/Type/Metadata>>stream
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> +<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 "> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description rdf:about="" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <dc:format>application/pdf</dc:format> + <dc:title> + <rdf:Alt> + <rdf:li xml:lang="x-default">logotalerv2</rdf:li> + </rdf:Alt> + </dc:title> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:xmp="http://ns.adobe.com/xap/1.0/" + xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"> + <xmp:CreatorTool>Adobe Illustrator CS6 (Macintosh)</xmp:CreatorTool> + <xmp:CreateDate>2014-09-16T12:36:31+02:00</xmp:CreateDate> + <xmp:ModifyDate>2014-09-18T11:52:58+02:00</xmp:ModifyDate> + <xmp:MetadataDate>2014-09-18T11:52:58+02:00</xmp:MetadataDate> + <xmp:Thumbnails> + <rdf:Alt> + <rdf:li rdf:parseType="Resource"> + <xmpGImg:width>256</xmpGImg:width> + <xmpGImg:height>192</xmpGImg:height> + <xmpGImg:format>JPEG</xmpGImg:format> + <xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAwAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqkHn
Lz35W8naZ+kdfvktYmqIIftTTMP2Yox8THffsO5AxV4lqP55fmf5v5jyLpUWi6QWMUer6gY2kZqh
fh9T9yp5OoYBX41BJAxViV/5Y826oyt5w87zrLKob6pPctDGrcI2YKJGSIhGnUHgvVZBtxrgVJrn
yn5NW48x2ei3VvNBc2trHpV5qFeSTGaJ7hlkeNaHgsg5UGx232CrI28syWOl2d95U8z3OixW0U1x
eXsdxcMsscdsvFOCSUaQTW8hZBGPhkBPZcKo22/Pf81vImox6Z5yt7fW4CCSwaNLkKkjRN+9gqnJ
WQ1WROXiRir3TyD+ank7zxal9Gu6XkahrjTpwI7mP5pU8l/ylJGKsuxV2KuxV2KuxV2KuxV2KuxV
2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsG/Nb80tO8iaRGwj+va9fkxaRpaVLSyHbmwX4vT
UkVpuTsPZV4GnlTUNVv280/mTPLqGrXgKWNjs9vzV2UWy/Vnd+akf3SJxUtVyxDpir1Hy9+WWp30
cE2pXcuk2jxktp9okdvM3xzKpd46rCXhmpJHDRflsALVnej+QfJukqPqOk2yuNzO6CWUnxMj8nr9
OBKerFEiBFRVQbBQABiqUar5L8p6sjJqGk2txyFC5iUP9DqA4+g4q8y8/wD/ADj/ABan6l/ot3LN
d8afVL2QyVVRskczfEPYPX5jDavnnUNH8w+Vdb+s2rT6Zq1hJUMKxyxN/Qg/Ij2xQ+nPyQ/O2Dzr
anSNY4W/ma1QFwKKlyg29WMdiP21+kbdCr1rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUv1zzDoWg
2Rvda1CDTrUbercyLGCfBeR+I+w3xV5Tr3/OVX5c2EjRaZDe6y42EsEQhh2/ypyj/cmKsXk/5zCI
ciLygzJ2ZtQCn7hbN+vFUXp//OX2ku6/pPyzc20f7TW1zHcEfIOlvX78VeieVPz2/LLzLIlva6st
leyEBbO/H1ZyT0Cs37pifBXJxVn4IIqOmKuxV2KuxVB6zq9ho2k3mrahJ6VlYwvcXEngkaljQdzt
sO5xV8iwebtY8yeYtS82eYtHt7zTdYSQaUblDJ9UW0bjAsRhKXCJzkEbvGU5SGvKoIwK96/LnyC9
rOfNGuxV128QejAzNILSGlFjDPVjIV/vHO5NfE1BKUg/5yM8t2d1odnrJjv7q/tri2t4LW0SeWP0
DcpLdMyQq1CYUarNtQUG+IV2p3+teTvyj05vKz3c11LJDbQSSQPdyxIUdhyQglalAnJkNK0pWmKq
HmHzHrHmLyL5R1WeyuYdRGs2T3KRwTKyD05BI7KAWSOp/b26VxVX/MLV/wAx7Tzl5XOjTXcOhNDE
0v1eD14pbgzFbiO7+AqqegV4s0qcdyvJgFxVOTq3nqf8z/qkd3c2/l+Kzs5fqz2iC1mnlE4uFS5a
HmxSkLfDLsdqeCqZfmb+W9l5v0kvGqxa5bIfqdz0D9zDJ4q3Y/snfxBVfId4mr+Wddi1KyL2Wpaf
PUA/C0csbUZHHzBVh9GFD7S/LTzzaedfKdprUAEcrjhdQVqY5V2dT9PT2wqyrFXYq7FXYq7FXYq7
FXYq7FXYq7FXin5w/wDORVj5Xnm0Dywseo+YEql1cNVre0b+U0/vJR/KDRe+9VxV87vZ+cfO2r/X
tWubjU76c0V5KudzXjGg+FV8FUAe2BXp/lr/AJxs1y5jSW+WOzQ0NJ2q9P8AUQNT6aY2rMoP+ca9
LVBz1MBu4W3qPvMgxtUJqf8AzjTCyH6nfQyt2EsbRfipkxtXmPm/8kNf0ZWkltWWEdJk+OL/AIJa
0+mmKrPIv5vefPy8uI7OZm1PQVID6bcsTwUf8s8hqYj7br7d8VfV/knzz5d856KmraHcerCTxnha
izQyUqY5UqeLD7j1BIwqn+KuxV5D/wA5Da2fqWg+U4b02V3r94GSQQrcKfqjI0aSozIoR53jqzVF
AdiK4qlfljyvb3HnOy0WNIRp+jKdTvzayh4Jpmcrb0jiWKCMScfW4JGo6dTVmBV6j5xn1C38pa1P
pt3Dp+oRWNw9pf3JVYLeVYmKTSlw6hI2+JiykUG4wJfJDfmr+eXJkk/MLRpFBoSs+lUPyrBhQqR/
mr+cITg/n3S+P8q3Ojgfcbc4qxq5/wCcg/zohuJ7eLzOJIonaPnHa6e6MVNCVZYOLLtsw6jGlT2y
/OT83b60SVfPNjGDQqlzJpULU7Vje2qp9q40q9/zW/O4kFPP+j1HQtPpJp/yQxV7Z+QPmf8AMbWL
W4bzXqVtrMTuz2moWrQMhjACFAbdY0PF1bt9OApYt/zkh5PitdYt9bt0CwauhjuaDYXMIFG8P3kf
/EScIQlP/OLfmyXTfNN75amaltqA9eFSdhLH8LU/1gR92FX1ZirsVdirsVdirsVdirsVdirsVeKf
85FfnDN5XsF8saBP6fmHUY+VxcofitLZqiqntLJSi9wPi68Tirwz8svy01LzRqaRxoSCec0z14ot
d3c/51wK+tPKPkbQfK9msNhCGueNJrxwPUc96fyr/kjAlkOKuxV2KrXjSRGSRQ6MKMrCoIPYg4q8
j/M/8mLDULSbUNFgCyqC01ko2I7mL3/yfu8MNq8D8seZvMH5Z+bI9VsKyWzER31kxIjuIa7o3+UO
qN2PtUFQ+0vLvmDS/MOh2Wt6XL61hfRiWB+hodirDsysCrDsRhVMcVfOH51W0uqfnbo6TQxT6ZYW
AhZJ51hj+sSpcTDkxDsmwQ1VSRSo3xVn/wCTmnRxL5hvPTWKZtQ+pFEkkmASyhSJR6kxMjUNd23y
JS9BvLO1vbSezu4UuLS5jaG4t5VDxyRyAq6OrAhlZTQg4q82uf8AnHL8rJ53l/REMfMk8I4o1UV7
ABaAY2qn/wBC2flZ/wBWuP8A4CP/AJpxtXyZ5g8tW9p5m8w2donG1s9UvraBadI4bmSNRt4Bckh7
f+Rn5KeQ/Mnke01LVbFJrqRFLuVQmpHuDgJS9E/6Fs/Kz/q1x/8AAR/804LVlPk78t/LXlEt+hoj
CjKV9IEBACeRooAA3xVJPz809Lr8tr2cir6fNb3Mfz9UQsf+AmbEK+YfId6+mfmdo1yhp/pQU08H
BX+OSQ+60bkgbxFcVbxV2KuxV2KuxV2KuxV2KoHXdZstE0W+1i+bhaafBJcznvxiUsQK9zSgHjir
4WW61Xzt5yu9YvgZLzUrgysgq3EMaRxL34otFX2GBX2T5A8n2vlfy/BZIi/W5AHvZR1aSn2a+C9B
9/fAljmqf85E/k7pWqXmlX+vmK/0+eS1u4RZ3z8JoXMci8kgZTRlIqCRjSqSf85J/ku/2Nfkb5af
qJ/7F8aVbL/zkv8AkpC3GXzC8bHcB7DUVNPpt8aVdH/zkn+TEq84tekkUdWXT9RI+8W+NKtb/nJb
8lU+15gdfnYaiP8AsXxpU88mfnF+XHnTVZdK8s6v9fv4YGupYfq11DSFHSNm5TxRr9qRRStd8Vea
/n5+XsCE6vaRhYLsn1VA2Saldv8AW6/fhCEu/wCcVvOk1pq2oeSL2T9zOGvNMVj9mWPaeNf9dKPT
/JPjhV9L4q+WP+ciPNHmLQPzHnstMuWs49RtrO8e4iLLKREJYAlQ3EpUE/ZrXvir1H/nHy5a58j3
E8knq3EmoTvO9KEu6RuTtQb8siUvTcVdirsVfIFzoBvNb8z3AWtdc1YVp4X0wySHs3/ONIp+W9kP
BV/VkSl6zirsVYH+eU0cX5W64XNOa26KPFmuYgMQr5M0NTJ570lV3P1qI0Hsa4UPvS3/ALiP/VH6
sKqmKuxV2KuxV2KuxV2KuxV4/wD85Ta5Jp/5YGxiYq+sXsFo9OvppyuG3+cIB+eKvK/+cb/Lkd95
nhuZUqlmrXRB8Uoqfc7A4Cr6rwJeJ+YPy0/Me817Uruz8q/lvcWtxdTy29xqFhdPeSRvIzI9y6xl
WmZTWQjq1cVQI/K381R08o/lcP8At3Xn/VPFXhf54+WvMemedbSy1zT9B026OmxTpbeWYZbezKNP
Mgd0lCsZiUIY/wAoXwwhDLvyK8l+edY8q38+iaJ5N1O0g1GSBp/M1rcXF4riCFzHG0SsohpIGA/m
LHviVein8rPzUPXyh+Vp/wC3def9U8CWcflb5J17QrrULvzBoPlLS7uRI4rG48rWklvI0ZLNOlw8
qKxUssZUL4GvbFWT+dtJTVfK2o2rLyb0mli8ecY5inzpTFXyFpF+3ln80NE1VTwS3voTMen7p39O
YfTG7DCh9xYVfNX/ADl55fdLry95jjUmMiXT7l+wIPqwj6f3n3Yqjv8AnGDzJGy6hokjgNMi3Vup
/mj+CQD3IKn6MBV79gS7FXYq+bNI1Cyhl80RSgeodd1ihPvfTZJDO/8AnGw1/Lm0I6UXIlL1jFXY
q8k/P/UUurfQvKUcnGXVLsXN2RU+naWwPN3ChiFDMGrQ/ZOxxCvKtHn0rzd+dOnNokFv+jrMoxub
aORPUbioPISpEwChOKjj798kh9cgUAHhirsVdirsVdirsVdirsVdir54/wCcwZWGl+V4duD3Ny58
aokYH/EzgVd/zi/Egj1CTbkIIwPGhap/ViVe+YEuxV2Kvl3/AJyP003v5s2wArw0K0/G8vP6YQhn
n/OLVsbbybr8J2467L+NjZnAUvZsVdirTqrqVbdWBBHscVfDP5jLw1Esp4sCSCNiCMKH3hhViP5r
eSo/OXkbUtFoPrTJ61g5/ZuIvij37An4T7E4q+O/InmTU/KvmaGfi0N9p85WWB6qQyEpJE/hUVU4
Ffa/lzzBp3mDRrbVtPfnb3C1p+0jD7SNToynY4EplirsVfFesa41r5l80wBqcdb1Xb53spySHvX/
ADjQa/lrZHxRf1ZEpetYqhdT1Ow0vT7jUdQmW3srVGlnmfYKqip/sHfFXzZf+bbrULrXPzIvY2ij
9P6tolsJza3cVpG4XnCxVl+PmVdlaodiFr9lihkv/OM3lK9kN95t1RSbu9laQOVCVZ6liFUBQKno
NsKvoHFXYq7FXYq7FXYq7FXYq7FXhv8Azlvpkk/kbS9RQEix1FVlA6BJ4nXkf9mqj6cVY3/zjBqs
a6jcWTGhnt24DxaNgaf8DywFX0bgS7FXYq8L/MzTEv8A84nRv2fL9kRX/mMvcIQyb8h7VbXTfNMC
9E11x/3LrLAUvTsVdiqF1W8Wy0y7u2NBbwvJX/VUnFXxVrlodc856dpMY5NfXcNsAP8Ai6VU7fPC
h90YVdir5s/5yN/J6dbp/Ovl6DkW31e1jG5Kj+/UeNPtAfPFWB/lT+beo+Vb4UJuLCagu7Jmor9g
6nfi48foOBX1V5V85+XvNFkLrSLpZSADNbNRZoiezx1qPn0PY4Ep5ir89vPmoGHz95ujrSmtal+N
3JkkPq7/AJxl/wDJZ2H/ABjT9WRKXo3mLzPoPlywN9rN4lpBuEDGryN/JHGKu7eyjFXhfm384dSu
9WjvdS0s23lGGUxR2zshuw6ShTePEGYMEZeHFkKbleXMgg0hiei2Oqfmhr1tpljYwWXlzTJBxaKN
k9VY5JPTkb1GdwAszcU5bVpvQYVfV+haLZaLpcGn2aBIYVoABSp7nFUfirsVdirsVdirsVdirsVd
irGfzL8qDzZ5F1nQQB693bk2hbYC4iIkhJPYeoi19sVfIH5VeZ7ny55ntpnVkltpaSxNs23wSIQe
hpUYFfa1nd295aQ3ds4kt50WSJx0KsKg4Eq2KuxV8+/nDqx0783uYbiX0CyH3Xl7hCGW/wDOPV39
b0PzNcVrz119/lp9kMBS9VxV2KvOvzq81Q6V5cOno4Fze7uK7iJTX/hmFPvxCvGP+cevLcvmT80G
1yWMvp+go1wzn7JnkBjgX5/akH+rkkPrXFXYqtkjSRGjkUMjCjKdwQcVfO/5tf8AOOHrzz655RKw
zNWSfTqURm7lOI+EnFXhyal5k8samEu0uNPv7dvgkBaKRSO6OtPwOBXo+hf85IedLKNY5riDUEUB
R9ci+IAf5cRiJPu1caViXmz8mb/Wdb1rVLTUTNquqSpqcVosMaQ89Rmjkki5md3AiF4vxOi1ofnh
VnXkC9/MLy75cPlpNWsNHktrVp7doYxeXEsUUpheXYzIIlEcjM6qSApNOmClSjVLzQtN1S6vNdvp
vMfmO2aezkiuf38sV1G7xBPq0p+xGQsgZiY2WqcQ1DhVNdI8n+evzNv63iPpPlzkGS1ZnkJVT8Hq
ysAZWChV7A8QTVt8VfRXk7yZo/lbTFstPjANB6ktAGYgUqaYqn+KuxV2KuxV2KuxV2KuxV2KuxV2
KvlP/nI/8tJ/L3mI+ddJiP6J1SUHUFQbQXjdXP8Akzdf9aviMVZH+Rv5uWsdtHoerS8bZm/0adjt
E7dVb/IY717H57AhXv4IIqNwehwJbxV8o/8AOT9+bT82LMg056Fa/heXeEIehf8AOJ9wbjyLrkx/
a12b8LK0GApe2YqlPmXzLpnl7TXvr5wAKiKIH4pG/lX+J7Yq+S/zB836x5u8w/VrVWur++lWGC2i
BJJY8UjQYUPp/wDKP8vYPIvk630tuL6lOfrOqzruGuHABVT/ACxgBF+Ve5wqzTFXYq7FXYqx/wAy
+Q/K/mOFo9Vskm5dX6HFXlet/wDOKnla4dn027ltSTUJ1A/HFUJcfkF56L1j81XI+wDIrBHPpMjR
lnWjMUMScSTtQYquh/5xnvb2NINb8w3d3bJIZVt5JHdA7dWVGYqCfGmKs58rfkX5H0BklW3NzOm/
OQ7V8aDFXoMMEMEaxwoEjUUVRsAMVX4q7FXYq7FXYq7FXYq7FXYq7FXYq7FUNqmmafqunXGnajAl
zY3cbRXEEgqrowoQcVfJH5ofkt5i/L+9k1jRBLqHlgkt6yjlLagn7FwB+yO0nTxoeoVOPy3/AD9v
9Lhjsb7/AEuyWgWKRqOg/wCK3329jt8saV7lon5peS9WjUx362srdYrn93T/AGZ+D/hsFJfL/wDz
l5qVtJ+Z+mTW0yTRHRIB6kbB1qLu62qtcIQ9G/5xI8w6NYflnqz6hfQ2zNrc7BJHVWK/VLUVC/aP
TsMSlnPmr89PLumxOmlj63OAaTSVSIe9DR2/DBSvA/MfnjzT531tLKwWbUdRum9OCGIV28FUbKo6
k9B1OFD3j8lfyPg8nKNc1wrd+aJ0K7UeK0VuqxHvIw2d/oG1SxV63irsVdirsVUJr2CGT025s9Ax
WOOSQgEkAngrUrQ4qs/Sdt/JP/0jz/8ANGKu/Sdt/JP/ANI8/wDzRirv0nbfyT/9I8//ADRirv0n
bfyT/wDSPP8A80Yq79J238k//SPP/wA0Yq79J238k/8A0jz/APNGKuXUrUsqkSpyIUF4ZUWrGgHJ
lA3OKorFXYq7FXYq7FXYq7FXYq7FXYq7FXYq0yqylWAKkUIO4IOKvJPPX/ONXknzDLJe6QzeXtSc
lma2UPbOx7tbkqF/55svyOKvJtU/5x8/ODRXY6b9X1i3WpRradY34jxSf0t/ZScVSZ/Kf5z2zenJ
5b1Bm8Y4mkHh9pOQxVEWv5c/nfqhCxaDcQq1KtcPFbgA+Pquh/DArMPLv/OLPmS/kWbzbrUdrAd2
tbGs0pHgZHCxofkr4Ve5+TPy88o+TbM22g2CW7uAJ7pv3lxLT/fkrfERXfiPhHYYqyPFXYqw/wA2
+b/N2nawmmeWvKx8xOlsLu9kN9FYrEru6RIvqo/N3MT+A264qi/IXnzS/OOlTXVrDNY31jO9nqul
XahLm0uY/tRyKPvVhsR71AVZLiqFj/46tx/xgg/4nLiqKxVak0Lu6I6s8dBIoIJUncVHbFWoZ4J0
LwSLKgZ4yyMGAeNijrUd1dSpHYimKvKfLsn5uyfm9rUd9c6HHp0dlpb3sMUd7K4s2nvzAsBaSNRO
SriV2Xj9khdiMVes4q87/wCcgLu6s/yp1S7tJWguYLrS5IZoyVZWXVLYggjFWdan/vMn/Ge3/wCT
6YqisVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVSa1/5TLVP+2dp/8Ayfvc
VYNp/o6L/wA5D69DGyw2et+WrfV9QJPFBPZXRtfUYn4R+6bc4qyny/L5f1LzJe69Za8uqyXcEcNj
aRTAwQWsdC7RRqxDmWU8mlpuOKjYbqovzJc3Nrpuv3NqxS6g0tpIHUVIkRZ2Ugb7gjFWC+WPLOkL
+T2j+ZrSD6v5lXQItSbWYdr2a6lsfUlaafeSb1HY8lcnt3Aoqhvy68s69ceSfJt3Y2+kWVs1vpOo
T6ohl/SMjMsM95zfgFZ7kmRHq2/I4qxfyP5Y0ZP+cfdS85mASeaLSLW9Ts9YkJe6gmsru5kjEMrV
eNGeHk6qaMS3IGpxVGfn3LLFp35lNG7IzaR5VRipIJWTWLxHU07MrEEdxirNYvJHkx/zCuNFbRLC
30i30q2vbfSorWGO2uriW4uIppZokVUmNskUQTmDw9UkbnFXjXmcJDB+demW11Jc6fpt35VtbCN5
GkEEYv8AmbeMkmixSOyBe1KYq+o9T/3mT/jPb/8AJ9MVSyTzLbWms6zFqd/p9ppel2tncPI83CWH
6w06u90ZOMUcbekoj3rs1e2KrtM88eStVjuZdL1/Tb+KzjMt5JbXcEywxjq8rI7BF92xVQ8rfoi5
vtU1ey1j9LS6m8cnESco7e3RfThjiiqeCHiz8qfGxJ6UoqyLFXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYqxK+80+WtE87Xses6raaY1zplnJB9cnjgDrDPd+oVMjLXj6i1+eKvG/OX1nzh
5f8AzP8AzDsY5W0ltJj0Ly5NxZWuLO1l9e8uFBofSeUnie4BxV69baZPc67oV/c+Zra6skkmvNF0
63t4ofVja2kiokiyOzxxxXHLYUrxOKsmmE0d5LKLdriOaKOMhCmxRnJ5B2TrzxVIdM8m+XdLnhks
tFuo47Zme0s2umktIGcEMYLSS5a3hNHYD00FATTqcVWaZ5H8s6ZcQzWOiXcUdrIZrSy+ts9lBIa/
HBZvctbRMORoUjFKmnXFUVHonl+w8tXOgLorW/l+4W5S4s2eERFL53edatNsJHmbYHatBTFUFN5P
8o6pBrX1jRptQh8wmIas0t164lFu5eFFZrlvSWJ2LIkXEKegxVNNa0ix1r0DfaZd+tbFjbXNtcfV
LiPnQOEntriKVVfiOShqN3xVKB+XXkoWd7aR+WpIoNS+qfXhFMI2lawnNzbO7pcK5dJmLl68mP2i
cVZBHBKLWGyitrhI45I29W5mEzUSUSHlI0ssrdNq1+7FXnt75aHmT8wfPuleuluzWvle5jlkiFxH
6lldXV0iyQlk9RGaEK68hUE74qx7849S8y2Ply/8uX+oadqVpNZpqM62Fm9nJbJYavpqyeqpuboG
N4bmQtVRQIeorir0rSNJvF822+oal5ih1O7TT7hLOxht44P9HuZrd2mJWSRnUNAqqem5xVluKuxV
2KuxV2KuxV2KuxV2KuxV2KuxVRu722tER7h+IkdY4wAWZnboFVQWO1SdtgCTsDiqsCCAQag7gjFX
Yq7FXYq7FUq1ryn5W114X1zRrHVXt6/V2vbaG4MddzwMqtxrTtiqZRwQxwrBHGqQooRIlAChQKBQ
o2pTtiqX6T5W8saPPNcaTpFlp1xcbTzWlvFA8gry+No1UtvvvirH9W8p+crvzwNXtPMUllov1L6u
lpGsbNDNzBLLHLFLHJ6ndiVZaUFQdlUd/hrzV/1N97/0i6f/ANk+Ku/w15q/6m+9/wCkXT/+yfFV
ax0nWLC7S61DV7jWYEVgsUkFuhidqD1VFvHGzHjVe/XFW7/S9Y1G8Nzp+rXOjwcFQqkEDGVlLEsy
3McjLSoA6V+VMVUP8Neav+pvvf8ApF0//snxV3+GvNX/AFN97/0i6f8A9k+KpfaeTvN8Pniy1qfz
NPdaVb2kkNzaSLEn1h3aqK0MUUcarH9r1Klz9nYdVWU3ei6PeC6F1YwTi+jSG99SJG9aKMsUjlqP
jRS7UVttz44qgdH8k+TNFeZ9G0DTtMe5T0rhrO0ggMkZ34OY0XkvscVROj+W/LuiLKujaXaaYs5D
TCzgitw5HQt6aryO/fFUxxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsV8+NcTLpemx29u63V0JTc3QnZ
I3tB9YjVUtyjyPI0dOHMBl5V5CqlVk1q0z20LzKiTMimRI2LorEbhXITkoPQ8RXwxVUxV2KuxV2K
vmLVf+c0Y9P1S8sD5aMhtJ5IDIJwAxjcpWlDStMVQv8A0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/
0kD/AJpxV3/Q78X/AFLDf9JA/wCacVd/0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/0kD/AJpxV3/Q
78X/AFLDf9JA/wCacVd/0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/0kD/AJpxV3/Q78X/AFLDf9JA
/wCacVd/0O/F/wBSw3/SQP8AmnFXf9Dvxf8AUsN/0kD/AJpxV3/Q78X/AFLDf9JA/wCacVd/0O/F
/wBSw3/SQP8AmnFXpf5I/n0v5n6hqloulHThpsMcpYyepz9VitOgpTjir1zFXYq7FXYq7FXYq7FX
Yq7FXYq7FWHefdU1OzlRYbu5s7RLG9ulFmsXr3d1bhGhtI3ljnCsylmVVTk9NtlYFVkmjG7OnRC7
m+sTKXX6xRVMqK5VJCEotXQBjxFPDbFUbirsVdirsVfmP5gsry986apaWUElzdz6hcJBbwo0kjsZ
moqIoLMT4DFULrXlvzFoUqQ63pd5pc0gJjjvYJbdmA6kCVVJxVLcVRmlKpvPiVXCxTOFYBhySF2W
oNQaEYqrfX5v99wf9I8H/NGKu+vzf77g/wCkeD/mjFXfX5v99wf9I8H/ADRirOfy1/LbzT55e+ls
LOA2Flb3TNccLGKt1HayS28I9bhUPKqK5GyqakjrirFNatdV0XVbnStRhtEvbR/TuEiSznVWoCR6
kIkjald6NsduuKoH6/N/vuD/AKR4P+aMVZR590AeWrjREgeKePV9F07V/jtrfkj3kAeSPaMVAkDc
fanzxViGqKq3nwqFDRwuQoCjk8Ss1ANhucVQmKuxV9Nf84P/APHf81f8wlt/ycfFX11irsVdirsV
dirsVdirsVdirsVdirsVdirsVdirsVdir84LK8u7H8ytZvbOZ7e7tTrU1vPGSrxyR21yyOrDcFWF
QcVZd+VXmLV/PdnrP5beZLubVotTsrm88vSXbmaW11SziaeNopJCXVZERw6g7/SaqvMdF0/SLm21
O51O++qLZ2xezgRQ8tzcswSKJQSOK7l3fso8SMVQ+k/71t/xguP+TD4qzGTyx5Z0fyh5d13Wlvb6
bzGbqSGCzmitlt4LSc2xLNJDcmV3dWIHwgDxriqbw/lrpx8q2er6bbXnmKXWLu/isDC6Waw2dg8U
fqyq6ykyymcUXlRaH7WKo26/LjyXoXkrV9e15r24ntdVsLK1srWWKOX/AEnTjeSW8kxSaNXQy8Xf
0m3joFHLZVB+XNF07TvMP1vS3mbTNZ8p67f2aXJVpowNOv7aSOR0CK5Sa2cBgoqKGg6YqiPN35ce
T9J/MC48iWQvxd2kUVxda7c3ULW6QrYrf3U31NLVXpHDzovr9uuKobyl5F8jebrDzNc6ZNqOny+W
tIvdU+r3UkE5u1t4mMTq0cUQhpLx5xnnsdn2xVT/ADt/3p8lf+Afon/Jg4qwZtI1LVNRkhsIGnkt
7EXc4Wg4QW1oJppCSQKKiE+/Qb4qk+Kproun6Rc22p3Op331RbO2L2cCKHlublmCRRKCRxXcu79l
HiRir6F/5wf/AOO/5q/5hLb/AJOPir66xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV+b
Q/5T/wAwf6muf9Ql1irIf+cck+rfmUnmCXaw8tafqGq38m1FijtZIxWu28kqjFUyu/IXlWzPlSwu
ZNMtNN1DTdN1HWtRvrtodQ5ahGJZngTmF4QJJSNfTIYr8XI4q8q0n/etv+MFx/yYfFWZHzN5Y1jy
foGg6617YzeXDdJbXFjBFdLPBdz/AFgq6yzW3pukjNRhyBB6bbqr280eUtW8oWHlrVhf6bHol5eX
Gk3drHDfM0F8Yy8NwjyWI5oYVIkVt9/gGKqN95x0Q/l7e+UNPtbmKN9dh1azmndJD6EdnLbMspUJ
8ZZlYcVp1HbdVFaL528v21haG7S7F7pnlzUtCtIoo43imn1KXUCZJJGkRo0jTUF6IxYg9OuKqvm3
8z7XVvzVuPOtpZP9Uu4Ybe606cgF4W05LC8i5py2kT1FVvAgkdsVRflHz15F8oad5mtdNh1HUZ/M
uk3mlm6uY4bc2i3ELBEVI5phNWUpzkJT4V2SppiqR/mD5q0fzImgTWbXKXGlaNp2kSwTQxolbK3C
SSLKs0hblJXjVF+H3xVvyTo+m6pruspfwidLTyzqN5ArEgLPb6UzxPsRujbj3xVkI8t6HfafpNh5
W8v+XtUvbyxsl+uXmtumoS6hPbxmeNbL6/aqrrcu8aJ6R5UHWuKq135C8q2Z8qWFzJplppuoabpu
o61qN9dtDqHLUIxLM8CcwvCBJKRr6ZDFfi5HFWcf84P/APHf81f8wlt/ycfFX11irsVdirsVdirs
VdirsVdirsVdirsVdirsVdirsVdir81zrlvof5lX2p3NiupWkN/eJdae7tEs8MzSRSxmRasvJHIq
OmKpp5g/M/SW8uXflvyZ5bj8q6VqjI+st9alv7u6ER5RxNcSqhSJTvwVdz361VQd7580bVrLSjru
htfavotjFptncx3ZhtpYLYFbf61biJ3kManjWOaPkAK4ql35eeXbXzH5z0rRbrVo9EgvZhG2pSkg
JsTRSKDm/wBlKkCp64qyfU/JENrqV3aj8xtIhEE0kQhuJtXEycHK8ZKWAHMUo1O+KoX/AAjF/wCX
L0P/AJHax/2Q4q7/AAjF/wCXL0P/AJHax/2Q4qkiXVxJrf6HN2YYjcG1OpNNc0RQ/D13ozfAv22o
nTFU7/wjF/5cvQ/+R2sf9kOKu/wjF/5cvQ/+R2sf9kOKpl5c/L+31PXtP0+T8xdKnju50heK0m1U
zkO1D6YksQnIdfiIHiR1xVIDqUfkbzj5js7W7h8wwyWmpaMNRhdljlW9t3t/XViGqU51I3BIoGpR
sVb8mebPJHlu+07V5PL9/qOuaZNFd287anHDa/WIHEkbfV1s2k4q6iqmbf2xV17580bVrLSjruht
favotjFptncx3ZhtpYLYFbf61biJ3kManjWOaPkAK4q9m/5wf/47/mr/AJhLb/k4+KvrrFXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX55+Zfya/NafzHqs8PlPVJIZby4eKRbWQqytKxUg06
EYqln/KlPzc/6lDVf+kWX+mKu/5Up+bn/Uoar/0iy/0xV3/KlPzc/wCpQ1X/AKRZf6Yq7/lSn5uf
9Shqv/SLL/TFXf8AKlPzc/6lDVf+kWX+mKu/5Up+bn/Uoar/ANIsv9MVRB/KL85jD6R8qatxoFr9
UfkVHRC/HkV/ya0xVD/8qU/Nz/qUNV/6RZf6Yq7/AJUp+bn/AFKGq/8ASLL/AExV3/KlPzc/6lDV
f+kWX+mKu/5Up+bn/Uoar/0iy/0xV3/KlPzc/wCpQ1X/AKRZf6Yq7/lSn5uf9Shqv/SLL/TFX0F/
ziF5E85eWda8xy+YNFvNKiuba3SB7uFog7LI5YLyArSuKvp3FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FX//2Q==</xmpGImg:image> + </rdf:li> + </rdf:Alt> + </xmp:Thumbnails> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" + xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" + xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"> + <xmpMM:OriginalDocumentID>uuid:AF0436EA903B11DB90DFD5CCDBF73423</xmpMM:OriginalDocumentID> + <xmpMM:DocumentID>xmp.did:0180117407206811822AA5DFA83DBEE8</xmpMM:DocumentID> + <xmpMM:InstanceID>uuid:a78d6ca4-ee25-5f4f-9857-efe87b47a9ff</xmpMM:InstanceID> + <xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass> + <xmpMM:DerivedFrom rdf:parseType="Resource"> + <stRef:instanceID>uuid:d0c80ddf-8d24-9c4a-b5a2-f5403c78eac7</stRef:instanceID> + <stRef:documentID>xmp.did:F87F11740720681183D1ECDDA24ACDE7</stRef:documentID> + <stRef:originalDocumentID>uuid:AF0436EA903B11DB90DFD5CCDBF73423</stRef:originalDocumentID> + <stRef:renditionClass>proof:pdf</stRef:renditionClass> + </xmpMM:DerivedFrom> + <xmpMM:History> + <rdf:Seq> + <rdf:li rdf:parseType="Resource"> + <stEvt:action>saved</stEvt:action> + <stEvt:instanceID>xmp.iid:0380117407206811822AABA902D116C4</stEvt:instanceID> + <stEvt:when>2014-09-11T14:30:24+02:00</stEvt:when> + <stEvt:softwareAgent>Adobe Illustrator CS6 (Macintosh)</stEvt:softwareAgent> + <stEvt:changed>/</stEvt:changed> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <stEvt:action>saved</stEvt:action> + <stEvt:instanceID>xmp.iid:0180117407206811822AA5DFA83DBEE8</stEvt:instanceID> + <stEvt:when>2014-09-16T12:36:28+02:00</stEvt:when> + <stEvt:softwareAgent>Adobe Illustrator CS6 (Macintosh)</stEvt:softwareAgent> + <stEvt:changed>/</stEvt:changed> + </rdf:li> + </rdf:Seq> + </xmpMM:History> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/"> + <illustrator:StartupProfile>Video</illustrator:StartupProfile> + <illustrator:Type>Document</illustrator:Type> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/" + xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#" + xmlns:stFnt="http://ns.adobe.com/xap/1.0/sType/Font#" + xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/"> + <xmpTPg:NPages>1</xmpTPg:NPages> + <xmpTPg:HasVisibleTransparency>True</xmpTPg:HasVisibleTransparency> + <xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint> + <xmpTPg:MaxPageSize rdf:parseType="Resource"> + <stDim:w>1920.000000</stDim:w> + <stDim:h>1080.000000</stDim:h> + <stDim:unit>Pixels</stDim:unit> + </xmpTPg:MaxPageSize> + <xmpTPg:Fonts> + <rdf:Bag> + <rdf:li rdf:parseType="Resource"> + <stFnt:fontName>OldNewspaperTypes</stFnt:fontName> + <stFnt:fontFamily>OldNewspaperTypes</stFnt:fontFamily> + <stFnt:fontFace>Regular</stFnt:fontFace> + <stFnt:fontType>TrueType</stFnt:fontType> + <stFnt:versionString>1.0 2007-02-14</stFnt:versionString> + <stFnt:composite>False</stFnt:composite> + <stFnt:fontFileName>OldNewspaperTypes.ttf</stFnt:fontFileName> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <stFnt:fontName>Perpetua</stFnt:fontName> + <stFnt:fontFamily>Perpetua</stFnt:fontFamily> + <stFnt:fontFace>Regular</stFnt:fontFace> + <stFnt:fontType>Open Type</stFnt:fontType> + <stFnt:versionString>Version 1.76</stFnt:versionString> + <stFnt:composite>False</stFnt:composite> + <stFnt:fontFileName>Perpetua.ttf</stFnt:fontFileName> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <stFnt:fontName>SmothBight</stFnt:fontName> + <stFnt:fontFamily>Smoth Bight</stFnt:fontFamily> + <stFnt:fontFace>Regular</stFnt:fontFace> + <stFnt:fontType>Open Type</stFnt:fontType> + <stFnt:versionString>Version 1.00 (Kustren)</stFnt:versionString> + <stFnt:composite>False</stFnt:composite> + <stFnt:fontFileName>Smoth-Bight - Por Kustren.otf</stFnt:fontFileName> + </rdf:li> + </rdf:Bag> + </xmpTPg:Fonts> + <xmpTPg:PlateNames> + <rdf:Seq> + <rdf:li>Cyan</rdf:li> + <rdf:li>Magenta</rdf:li> + <rdf:li>Yellow</rdf:li> + <rdf:li>Black</rdf:li> + </rdf:Seq> + </xmpTPg:PlateNames> + <xmpTPg:SwatchGroups> + <rdf:Seq> + <rdf:li rdf:parseType="Resource"> + <xmpG:groupName>Groupe de nuances par défaut</xmpG:groupName> + <xmpG:groupType>0</xmpG:groupType> + <xmpG:Colorants> + <rdf:Seq> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Blanc</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>255</xmpG:red> + <xmpG:green>255</xmpG:green> + <xmpG:blue>255</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Noir</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>0</xmpG:green> + <xmpG:blue>0</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Rouge RVB</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>255</xmpG:red> + <xmpG:green>0</xmpG:green> + <xmpG:blue>0</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Jaune RVB</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>255</xmpG:red> + <xmpG:green>255</xmpG:green> + <xmpG:blue>0</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Vert RVB</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>255</xmpG:green> + <xmpG:blue>0</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Cyan RVB</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>255</xmpG:green> + <xmpG:blue>255</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Bleu RVB</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>0</xmpG:green> + <xmpG:blue>255</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>Magenta RVB</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>255</xmpG:red> + <xmpG:green>0</xmpG:green> + <xmpG:blue>255</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=193 V=39 B=45</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>193</xmpG:red> + <xmpG:green>39</xmpG:green> + <xmpG:blue>45</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=237 V=28 B=36</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>237</xmpG:red> + <xmpG:green>28</xmpG:green> + <xmpG:blue>36</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=241 V=90 B=36</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>241</xmpG:red> + <xmpG:green>90</xmpG:green> + <xmpG:blue>36</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=247 V=147 B=30</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>247</xmpG:red> + <xmpG:green>147</xmpG:green> + <xmpG:blue>30</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=251 V=176 B=59</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>251</xmpG:red> + <xmpG:green>176</xmpG:green> + <xmpG:blue>59</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=252 V=238 B=33</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>252</xmpG:red> + <xmpG:green>238</xmpG:green> + <xmpG:blue>33</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=217 V=224 B=33</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>217</xmpG:red> + <xmpG:green>224</xmpG:green> + <xmpG:blue>33</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=140 V=198 B=63</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>140</xmpG:red> + <xmpG:green>198</xmpG:green> + <xmpG:blue>63</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=57 V=181 B=74</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>57</xmpG:red> + <xmpG:green>181</xmpG:green> + <xmpG:blue>74</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=0 V=146 B=69</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>146</xmpG:green> + <xmpG:blue>69</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=0 V=104 B=55</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>104</xmpG:green> + <xmpG:blue>55</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=34 V=181 B=115</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>34</xmpG:red> + <xmpG:green>181</xmpG:green> + <xmpG:blue>115</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=0 V=69 B=157</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>169</xmpG:green> + <xmpG:blue>157</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=41 V=71 B=226</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>41</xmpG:red> + <xmpG:green>171</xmpG:green> + <xmpG:blue>226</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=0 V=113 B=188</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>113</xmpG:green> + <xmpG:blue>188</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=46 V=49 B=146</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>46</xmpG:red> + <xmpG:green>49</xmpG:green> + <xmpG:blue>146</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=27 V=20 B=100</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>27</xmpG:red> + <xmpG:green>20</xmpG:green> + <xmpG:blue>100</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=102 V=45 B=145</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>102</xmpG:red> + <xmpG:green>45</xmpG:green> + <xmpG:blue>145</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=147 V=39 B=143</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>147</xmpG:red> + <xmpG:green>39</xmpG:green> + <xmpG:blue>143</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=158 V=0 B=93</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>158</xmpG:red> + <xmpG:green>0</xmpG:green> + <xmpG:blue>93</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=212 V=20 B=90</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>212</xmpG:red> + <xmpG:green>20</xmpG:green> + <xmpG:blue>90</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=237 V=30 B=121</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>237</xmpG:red> + <xmpG:green>30</xmpG:green> + <xmpG:blue>121</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=199 V=178 B=153</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>199</xmpG:red> + <xmpG:green>178</xmpG:green> + <xmpG:blue>153</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=153 V=134 B=117</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>153</xmpG:red> + <xmpG:green>134</xmpG:green> + <xmpG:blue>117</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=115 V=99 B=87</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>115</xmpG:red> + <xmpG:green>99</xmpG:green> + <xmpG:blue>87</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=83 V=71 B=65</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>83</xmpG:red> + <xmpG:green>71</xmpG:green> + <xmpG:blue>65</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=198 V=156 B=109</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>198</xmpG:red> + <xmpG:green>156</xmpG:green> + <xmpG:blue>109</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=166 V=124 B=82</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>166</xmpG:red> + <xmpG:green>124</xmpG:green> + <xmpG:blue>82</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=140 V=98 B=57</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>140</xmpG:red> + <xmpG:green>98</xmpG:green> + <xmpG:blue>57</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=117 V=76 B=36</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>117</xmpG:red> + <xmpG:green>76</xmpG:green> + <xmpG:blue>36</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=96 V=56 B=19</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>96</xmpG:red> + <xmpG:green>56</xmpG:green> + <xmpG:blue>19</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=66 V=33 B=11</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>66</xmpG:red> + <xmpG:green>33</xmpG:green> + <xmpG:blue>11</xmpG:blue> + </rdf:li> + </rdf:Seq> + </xmpG:Colorants> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:groupName>Gris</xmpG:groupName> + <xmpG:groupType>1</xmpG:groupType> + <xmpG:Colorants> + <rdf:Seq> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=0 V=0 B=0</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>0</xmpG:red> + <xmpG:green>0</xmpG:green> + <xmpG:blue>0</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=26 V=26 B=26</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>26</xmpG:red> + <xmpG:green>26</xmpG:green> + <xmpG:blue>26</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=51 V=51 B=51</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>51</xmpG:red> + <xmpG:green>51</xmpG:green> + <xmpG:blue>51</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=77 V=77 B=77</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>77</xmpG:red> + <xmpG:green>77</xmpG:green> + <xmpG:blue>77</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=102 V=102 B=102</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>102</xmpG:red> + <xmpG:green>102</xmpG:green> + <xmpG:blue>102</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=128 V=128 B=128</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>128</xmpG:red> + <xmpG:green>128</xmpG:green> + <xmpG:blue>128</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=153 V=153 B=153</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>153</xmpG:red> + <xmpG:green>153</xmpG:green> + <xmpG:blue>153</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=179 V=179 B=179</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>179</xmpG:red> + <xmpG:green>179</xmpG:green> + <xmpG:blue>179</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=204 V=204 B=204</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>204</xmpG:red> + <xmpG:green>204</xmpG:green> + <xmpG:blue>204</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=230 V=230 B=230</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>230</xmpG:red> + <xmpG:green>230</xmpG:green> + <xmpG:blue>230</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=242 V=242 B=242</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>242</xmpG:red> + <xmpG:green>242</xmpG:green> + <xmpG:blue>242</xmpG:blue> + </rdf:li> + </rdf:Seq> + </xmpG:Colorants> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:groupName>Groupe de couleurs pour films et vidéos</xmpG:groupName> + <xmpG:groupType>1</xmpG:groupType> + <xmpG:Colorants> + <rdf:Seq> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=123 V=112 B=49</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>123</xmpG:red> + <xmpG:green>112</xmpG:green> + <xmpG:blue>49</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=199 V=163 B=21</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>199</xmpG:red> + <xmpG:green>163</xmpG:green> + <xmpG:blue>21</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=182 V=165 B=85</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>182</xmpG:red> + <xmpG:green>165</xmpG:green> + <xmpG:blue>85</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=235 V=208 B=101</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>235</xmpG:red> + <xmpG:green>208</xmpG:green> + <xmpG:blue>101</xmpG:blue> + </rdf:li> + <rdf:li rdf:parseType="Resource"> + <xmpG:swatchName>R=122 V=111 B=185</xmpG:swatchName> + <xmpG:mode>RGB</xmpG:mode> + <xmpG:type>PROCESS</xmpG:type> + <xmpG:red>122</xmpG:red> + <xmpG:green>111</xmpG:green> + <xmpG:blue>185</xmpG:blue> + </rdf:li> + </rdf:Seq> + </xmpG:Colorants> + </rdf:li> + </rdf:Seq> + </xmpTPg:SwatchGroups> + </rdf:Description> + <rdf:Description rdf:about="" + xmlns:pdf="http://ns.adobe.com/pdf/1.3/"> + <pdf:Producer>Adobe PDF library 10.01</pdf:Producer> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> + + + + + + + + + + + + + + + + + + + + + +<?xpacket end="w"?>
endstream
endobj
3 0 obj
<</Count 2/Kids[7 0 R 8 0 R]/Type/Pages>>
endobj
7 0 obj
<</ArtBox[224.515 66.2373 1617.31 1080.0]/BleedBox[0.0 0.0 1920.0 1080.0]/Contents 732 0 R/Group 733 0 R/LastModified(D:20140918115258+02'00')/MediaBox[0.0 0.0 1920.0 1080.0]/Parent 3 0 R/PieceInfo<</Illustrator 734 0 R>>/Resources<</ColorSpace<</CS0 735 0 R/CS1 736 0 R/CS2 737 0 R/CS3 738 0 R/CS4 739 0 R/CS5 740 0 R/CS6 741 0 R/CS7 742 0 R/CS8 743 0 R>>/ExtGState<</GS0 744 0 R/GS1 745 0 R/GS2 746 0 R/GS3 747 0 R/GS4 748 0 R/GS5 749 0 R/GS6 750 0 R/GS7 751 0 R>>/Font<</T1_0 752 0 R/TT0 753 0 R/TT1 754 0 R>>/ProcSet[/PDF/Text/ImageC/ImageI]/Properties<</MC0 730 0 R>>/Shading<</Sh0 755 0 R>>/XObject<</Fm0 756 0 R/Fm1 757 0 R/Fm2 758 0 R/Fm3 759 0 R/Fm4 760 0 R/Fm5 761 0 R/Fm6 762 0 R/Im0 763 0 R/Im1 764 0 R/Im2 765 0 R/Im3 766 0 R/Im4 763 0 R/Im5 767 0 R/Im6 768 0 R/Im7 769 0 R/Im8 770 0 R/Im9 771 0 R>>>>/TrimBox[0.0 0.0 1920.0 1080.0]/Type/Page>>
endobj
8 0 obj
<</ArtBox[6464.01 6726.74 7856.81 7766.5]/BleedBox[0.0 0.0 14400.0 14400.0]/Contents 772 0 R/Group 773 0 R/LastModified(D:20140918115258+02'00')/MediaBox[0.0 0.0 14400.0 14400.0]/Parent 3 0 R/PieceInfo<</Illustrator 734 0 R>>/Resources<</ColorSpace<</CS0 735 0 R/CS1 736 0 R/CS2 737 0 R/CS3 738 0 R/CS4 739 0 R/CS5 740 0 R/CS6 741 0 R/CS7 742 0 R/CS8 743 0 R>>/ExtGState<</GS0 744 0 R/GS1 745 0 R/GS2 774 0 R/GS3 747 0 R/GS4 775 0 R/GS5 776 0 R/GS6 777 0 R/GS7 778 0 R>>/Font<</T1_0 752 0 R/TT0 753 0 R/TT1 754 0 R>>/ProcSet[/PDF/Text/ImageC/ImageI]/Properties<</MC0 730 0 R>>/Shading<</Sh0 755 0 R>>/XObject<</Fm0 779 0 R/Fm1 780 0 R/Fm2 781 0 R/Fm3 782 0 R/Fm4 783 0 R/Fm5 784 0 R/Fm6 785 0 R/Im0 763 0 R/Im1 764 0 R/Im2 765 0 R/Im3 766 0 R/Im4 763 0 R/Im5 767 0 R/Im6 768 0 R/Im7 769 0 R/Im8 770 0 R/Im9 771 0 R>>>>/TrimBox[0.0 0.0 14400.0 14400.0]/Type/Page>>
endobj
772 0 obj
<</Filter/FlateDecode/Length 3434>>stream
+Hԗ[o%WWiqA`K\Ϸt&y +sNuWZU7MW7]߸`)ΡG->ٻ1૫-N5eS[7ɻ?-<`O? {W?ӯTKD]%%5w璛5yZDⓜcε\v7 +_:Z{:.`nQ,6fE-Msx,/51*1y2B +w: +cqb;O/_=w^ +?zaaLfҜ{8e8!W,!?PO9yL뗸K)<oJaj:H.?mX +貟+'PU#om%9(4)uH#+S"/|]yx2zRy3Ѱ\C[rƴ)7չFVC;u6-;: Ҫm + +χ!LzD[HK߬H! ++1Cɐ*-<5&]a5/, +4>/c,ky3Ez{ՄMIiE6-p]N+N!:HF,RC,)!U]aW:V
g;+WtHu<j8%ےbTţKZ0EgA]),2IۄÝ:9QuIaB^a`X;*-nmw +__)δ
TY^&vypJOSҟNI)߷G|vܷn`&k
&75Ћ,,ۃ6/+D`X3욹5i:0BH6'53R'Y⋻Otw6^hӋOqܖ }g]]xw~pQ=-yyTY CֳĠpyと.nFͩq>w`u߾ު/3wƒnIrūF:OWGWNgh=活{z7o
o5Qp;|B̭{4tV]Z.ytP߀z +wH.lO^_^7[)@#IKuxZv䒪w˥ǵ IbeߍuhO?5%mo +9XϜǽ +ym$1ڽ"dJ~#w2J+nn)|ѐʟ PDoO$
lOpsfnBHexWumDѧ.nWMe#ɭcx=OAA<be՜OBI<WBj1VTqP)l6j2_[zUpv! +2$?ܦEʰ(=DE84fd|WuKs8deZ};bf),G#+2OJ}u)vonRQX;
N"IRJ!=:hj @jua2j;.5A7JO^Uؾ\[jp_)K +",p2U?G cAlp^0lw9U4^Kel.Fh)O|#}[tg{(NƹN6.U1Y*j+~e"#z-+VB taψ;/hqHA#+q=uaO"pְd#6P|9XhVY&`ٺ\#/$rː*fJO~j~>`U4T OIZĶ8|%]2(.%.c +q +/GS0 gs +486 0 0 480 6513.1162109 7207.1401367 cm +/Im0 Do +Q +
endstream
endobj
780 0 obj
<</BBox[6557.12 7644.14 6955.12 7246.14]/Group 791 0 R/Length 61/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 792 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 793 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +398 0 0 398 6557.1162109 7246.1401367 cm +/Im0 Do +Q +
endstream
endobj
781 0 obj
<</BBox[6625.12 7519.14 6893.12 7475.14]/Group 794 0 R/Length 60/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 795 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 796 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +268 0 0 44 6625.1162109 7475.1401367 cm +/Im0 Do +Q +
endstream
endobj
782 0 obj
<</BBox[6622.12 7549.14 6896.12 7246.14]/Group 797 0 R/Length 61/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 798 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 799 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +274 0 0 303 6622.1162109 7246.1401367 cm +/Im0 Do +Q +
endstream
endobj
783 0 obj
<</BBox[6647.5 7607.5 6943.5 7516.5]/Group 800 0 R/Length 48/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 801 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 802 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +296 0 0 91 6647.5 7516.5 cm +/Im0 Do +Q +
endstream
endobj
784 0 obj
<</BBox[6485.5 7766.5 6957.5 7240.5]/Group 803 0 R/Length 49/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 804 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 805 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +472 0 0 526 6485.5 7240.5 cm +/Im0 Do +Q +
endstream
endobj
785 0 obj
<</BBox[6522.5 7682.5 7035.5 7180.5]/Group 806 0 R/Length 49/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 807 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 808 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +513 0 0 502 6522.5 7180.5 cm +/Im0 Do +Q +
endstream
endobj
763 0 obj
<</BitsPerComponent 8/ColorSpace 736 0 R/Decode[0.0 255.0]/Filter/FlateDecode/Height 87/Intent/RelativeColorimetric/Length 321/Name/X/SMask 809 0 R/Subtype/Image/Type/XObject/Width 310>>stream
+HgN@@uޝad"xVzj +slBXiniYBru\(N[m.LCN~ݬ_֛n840-!+b-7NӴIEWLn5ql_l˯6t^QETPc`PO՞诪 +Hg{Jc6'9
Lw +jH!T2#Wu\dt3 }?^ؽ>{}`s3fz~p?xxv/^zkZ욣f(uP6ʲ$vf~{iv] N 0Ʒ)v8XLc<麆RPQd$ <q,0>Mӽ^ˋfXTW3 +c>vڭf^Vʥb!fJ&s$>yƓTb;'gL]7JY*GA(x"t&rZ7v"BUSjz9w,$Pcˋ|x;.n *opԙ/NsXүRD,^_~ݿ?/..>]=>4l{S7`yHʕj+ϝ'GGD4SժL*y|-U`58;cM+phSu4uQÞ>YܝjLJC CC2ߴ!_FSkTMgOofb#M{?-3-lmd8M;)*S쾬4Fl}O1n>w~KƟeaX{Zw~=ې)'Vv?!t١ F=!5ʕ[ 'Ṿ-ڈ5Ն͊N$Lef{ +=kr\y=]<;15FtDn׳& +NC1rcӳмlv'y%vr^XF@/؝Smh_>_f
ܳ7 7Բw嬽f#zϗ?myKVDD[zv"wv,&n@6}r_ϴ;h!;,Uv_Y1*C7Xwj"+Do^:k휩c9/YGAv''Ʀ=`;1;Ұ$JDO!"v]r =UX3vY6r@ zC͘얳)XnWoq{N*9ާMcv4rDOLgvHXeX')>lVDE,'ʚv/tۆ&XO9:Y*
ݣ@R +;ͦc^Gxq8B<;ۍrNq``皺<`\!z@LV<ڭ6cxY8`h7$Zsj]m"KcfIhVvCaFAz.+~kuvvӑ,Wֈ^e} +vv˩.s6VD$m +F`G;[/\ʧ/;:ۀYG;S+\*"zb͂]LeB%I-T[vL`D<A2Jvvvٰ,X.tܠ;эRŢ1^T]QGe]VHs҅jSSI, G^Wڲ
vڕr^X/A-[;rMʼnܓO/ʖ:5Kd==>X2Sl]-5D=^4.Ekvg*B!5vky*rD/rUa +va\*J}zT^hgj6rwwD/T`e',w{sK"4ؽn:G,w=T7`wb|1{_OX5{xJ܂i.x>]}"zI{JH]]^P$Qd';%X>`BvixnN%k8\noww Iq/>0٭Ozk\0˥!"ESYihx&#AzD/Ifh7e +rzYXi턍- ꈞŎv
VJ1rZmhq
xYM QkɊvNQrj-HavS)UD`F;X结Be҃R$zz+_ܢ1;rN]g1v 'ɉoЎ߸dRӠxCB.ЫAN*=q;I$DOvvO7٠]9+S,Sx#v&`Anfzɵ6uvxvNniv=^~Frl'X}ts +ܯ1=AYRѓ-ػ쿠/'ON>lhwߠ)J2ӛT;{Cv#߾_b{eRαtCo_X9ɛ\Ўߤ+WVKr#ܗ/oVa$g%fej響/h
VN51g8 +X[bJ(z r{Pvȹ=^_=^n舟x@<^}D=RT vbmӱ"zDctmv]<@qz G8ډ5idz t}]6 r@0̠HCf.
r`(6vn"ӱp0H8`(sh'kzUP@.|s +턁]1àrH4.HCv)E"rh,)N1T<=l Dz;grt"=ti_?A;~`Wx@.Hykdw{P)S\"J7h'L%StXA;v/wGRN@R4-Un"34M15x3(Nt&nNg,4\:-Kh7Ҫs\&+Vw/h'[mWKl@.˗lhzY1\qr|W;h7gPg<\Pdkh7dcQ. +rbXXܣ0nsYaEb47n]tkUeJ%T[K'h'^i#rL^BɆvoze +×{s{a;LwHj| u4({U[^^b:%kr +=ȽO;ͥTct=ȽNz6~G`Wy' 7O曣T{b:Crt={աzLg˝bj"Gۯ) 7W{Վh';^fЃlX4'{]YЃ|u'{iZ,r=n\.Me^.jS/Tc߬VЃj٫N +vvخЃz=hV@;.lvw ;.r~䶻pB=.yavb8QN;Ʈ=S=ЃpTM7*h';N w84ˋi'ؕI`=OnqI;.
C=N=Ui@r Zek +=ȩn!<4AN
ˋJ:}uAN7ۏvR[]āc^'gN紓kE8j g+7=ڕiڦ =ș "ϱ,Aβ/JK5v1cЃ8NYNOrYE;9q Vr}iܽ=$]zs=?L4].σ<?Ң\k1Cr~@oH0A.=Qhvbh#A.#
ډM0(SA:"Nh;]i=E1#̒8ʺ9ivbhSA.NRaw\gWi@rI@8R,A. +
˧ fv7uvug==e +
Է+T,/h7Xy=nchˢSA,]?؉?Azʪꦂvzv:7]uӃ\UwSA~]7;˹5~diׯgwE{.W
vٹڣ]٥vpk7~qTn|ۏ~qTyyyc1c1c1c1c1cO +HI +Hҹ0Plc@P +kku.`A|
i +H{_t:if3q."&"?#i~oaaaaaaaaaaa/Cv/^ڽ<{yyo~?۷ݒ?h;r߱Kfٜ|;^l.zɕ9I}K҂dsE2M˴z2c"^جWK{nNHO۶%Η9c|wnvϳ!*\LӦxe4e$!DZ`0>Mӽ^̀$U3=swZ",|9@aA/HQ'iA=AvO;Z)eDDrӽnj6j\*l&MQd"F"ch,r@z2~֎tAᇇAQdҙl._(ʕjhڝnϹ=,7*Xn6a]/&v(rۛϟ>^~_]8Z-ܯPx,t?|/.>\~.t)̩bu{U=3^a9Hʕj+^>O/O%br|"_Jydpk}|MjctX(Vf-,'6TH%}|@SɴǑUW\{\9w]iLSt2ɞ +4U[s^suQve[nwDm|7-aWl)/ى +f)f zi|݈gz ר7@婉vvT WA礉vvDժ5Ь4Fvkc,6U+UkNWArrZ]F@N`:M+ʠyezBv3 W*@Ѧ#
VhH W,Aq2\YB +E#QKtKjJ\$$E|vJJΥV9{r^<)55K#=?c2]l]@;KlӉ݇A/ +uܠy]@ţ ^4NkhNށ^1
vT̓blmLMF ws}zh"SeH<vAa%߃+л?R%V_y`G=C gchю+S;=DSŁ~B.A>Dvk-%#p z7pVhw%w ]߅hC8`vד䜳D"#298"mU#շ30qp,o_X`KEC녢,ډշ{id Q^ +Dv]&@8F;vt<9zPn^tNo@r/E;jL4˹.^/IU6λh'is:Gh'Z߮{Ye> +vQPv!rvtD^Tj=Hkgh7o gXAMNrul=gW_No8Al2Ch56A g2@c_wh'le9zvvqv/glr@Π7On!;:=Yh'^J)[@NՁNnn!;-mh'tXM Qk@dumkb9J
zFډ=ߞ,"nTJ̮Pn}vLzS*7x=b>2@N!Wf珯Nа29i
h'^9
Ie;#kN
rY)nB|6Ы@nvfT:?:<hvJ=%Mh`w4 u +LBk1kNJ$?%'G qvVoA4Vi_;^vٽ>vϪ?@OЩeF#r߿}'zh7~-j}IfUf*YٽHB/_zhqv˽z6f9ܗ_z=ɛ\<q;NϜg=n-I3ܧO^oZa+h7ݧ>[_.v Q1M"x+oG;AFow]{r OSwEͳ;x~<>ُ=^7ڢJ8^_̪T{E;AB?z}Mo9h2>F6
~JjHv.'F;ߦ̀i4ؿz|EFfgywYgt}4 u +T*|LvFd2)#r2&4;z\zDN.WtS)G +M{i/BNZ="T@f
FӨTGT*
Mow^̇]FZ
zDN1ڍ5lh@i4:#ډ׳|{TMzV7B3 3t:#r:vqv/wŨbAF;_lߢ!;` +Pn vvArs<t +b:
AȅS@;vsL2DbtyB;a`fSh\4OeJh'ֻ<cX,d^ rIG$C;v7B:HK$R|yn턁]aI#r$)TNbeR)#r-Vn<4Εjh'һRgdl}"vr!NKv"
+J1ɀdrrsչj)͂fJkD۽kl!r[[=FB-G"[_C;v'rP +˂cJ}ne"vkjzD\6WwN]lo}YT@U*ڍE7Zj\Zo-ݿrJsRΉ`n]w*SO!%^^:Vm4(NqN-ﯯGrV]Hcin AdgV;h밓q=|}|gnXk|2ɍFhewlHn<.6'3`7luM&GrlU,a:tj>MNa'\^fGrbלv8n9|t7^k{iZ,XhN[/Grzw2ݠ. ,uYXV^1}IjRakzz$^o$v~m6Gr"ݰ.]nYIwDz \S9vGrA10ݰ%<S=GrQ5vZ"-t8'v.uxd=;vZ2NGrjd]*\SS#9ELG$vZ*
]SWU#9UMWVYYiaaZnXgۦi{QV]a78pL`=3a'awX鱜iAװڝDiə트oDvezeY㉤8nXgmٶi ;Iݥ8Gri9b==s=ь솱]uY\q֮ư{N^y^9duvi$|HEzsЊ `=v%%C#9͘$km$둜QъȮIGra%Y;Y]QrQ)X> +IRfI#8Ɋvخiqή$a=K4vO]Y#4+*Ic˗;I]]YO䲼1vEeYQְvͰ(H.ϋ +vsЖEO䊲͎Haw$k>xU#a'ܧ`T\UUv:빮zz$ea'o`ܧv\7~[£vO쮗s}5]vOݟ:S
vvWqۣSu.iVwy7Ͽ}ڿ?oo;B!B!B!B!B! +H +H
0Qְ+Qs@+`䓋 +vG_ +H@ +H z--JRMX49jV&""""""ڟŇSrM'crfG +'3ݞ<SSvA[*^HL+{,[Uw;лƔ0WL9E@}S
T&""""" +Hͱ +tttlllfffaaaZZZTTTOOOJJJDDD???;;;555222---)))!!!
+HyTSwoɞc
[5laQIBHADED2mtFOE.c}088GNg9w߽ + +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'K +x- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r9\A&GrQhE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mDeԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel
}}Cq9 +N')].uJr +wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó tizf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=G</z^^j^ ޡZQB0FX'+t<u-{__ߘ-G,}/Hh8mW2p[AiAN#8$X?AKHI{!7<qWy(!46-aaaW @@`lYĎH,$((Yh7ъb<b*b<~L&Y&9%uMssNpJP%MIJlN<DHJIڐtCj'KwKgC%Nd|ꙪO=%mLuvx:HoL!ȨC&13#s$/Y=OsbsrnsO1v=ˏϟ\h٢#¼oZ<]TUt}`IÒsKV-Y,+>TB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY.=b?SƕƩȺy
چk5%4m7lqlioZlG+Zzmzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś
nLl<9O +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs
2F[p(@Xr4Pm8Ww)Km +H
+}}}tttlll^^^PPPCCC222(((
+H1
+bbb[[[III444///+++###
+ + + +HA
+tttlllPPPJJJ222(((
endstream
endobj
737 0 obj
[/Indexed 735 0 R 121 823 0 R]
endobj
813 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 316>>/Filter/FlateDecode/Height 345/Intent/RelativeColorimetric/Length 604/Name/X/Subtype/Image/Type/XObject/Width 316>>stream
+HA
+}}}{{{yyywwwvvvtttqqqpppnnnlllkkkhhhgggeeebbb___^^^\\\ZZZYYYWWWVVVTTTRRRQQQOOONNNLLLJJJHHHFFFEEECCCBBB@@@???>>><<<:::888777555444333222111///...---,,,+++)))((('''&&&%%%$$$###"""!!!
+ + + +H̡
+rrrkkkWWWPPP???444///+++&&&""" +H1 +|||tttMMM@@@***!!!
endstream
endobj
810 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 316>>/Filter/FlateDecode/Height 345/Intent/RelativeColorimetric/Length 603/Name/X/Subtype/Image/Type/XObject/Width 316>>stream
+H +H1
+kZaVX+ +kZa +~~~wwwpppiiibbb\\\VVVQQQKKKFFF@@@<<<666222---)))%%%!!! + + + +HnFE/ b-Q
Ϋ +Hvv0< @ Ivot".V[ +c$ +Q _?c'I>jޟ?6S<?/EDƟLәL:J&T
|,{>mT&ˋ\_sGe|TB^EHT\_zZ)T$ +К +`<<Q`r| +Tkcs:M:F\) +'*hf9ﻎ5[RN WZsa;
+@ +p$FotA[-S +W\7@:_mgkw<9)_'~=w%P&@c'<rRz)v +@[0_ +Q噤8Eᨩ*0{d@B$6ǻy=#:*'dĹyLe +4{4tCoȟOi + +^ʘ +GL
N$_Y}5 +rR)Z;(s^Ԕɰ Nƣa4 +O?o,o}]0܃quJ +&|՟̌߀/oo/O\gihP +, +R5`9||~<ǘInTY6@HK@IR.`/
vmiLO
N(/@=<==7@TCa?ř_Y8hPT?G0L|b;]ms8Q0'>$ +<I'tP˪zE +Y +k!(rV4M(Xb@끅
4(_Bx%hbfTk*^D_Pub6@4%IE~_[UXb8O!{ +l +?-2Y>}`owzvqz'|o~
+o S\:$I($eR +k5;V#z:ڭ&å|}{A{fXt2mNpa6X$;ՙ{> +{O39O2}+TbB}]k^p<<b9n%:Na#vZgR>/t0u;;wf9slۦm[Te_$d +@}g<E+p +`LWg0N
I> +$h +'oiz26t*^X4B_/.a +_`DgӢςHX6x|}] + +q +/GS0 gs +513 0 0 502 6522.5 7180.5 cm +/Im0 Do +Q +
endstream
endobj
832 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
833 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 513>>/Filter/FlateDecode/Height 502/Intent/RelativeColorimetric/Length 8911/Name/X/Subtype/Image/Type/XObject/Width 513>>stream
+Hvv0< @ Ivot".V[ +c$ +Q _?c'I>jޟ?6S<?/EDƟLәL:J&T
|,{>mT&ˋ\_sGe|TB^EHT\_zZ)T$ +К +`<<Q`r| +Tkcs:M:F\) +'*hf9ﻎ5[RN WZsa;
+@ +p$FotA[-S +W\7@:_mgkw<9)_'~=w%P&@c'<rRz)v +@[0_ +Q噤8Eᨩ*0{d@B$6ǻy=#:*'dĹyLe +4{4tCoȟOi + +^ʘ +GL
N$_Y}5 +rR)Z;(s^Ԕɰ Nƣa4 +O?o,o}]0܃quJ +&|՟̌߀/oo/O\gihP +, +R5`9||~<ǘInTY6@HK@IR.`/
vmiLO
N(/@=<==7@TCa?ř_Y8hPT?G0L|b;]ms8Q0'>$ +<I'tP˪zE +Y +k!(rV4M(Xb@끅
4(_Bx%hbfTk*^D_Pub6@4%IE~_[UXb8O!{ +l +?-2Y>}`owzvqz'|o~
+o S\:$I($eR +k5;V#z:ڭ&å|}{A{fXt2mNpa6X$;ՙ{> +{O39O2}+TbB}]k^p<<b9n%:Na#vZgR>/t0u;;wf9slۦm[Te_$d +@}g<E+p +`LWg0N
I> +$h +'oiz26t*^X4B_/.a +_`DgӢςHX6x|}] +HAnVDA䖚OfTĖoo + +MH'Bz .5.oŮoy/tO~[~F=}o|u3&j]p[]ï҈7`x 7̶a#w_}T=rqg>!pU=iQEA-Q}L&t&t˚>Sd$kYugIV]YUaeuUUuEJ:ªα4SZimV]ڬ!k'Y+Pթ^1Utϯ UŦzUYRd$k'U+INvҵd$k'];ZJNvd$k)Y;INvUuǪʺ Y+ZINVeuǚʺMuwZINVt$k%k'];IRvҵd$kSYu\UYӵNfu8Y+];INVvd-%k'];IRvҵtk)]KZJNvtk'Y+];IJvҵdk']+INvҵtk'];INVz6{tlV]tV]w|V]7r 8Umʪ.eu꺇Yu݃guvҵtt8[еtk'];INvҵtk'];INvd$k)Y;IRvҳd-%k+Y[JU +HviEV$9TE3Bm?̜U!px<=dx l{B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!(|8|4*?3yts"V\!nt~xPN? Jťpqg6O˫kq}}uya%$w^;.r9aʥz$K3hu5WRR%egёBK`0=nK_k<;%pk)jz bp'b/d^Y >81k.*Wǒt:@Z)ztA!<(|خ.?OgB.u)b/NG|(A, +"}o~%'Èk(Η*F\$~NW*[7t&H!yI=S/vzdlju٢JkVbT*҉H+wQUE/U̙ao0)UNY-gaCZ#1Z,fZ5t\U}G+EJ_1}dhtz^iRbVh2jVըV/S˨ (h]Dϥ:44]{Ѱ0B SB٨5Z^mլ8;.a2Nsʧ%aPM^v6YYX'ɰ[/g`5KC1j֛p<
nf=j<%q\Z>S|vT5g|65+8E`4-Tw}8dzg!@=?U9&A
G衉D*\«J`X'|#S8FrZikk={Cva|')\.+ET5,ֺ#[L±T\mvzllnWixn5&[{
ղ*RT8
Z>dR|fsRBP.楈SERxZ/gk؏lĕ<̗vDM;Jꋋ7|Jzy? +#*P[(:hh6jID^O揤KdF mSMJ^v\MDnܪdehB5e-h<ΈbDhJ]F8=hOW:lD8I\K6.֛aw[&'g*y!.sWn$5UDbbVZހѶd2qze+l٬d$Ry3b_T`F1J"aՖ\ݙxDH6֥BJ*VZt:]`KWY,l<C`,k^k4F$ty+a}~y}}}~a:
\fRIHX:dox"B*j;5 +ehu{pda],Wrmnת ~lIM-j!#x!Z^^?V^mJe3T2L=nam'ۉ? +~]5D/Wi9_V?U^l#~7l*kKsXVETJ~jifVc{uM(}<Y/C۬=LNdmVBdQrV֒˯lUTeb\Kb[*@Q#BǶ(詓*3k
aUb:굪&ǣ&ɨi"{RltQ^ +2_3~Cj90YU~ױoVe8HcDG"lj#͢z +U07~1Fr'( +ىa ; aի\*8mv +6/waVˌ<`jOX,Z>l1r$"AvN +JR-V*^lҷTw'jkI9{YG泩X~cû}X*WV,P91daZy +8,ޡmݡ[GTT!}vFF4Ɍ'32WIBmԪ$
֫+(5ĥ@۠<%Ѕ+2tfK'/NSXCNZT!E,#OQzB0g-
TmT5N%Ѱ^#K堒%4ŢX<A +S0ue31`ŜkըӱF
{ +XL2Kk7"Q%֥ȥVP^Vc< AYE.Z#H8 +P$Z,RZ3znZ0yCé猅%aݨ2ŀ=qTŒTw;&VQ+ul4z
(+e^TDju_[/-{Wpy>^ʖ\ +W&G:z}M&\dnh@[᪸Z)[SY-R9ѧ*qF G+6يZe`PsFڥfR^8.NYC!)ҀUMA%3ڴr=L] +z8sRk$@jz +ƹ"QH+حHץpUT.mifiz}}@ʪ$PĎ%l' W)an@dJ.-|: \v)^s2. +h;
UAݬf+5{IZ^^eOT2Մjva{|Ņ\/"LTk0r(0avho&UzLG64)RpȞS7#%OsS ^{Zy0x*tp`(bWԱo,Ei$XӹD C1ݭ6Z\5MzWe/ϳDm\_qfAQWa/,s~0EC`#=nJKU^)i@
ҩ]|ABFU<xQB*2ZT.$V]ݻdUIl*4Ɠ Z&Q`t6cPb|&*j~'z|GU%+]a0l"NBNa +jcKZ504 +T^]LS5xr,Ɓc}rUɪZ,!6x[.3q]A%R2U}=3=9$sьѠOXMnZ5]\WXcнmpvlT
oQդ>'j/N{LڃeJVAcK߁\>aWɮDs9T_/TA`;C"-R-M\*9S %U*k?M^mV˶ .Knn'sL:KWkj'0H%Xz#jsxx:_Utrb:pd"έJXQMM9+ D~ڠh:wZJ!э[ +_%~|W<LĚ̕mP%z[Hud`vZj1T_ogG+uN倵q;V~eD4RU
H<3}VKfg8]6v%l<wo[7:k6.eo*^Q^ +8tZy%Zk4Hk/V<vkۘOvV5MT$^_ 3hKۃ1ǯoƆ +VKY5O[Ot߮ǭcFVEEV"2d|BRpSout6ە
kFfU(JĖJ*M,xA#k%tNFҴz6*Q!E L&u8`D!Bm½x:[,͕Nts64ʪ,9<DVRI8ɰT}|4Lv7~`;Z8&Kg,C$ׯ'u</
4kYI]f3id! Kz
7D +V&ղ,jM4pƤ)P4J&T:C3l.c?kPg0 ++ܭyܭܰlǶWsXx +ZU]a&p2"x~#'d:B_k* +0A( @^n#RfA-h
\Эu=ϗy%+c_/X
NR/^{{z^ֵY%fMo;>2r9f|^uЮH+_}1TxUHq\Gzf0qH>+_wJZg4[X#rkVԂwu)dٟp|{1j/ ltbdΉj=ZጭB{MN]W,Z 'ԍ4E|Aw'=^_^.ƽF(rwd5RfiDDؚ`w+ +պnk1*3Th
2)frp>_P+coVKZ v2LN%YVdPpfzEZmvMrDkЭ墦+iV5ZONʘz-Њ5%ZkBjzn[F^o:ttCk"ޙ`'/PB\ټU[h<~FӅl~ZL*Np[S"$f4b>L&Srvj%pPz;z٬]{e(l?pV!,UPMVomzo +ap<Bztخms>aB`KgwzlqqdЮE5\ ~C`EoWXکO[`X3Na2栵QV%%Z +,Y^*UÙi:]]2Q]/ӉXh
%I,kplqVNh
!X4^&a,ɨm7j"!8*"~`asb\Gt2;zU/)<k}"ZDT%\oV+ +V\6>!iXBĶ:n'0lDkVA+xEDT5&`[Mae"ZÃUd5R*WkZEG~XЀId*ar岯AzDkH+HM$T*Me,"t+ރcODkkEAMS$+ŢRa +p7d +tfe +Cqךq\eˀX!/ycq%Z +G+E3HV4E0iMi +UQdnX+kx`N EYGҷ_xZS{\ժeM%Z3J|_YNTJj5UeQTjh
TURzvbE8q_sIq,Q]UxF rH jY\ w7D|' ﮠS=R13-2P1Fh,N3n^ [-!XcFD2LRk~+%P`8(6ЩT*L|JMv/{a3^^.dSWVԩ|X*˕Z~&] ,g۬sןd~!"ЩJh4v?b˭t]S,7Z)ND^]1PX^%ix2_{E;v5z9Ah"
1MdX
˶-CSn26k<&1CPt6gYnIt:9QE5B'lS9\7~<1ک_$bgjv{Q a=_ΞcDcZ1 _zdqT-=_|_7+_oӟΜdXm6iܳWV3b8Fb=|٣&ZѺzz]/K[Y_S9T|E`z߮yدĮx /:фоAX9GYXL]J~{ qV~|||m3V80waCagTwΗnqK<;!F]qӬx2xSBPPw] ﯠZFcd:[(_i&hW+os]P1GcD*Jfwpa_`0tԩv%vAG8勥Jg^9AܰtYJ슃5/+zZrSx]@]"]*VjVŘ[oEU7y.`Ռ6]qp\m{ј̹Fhئ4*4+z5+՚L
Y7dةD5)T]U6*np̣rJ|]]^dXk[Z;7* 9=hUD슅\!!3_;@* Gz)G^vA˕]m]2, `~h o.zzf+Vױy/!vF_S٣Nz}!a=a铉~:vaW +n{~\osrH㤗Kfp؆\?>׳K<;z%1돟ȰB
\蜯'͜PMKA1Npl& +9gGwQOOjk?2R*Wr3ffyI`]ZφJP$qӃk*_nj_xJYR8*9TTŃy.1-GL^D^~s|%Ⱥ鸮cjfNW!ox*[6{tUݴmSWd*zR/?~&92Jaȳt/\? WX2+!|:U^.ވ;IQUy~b++fz5vԄ]m"ؕ7 vE|DYQ=|\]J3OXHaQVUEڭY+ W0<2:۽>0 +b=ʕ=C/aH(oDPmdzVTuòmda9*abڵTRV:ܓZ9$ap<Nj.ںstkÄ+Vzp֡fj8]/K9;WpB7z4K^7Zj:lzS//h$sfYleW@_oS +qܳgҤ
WR^\%t\읬C1\͓zR{գi;mUa\+B\#z'iAV3?rH@Ѣs9$HsmY51
Pvf5՚{6ҒUf9yUOr^Nr:Kyz3k<[|>DԊ*fNx:n=7Z!MW%9HBtU:Ui*FROt\a{JrnTFR_n"3Tmp|z~~z;Gk'/>r
ˏorotU*qpo</^^^_U&誖Ϭh"ct+tUNֈRO÷y;rnY2k"/7|ݺn]`/aYӹbֺ>xMΫDWu80,R|}8?ۯCWE|<px2+Vw06ۣ~ihb:y>"t}|~~~ZٰSͧAGDW9X2+DF
d:_k59G2kVo4[no02zw8Za2z1cMtUx*[l֗av۬f~Ka&u؟WHkT7Nj>jZ(5lRƭk,pG9}dLgb1zj!۟W*>X*'⻪3OD|65tYɧb!>]O*Xc;Btr.BDXkixbf3Ӝ)r\Ȯz)DWE]DTSrZVZr^k4+7ylok{ +ZiFr;ÇnO]UsWZ}cdYvotUCk=0t: +y>*ՒUUkx-&y'"jZRy1m6z0֪2quٵ?6kyo֫b3s2{z9>]5_mFӹ|.dbx4vR$!MWU|v45}<5gё>z=MӺVV.dr6)*ޯzČ:fhj\̉V*ޯl*FR.B!fRX8Ǖ/Id +zIWJQ3-Rd"U5.םSb۷V)iY3 +H8%쾣:DWW|`|\VŬ +KV8]x2+/j.GáQ=nɪp +d:uHH[D#bnIy& +v.FvH4ć5hgub~&[@0o;*Nta}~~/Wҗp~II*v{Dۓ_+fqG֯M + +q +/GS0 gs +472 0 0 526 6485.5 7240.5 cm +/Im0 Do +Q +
endstream
endobj
838 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
839 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 472>>/Filter/FlateDecode/Height 526/Intent/RelativeColorimetric/Length 11417/Name/X/Subtype/Image/Type/XObject/Width 472>>stream
+HviEV$9TE3Bm?̜U!px<=dx l{B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!(|8|4*?3yts"V\!nt~xPN? Jťpqg6O˫kq}}uya%$w^;.r9aʥz$K3hu5WRR%egёBK`0=nK_k<;%pk)jz bp'b/d^Y >81k.*Wǒt:@Z)ztA!<(|خ.?OgB.u)b/NG|(A, +"}o~%'Èk(Η*F\$~NW*[7t&H!yI=S/vzdlju٢JkVbT*҉H+wQUE/U̙ao0)UNY-gaCZ#1Z,fZ5t\U}G+EJ_1}dhtz^iRbVh2jVըV/S˨ (h]Dϥ:44]{Ѱ0B SB٨5Z^mլ8;.a2Nsʧ%aPM^v6YYX'ɰ[/g`5KC1j֛p<
nf=j<%q\Z>S|vT5g|65+8E`4-Tw}8dzg!@=?U9&A
G衉D*\«J`X'|#S8FrZikk={Cva|')\.+ET5,ֺ#[L±T\mvzllnWixn5&[{
ղ*RT8
Z>dR|fsRBP.楈SERxZ/gk؏lĕ<̗vDM;Jꋋ7|Jzy? +#*P[(:hh6jID^O揤KdF mSMJ^v\MDnܪdehB5e-h<ΈbDhJ]F8=hOW:lD8I\K6.֛aw[&'g*y!.sWn$5UDbbVZހѶd2qze+l٬d$Ry3b_T`F1J"aՖ\ݙxDH6֥BJ*VZt:]`KWY,l<C`,k^k4F$ty+a}~y}}}~a:
\fRIHX:dox"B*j;5 +ehu{pda],Wrmnת ~lIM-j!#x!Z^^?V^mJe3T2L=nam'ۉ? +~]5D/Wi9_V?U^l#~7l*kKsXVETJ~jifVc{uM(}<Y/C۬=LNdmVBdQrV֒˯lUTeb\Kb[*@Q#BǶ(詓*3k
aUb:굪&ǣ&ɨi"{RltQ^ +2_3~Cj90YU~ױoVe8HcDG"lj#͢z +U07~1Fr'( +ىa ; aի\*8mv +6/waVˌ<`jOX,Z>l1r$"AvN +JR-V*^lҷTw'jkI9{YG泩X~cû}X*WV,P91daZy +8,ޡmݡ[GTT!}vFF4Ɍ'32WIBmԪ$
֫+(5ĥ@۠<%Ѕ+2tfK'/NSXCNZT!E,#OQzB0g-
TmT5N%Ѱ^#K堒%4ŢX<A +S0ue31`ŜkըӱF
{ +XL2Kk7"Q%֥ȥVP^Vc< AYE.Z#H8 +P$Z,RZ3znZ0yCé猅%aݨ2ŀ=qTŒTw;&VQ+ul4z
(+e^TDju_[/-{Wpy>^ʖ\ +W&G:z}M&\dnh@[᪸Z)[SY-R9ѧ*qF G+6يZe`PsFڥfR^8.NYC!)ҀUMA%3ڴr=L] +z8sRk$@jz +ƹ"QH+حHץpUT.mifiz}}@ʪ$PĎ%l' W)an@dJ.-|: \v)^s2. +h;
UAݬf+5{IZ^^eOT2Մjva{|Ņ\/"LTk0r(0avho&UzLG64)RpȞS7#%OsS ^{Zy0x*tp`(bWԱo,Ei$XӹD C1ݭ6Z\5MzWe/ϳDm\_qfAQWa/,s~0EC`#=nJKU^)i@
ҩ]|ABFU<xQB*2ZT.$V]ݻdUIl*4Ɠ Z&Q`t6cPb|&*j~'z|GU%+]a0l"NBNa +jcKZ504 +T^]LS5xr,Ɓc}rUɪZ,!6x[.3q]A%R2U}=3=9$sьѠOXMnZ5]\WXcнmpvlT
oQդ>'j/N{LڃeJVAcK߁\>aWɮDs9T_/TA`;C"-R-M\*9S %U*k?M^mV˶ .Knn'sL:KWkj'0H%Xz#jsxx:_Utrb:pd"έJXQMM9+ D~ڠh:wZJ!э[ +_%~|W<LĚ̕mP%z[Hud`vZj1T_ogG+uN倵q;V~eD4RU
H<3}VKfg8]6v%l<wo[7:k6.eo*^Q^ +8tZy%Zk4Hk/V<vkۘOvV5MT$^_ 3hKۃ1ǯoƆ +VKY5O[Ot߮ǭcFVEEV"2d|BRpSout6ە
kFfU(JĖJ*M,xA#k%tNFҴz6*Q!E L&u8`D!Bm½x:[,͕Nts64ʪ,9<DVRI8ɰT}|4Lv7~`;Z8&Kg,C$ׯ'u</
4kYI]f3id! Kz
7D +V&ղ,jM4pƤ)P4J&T:C3l.c?kPg0 ++ܭyܭܰlǶWsXx +ZU]a&p2"x~#'d:B_k* +0A( @^n#RfA-h
\Эu=ϗy%+c_/X
NR/^{{z^ֵY%fMo;>2r9f|^uЮH+_}1TxUHq\Gzf0qH>+_wJZg4[X#rkVԂwu)dٟp|{1j/ ltbdΉj=ZጭB{MN]W,Z 'ԍ4E|Aw'=^_^.ƽF(rwd5RfiDDؚ`w+ +պnk1*3Th
2)frp>_P+coVKZ v2LN%YVdPpfzEZmvMrDkЭ墦+iV5ZONʘz-Њ5%ZkBjzn[F^o:ttCk"ޙ`'/PB\ټU[h<~FӅl~ZL*Np[S"$f4b>L&Srvj%pPz;z٬]{e(l?pV!,UPMVomzo +ap<Bztخms>aB`KgwzlqqdЮE5\ ~C`EoWXکO[`X3Na2栵QV%%Z +,Y^*UÙi:]]2Q]/ӉXh
%I,kplqVNh
!X4^&a,ɨm7j"!8*"~`asb\Gt2;zU/)<k}"ZDT%\oV+ +V\6>!iXBĶ:n'0lDkVA+xEDT5&`[Mae"ZÃUd5R*WkZEG~XЀId*ar岯AzDkH+HM$T*Me,"t+ރcODkkEAMS$+ŢRa +p7d +tfe +Cqךq\eˀX!/ycq%Z +G+E3HV4E0iMi +UQdnX+kx`N EYGҷ_xZS{\ժeM%Z3J|_YNTJj5UeQTjh
TURzvbE8q_sIq,Q]UxF rH jY\ w7D|' ﮠS=R13-2P1Fh,N3n^ [-!XcFD2LRk~+%P`8(6ЩT*L|JMv/{a3^^.dSWVԩ|X*˕Z~&] ,g۬sןd~!"ЩJh4v?b˭t]S,7Z)ND^]1PX^%ix2_{E;v5z9Ah"
1MdX
˶-CSn26k<&1CPt6gYnIt:9QE5B'lS9\7~<1ک_$bgjv{Q a=_ΞcDcZ1 _zdqT-=_|_7+_oӟΜdXm6iܳWV3b8Fb=|٣&ZѺzz]/K[Y_S9T|E`z߮yدĮx /:фоAX9GYXL]J~{ qV~|||m3V80waCagTwΗnqK<;!F]qӬx2xSBPPw] ﯠZFcd:[(_i&hW+os]P1GcD*Jfwpa_`0tԩv%vAG8勥Jg^9AܰtYJ슃5/+zZrSx]@]"]*VjVŘ[oEU7y.`Ռ6]qp\m{ј̹Fhئ4*4+z5+՚L
Y7dةD5)T]U6*np̣rJ|]]^dXk[Z;7* 9=hUD슅\!!3_;@* Gz)G^vA˕]m]2, `~h o.zzf+Vױy/!vF_S٣Nz}!a=a铉~:vaW +n{~\osrH㤗Kfp؆\?>׳K<;z%1돟ȰB
\蜯'͜PMKA1Npl& +9gGwQOOjk?2R*Wr3ffyI`]ZφJP$qӃk*_nj_xJYR8*9TTŃy.1-GL^D^~s|%Ⱥ鸮cjfNW!ox*[6{tUݴmSWd*zR/?~&92Jaȳt/\? WX2+!|:U^.ވ;IQUy~b++fz5vԄ]m"ؕ7 vE|DYQ=|\]J3OXHaQVUEڭY+ W0<2:۽>0 +b=ʕ=C/aH(oDPmdzVTuòmda9*abڵTRV:ܓZ9$ap<Nj.ںstkÄ+Vzp֡fj8]/K9;WpB7z4K^7Zj:lzS//h$sfYleW@_oS +qܳgҤ
WR^\%t\읬C1\͓zR{գi;mUa\+B\#z'iAV3?rH@Ѣs9$HsmY51
Pvf5՚{6ҒUf9yUOr^Nr:Kyz3k<[|>DԊ*fNx:n=7Z!MW%9HBtU:Ui*FROt\a{JrnTFR_n"3Tmp|z~~z;Gk'/>r
ˏorotU*qpo</^^^_U&誖Ϭh"ct+tUNֈRO÷y;rnY2k"/7|ݺn]`/aYӹbֺ>xMΫDWu80,R|}8?ۯCWE|<px2+Vw06ۣ~ihb:y>"t}|~~~ZٰSͧAGDW9X2+DF
d:_k59G2kVo4[no02zw8Za2z1cMtUx*[l֗av۬f~Ka&u؟WHkT7Nj>jZ(5lRƭk,pG9}dLgb1zj!۟W*>X*'⻪3OD|65tYɧb!>]O*Xc;Btr.BDXkixbf3Ӝ)r\Ȯz)DWE]DTSrZVZr^k4+7ylok{ +ZiFr;ÇnO]UsWZ}cdYvotUCk=0t: +y>*ՒUUkx-&y'"jZRy1m6z0֪2quٵ?6kyo֫b3s2{z9>]5_mFӹ|.dbx4vR$!MWU|v45}<5gё>z=MӺVV.dr6)*ޯzČ:fhj\̉V*ޯl*FR.B!fRX8Ǖ/Id +zIWJQ3-Rd"U5.םSb۷V)iY3 +H8%쾣:DWW|`|\VŬ +KV8]x2+/j.GáQ=nɪp +d:uHH[D#bnIy& +v.FvH4ć5hgub~&[@0o;*Nta}~~/Wҗp~II*v{Dۓ_+fqG֯M + +HN@i@RrM;'yY{4e1c<O>flc]DZT+A6L65)'|5&`HԂi#j + _=!dMJ<i橫vJ4s\4v>g +
esӸT>U4g8 mf_,'p3Lk̚uԆͬN)>nu< AOK XKvO=ѣKvyCO+(4QU=EyzBO^+= e)īVS쉖\3<kTO,=S'M=MĻ&{JdOW穧({bÞ6G)STZX7OT\O0(*c (jZZ45QIph']ǀ.gݥ-zPh8G3(`vgCkHڻd|ex8!<c1c1c̛! +HWcCz +()RT`&ƶ4ƨIxo'IZ>3{^u{ש tU'IM)4_QKUX +JUE1@t:J!#T-7OR0eP&fsq\f1TJm@4߮J&BH$HRL&J%"âSɤ'pp6q +aP7 +0(,q~Pک6O$Sj.bZm6ۭ]fV)r4r[狤*L7U͗(6@ yLE ިp; +h2pqiUDGbx"L'Hm3(B!3ܫ'VL鰚
*M'_&Wljv2!ƇSD.ShnRK,j{k˕ 5\ +#Rn8-8UPݯ4TRtPrTvrzXZZ\(&RfP8trべvRTc{h8su<
k&կAET+Qp239;hcsk{gwwwg{ɣɱS#1\|~"hlBoF#iHx8Qdhy_h2'L6vONN?\_ˍ{m +J8RiXN9GG'fWo?uӗo߾|zwhR1F c19|1$V%Xt +n?HmvшעraCpnb6"Q\5t@Ǝk;N4Ha3Sh/_>szXL2(=6-Brf8AˤW|v=fJ(M <rNLBڀA/ +դS(qe*a4T2I'9A&,n8(6_6WӉթysЦQr.^oRSJtFe l8箁19huB!K`8uvb4A6tX=>/b) %eC'Qm=tnz~u۷7Wʅhlb.+mhu'Da8:* U3H027:ʉpMf\."Sogu1
.YOrL5ٺ}}Tyu듃T&
F6l*@79K$!5Df0OxwKN8:ٽLT*M3Ɉ:+VvX{Dj|"7ͤhMTjZ=%J s_$.<|{ųt<qTLs`Dcdn<]:]o3AWO"39==9Dx~S~LmZ]]]^(<D4WYX-2Ã!äҮZƟ {D1-@b|f{Of2^SjY!aoN$"`RIx``k8vԅlSt6*wbK)lЙ入奅9.TxIw{AUxxnV?2/>^+G>'4BZVA&XA= +%sb>Vdw #oﺠƑXc&s77dЮkOa`"j6QX:_TJ$
3o^s2:ʦW +Ɔ=O*vـy*f b9Fw7uʠu<1$ㄠ)(@v>S Kmu$qXL%4](WjR65̼p9<^p!:X`b#)7ڏ%2(DN\6C;al' +Hٵ}ѥ-̓yR/%u/ή?9Ufxk%GI\Wc<3G52;Z_u7Kg҉ۢe'F]*)E3Im& +״]Id<t]Tcxx[k߷jLҟl-N}b#D'Hfōx&6O
xvR!JD @ierR): 1 ;5(Ntȩ&S]~h7+Ϭbdg&WP2%#9ɍD88U˅u<؍ý]t< BgL8gBcpTJv?3!Blp2WfX TyDztmHd!btm1Yh`Ͼ`,!9!lKhV+ĽF9UĮ.'3JMʵc6v$(xGgҡǦ.YNd7w,orQqZ2>q<{Zn0}lxzy\wJЉz]N̗SPF.M`ٛJׯj>6A-MsG.r +>(B'K_̸Iu0Ƨ:.
}0Fs+ +1^݄Gil̜]+R_/K0(lP.h؏eDd"FIJ%0NnŨ a#N}1L(;H:h~~ylSW6*`(x|=ÎWءS*EN<CsyhQGzR6!sHe0(ۀ<`p"ĄU/oZ3ݬ⍦!4[UL&z 4TNF(@IEp +ٶ|s
4>u,RhfIv&G&ycL՛^w)n+i8L +^4},RdCR&fNy}>Mv+)0/d
{<RU+vدč r0Љ}_xN%d ϟ*UWFb:Zw4_?>}xz1mk$ѹ[lmB]9vE˟?ÚWdl_^^_v5g[r٬fL~@I.ڃv^.ȐqFroq +uw|Z_аC-J"ݖ.;8c9ѽW;0fs^-aQ%8c7Rj?ӋAVr:1('_ivry9 2)Cq:qZRHdo3;pr\}]ZLGVtxX^-NZ&
o Y@q{=Rb
FѰj\ct!\nTP8dN"nK5t;$vhױt:l<j\op0NլW\Ƙr%rJ^UDNIjvr@j +cHcBfH4c2{d4s
TFp,F(#v"> + +SbGR +yV|8zr](lG9; +î.Tq 't:J=E\o:/40 +q +/GS0 gs +296 0 0 91 6647.5 7516.5 cm +/Im0 Do +Q +
endstream
endobj
844 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
845 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 296>>/Filter/FlateDecode/Height 91/Intent/RelativeColorimetric/Length 6141/Name/X/Subtype/Image/Type/XObject/Width 296>>stream
+HWcCz +()RT`&ƶ4ƨIxo'IZ>3{^u{ש tU'IM)4_QKUX +JUE1@t:J!#T-7OR0eP&fsq\f1TJm@4߮J&BH$HRL&J%"âSɤ'pp6q +aP7 +0(,q~Pک6O$Sj.bZm6ۭ]fV)r4r[狤*L7U͗(6@ yLE ިp; +h2pqiUDGbx"L'Hm3(B!3ܫ'VL鰚
*M'_&Wljv2!ƇSD.ShnRK,j{k˕ 5\ +#Rn8-8UPݯ4TRtPrTvrzXZZ\(&RfP8trべvRTc{h8su<
k&կAET+Qp239;hcsk{gwwwg{ɣɱS#1\|~"hlBoF#iHx8Qdhy_h2'L6vONN?\_ˍ{m +J8RiXN9GG'fWo?uӗo߾|zwhR1F c19|1$V%Xt +n?HmvшעraCpnb6"Q\5t@Ǝk;N4Ha3Sh/_>szXL2(=6-Brf8AˤW|v=fJ(M <rNLBڀA/ +դS(qe*a4T2I'9A&,n8(6_6WӉթysЦQr.^oRSJtFe l8箁19huB!K`8uvb4A6tX=>/b) %eC'Qm=tnz~u۷7Wʅhlb.+mhu'Da8:* U3H027:ʉpMf\."Sogu1
.YOrL5ٺ}}Tyu듃T&
F6l*@79K$!5Df0OxwKN8:ٽLT*M3Ɉ:+VvX{Dj|"7ͤhMTjZ=%J s_$.<|{ųt<qTLs`Dcdn<]:]o3AWO"39==9Dx~S~LmZ]]]^(<D4WYX-2Ã!äҮZƟ {D1-@b|f{Of2^SjY!aoN$"`RIx``k8vԅlSt6*wbK)lЙ入奅9.TxIw{AUxxnV?2/>^+G>'4BZVA&XA= +%sb>Vdw #oﺠƑXc&s77dЮkOa`"j6QX:_TJ$
3o^s2:ʦW +Ɔ=O*vـy*f b9Fw7uʠu<1$ㄠ)(@v>S Kmu$qXL%4](WjR65̼p9<^p!:X`b#)7ڏ%2(DN\6C;al' +Hٵ}ѥ-̓yR/%u/ή?9Ufxk%GI\Wc<3G52;Z_u7Kg҉ۢe'F]*)E3Im& +״]Id<t]Tcxx[k߷jLҟl-N}b#D'Hfōx&6O
xvR!JD @ierR): 1 ;5(Ntȩ&S]~h7+Ϭbdg&WP2%#9ɍD88U˅u<؍ý]t< BgL8gBcpTJv?3!Blp2WfX TyDztmHd!btm1Yh`Ͼ`,!9!lKhV+ĽF9UĮ.'3JMʵc6v$(xGgҡǦ.YNd7w,orQqZ2>q<{Zn0}lxzy\wJЉz]N̗SPF.M`ٛJׯj>6A-MsG.r +>(B'K_̸Iu0Ƨ:.
}0Fs+ +1^݄Gil̜]+R_/K0(lP.h؏eDd"FIJ%0NnŨ a#N}1L(;H:h~~ylSW6*`(x|=ÎWءS*EN<CsyhQGzR6!sHe0(ۀ<`p"ĄU/oZ3ݬ⍦!4[UL&z 4TNF(@IEp +ٶ|s
4>u,RhfIv&G&ycL՛^w)n+i8L +^4},RdCR&fNy}>Mv+)0/d
{<RU+vدč r0Љ}_xN%d ϟ*UWFb:Zw4_?>}xz1mk$ѹ[lmB]9vE˟?ÚWdl_^^_v5g[r٬fL~@I.ڃv^.ȐqFroq +uw|Z_аC-J"ݖ.;8c9ѽW;0fs^-aQ%8c7Rj?ӋAVr:1('_ivry9 2)Cq:qZRHdo3;pr\}]ZLGVtxX^-NZ&
o Y@q{=Rb
FѰj\ct!\nTP8dN"nK5t;$vhױt:l<j\op0NլW\Ƙr%rJ^UDNIjvr@j +cHcBfH4c2{d4s
TFp,F(#v"> + +SbGR +yV|8zr](lG9; +î.Tq 't:J=E\o:/40 +H @MM}ZÄ +f;q!!#ebH8R#eH9R#H9R#H9R#H9R#5Ƒp4Xr0Xruϯ\yOM,#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#H9R#!H9R,#!!bb7so +H{Lg=PxB)VZ" +"iTj a+a5`R7 x"8 Cˈ8E$H!)'%/徯y>{lDZAheA&?cZ+0337701i{B1P{}X[<IܘC'`kڊawc@V-ށH80?G=N#|Id͕`0L ݕB!6 |Ґ,! v$ +t`s<=LOOÝIRHv%@B$S,/|f|?_Š@%FkhL7 Cvm" <o6DD,KBapPiX,2*:ƄEICŁ|.E#Y6b
E G(MّÊ?älChFHab,N<z,%-=#ӄHOK9vTI\6B!Y0ֈ,oH%WҳO9V3YgNd(͢<la5b!ؒl^D&OJ+T|g4VVzRA^Nj\& d[8azq\uQion^^Z;U7VYZMUƅ^L06FbYۻ<VW\Up~i-߿oֵbu` +%чRN_o + A0gǞ_!Mq3m隦k)2t4"fzЋ1E"8$*Ԃ"^Eu7^ftQ"&y~SiV9O<o=o WΟ0 7w5G/8<lln3ᔌsJs3Rtp={|3q@hHzNrD.;Ksҏ#C64~Q_QC"I"%;)DEw^/H-%:KNoxmO'- E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QFdD~/"H"2[ԵHO[ko%.to&x21q >^8;d%k)kqu,,i]2O//̌wZꊅ^~"~`]T|oڇ<ˑ;͵Eif>X'GSK;{O-[}ޯ[z>yRTZ,ETBzfg3oWV=ff㎛MՅ'BD%CqIU7>}jfn~af^|tFcUxRىx4X1ۙݽCc/&gf_{jvfzjPow3cXF-3&<&ђWlua/'&=51ѡn5;,1D|AaQ 9kC#OƤ;q`jGNFjBTXWV"^Hj/w^qo_K}us],Ϸ[HmD!8%3kvtvuǤk]wo]x07bӇ(d&"Z5:>j+t64_qS.ם7o\mnpV٬)*q*ec0MW;.5zRSc0Ϟ~dRV_[:XmLreU5uRNL\(s-Ih]_V{#CO:jʹ)(txQXp&/Ǟi=#"mĽ!h#b5#f?y*ve:ieeX-)&cLV1"rF +KF 6ϝb6nKYF^bNA0`6y~Yƾ +q +/GS0 gs +274 0 0 303 6622.1162109 7246.1401367 cm +/Im0 Do +Q +
endstream
endobj
850 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
851 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 274>>/Filter/FlateDecode/Height 303/Intent/RelativeColorimetric/Length 4305/Name/X/Subtype/Image/Type/XObject/Width 274>>stream
+H{Lg=PxB)VZ" +"iTj a+a5`R7 x"8 Cˈ8E$H!)'%/徯y>{lDZAheA&?cZ+0337701i{B1P{}X[<IܘC'`kڊawc@V-ށH80?G=N#|Id͕`0L ݕB!6 |Ґ,! v$ +t`s<=LOOÝIRHv%@B$S,/|f|?_Š@%FkhL7 Cvm" <o6DD,KBapPiX,2*:ƄEICŁ|.E#Y6b
E G(MّÊ?älChFHab,N<z,%-=#ӄHOK9vTI\6B!Y0ֈ,oH%WҳO9V3YgNd(͢<la5b!ؒl^D&OJ+T|g4VVzRA^Nj\& d[8azq\uQion^^Z;U7VYZMUƅ^L06FbYۻ<VW\Up~i-߿oֵbu` +%чRN_o + A0gǞ_!Mq3m隦k)2t4"fzЋ1E"8$*Ԃ"^Eu7^ftQ"&y~SiV9O<o=o WΟ0 7w5G/8<lln3ᔌsJs3Rtp={|3q@hHzNrD.;Ksҏ#C64~Q_QC"I"%;)DEw^/H-%:KNoxmO'- E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QF"E0`(QFdD~/"H"2[ԵHO[ko%.to&x21q >^8;d%k)kqu,,i]2O//̌wZꊅ^~"~`]T|oڇ<ˑ;͵Eif>X'GSK;{O-[}ޯ[z>yRTZ,ETBzfg3oWV=ff㎛MՅ'BD%CqIU7>}jfn~af^|tFcUxRىx4X1ۙݽCc/&gf_{jvfzjPow3cXF-3&<&ђWlua/'&=51ѡn5;,1D|AaQ 9kC#OƤ;q`jGNFjBTXWV"^Hj/w^qo_K}us],Ϸ[HmD!8%3kvtvuǤk]wo]x07bӇ(d&"Z5:>j+t64_qS.ם7o\mnpV٬)*q*ec0MW;.5zRSc0Ϟ~dRV_[:XmLreU5uRNL\(s-Ih]_V{#CO:jʹ)(txQXp&/Ǟi=#"mĽ!h#b5#f?y*ve:ieeX-)&cLV1"rF +KF 6ϝb6nKYF^bNA0`6y~Yƾ +Hױ
0CAfDa$M@ :ߔ())-JAҢ()-JҢ(-JҢ(-JҢO--JK"ƒ-=Ϲ88MCkܗ8ǯ +HkY +A.W(l1cR@ +` +hco5j +EOqiڇjV?)?ch_u6PL'x")y\Y~OcuL!?&х=5prbޓO_})@߾n|Z_}W~rtq@t.g?ogNm~~k5o +=Bϻ,CX#R/di+kwz:yGwzvJ +lw-vdZ=E3vfw8m\)ث C26`A6( LJu +XІZՑ`@$uZC
.pL
U*ZM RMҨb2\P(k`ĔC{KS
A%hS +E쫢ncKv
>Ф60 +q +/GS0 gs +268 0 0 44 6625.1162109 7475.1401367 cm +/Im0 Do +Q +
endstream
endobj
856 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
857 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 268>>/Filter/FlateDecode/Height 44/Intent/RelativeColorimetric/Length 1219/Name/X/Subtype/Image/Type/XObject/Width 268>>stream
+HkY +A.W(l1cR@ +` +hco5j +EOqiڇjV?)?ch_u6PL'x")y\Y~OcuL!?&х=5prbޓO_})@߾n|Z_}W~rtq@t.g?ogNm~~k5o +=Bϻ,CX#R/di+kwz:yGwzvJ +lw-vdZ=E3vfw8m\)ث C26`A6( LJu +XІZՑ`@$uZC
.pL
U*ZM RMҨb2\P(k`ĔC{KS
A%hS +E쫢ncKv
>Ф60 +H F +H?'uV7$sLQKBT䆵%*rlk(3`|En}~~}5B)I7F}%Y^F}-iзF +胯Z~:koO[.%SMG:~o.qWNA8 },3D9>p2!yЗF}-i^F}%YF })IWB}5[0G`_g
~oKRȔB6M%t*S:(!+ +Hy<{KeXNǾeɞٲg:gR2D#˄K!B"Iɒ%Fi;s}<u?x>d@}S Z\~vVFFfFV +;KWVeʯ;Ò +dK$AA
m
pq + Pk!!A~>^^n.$.&$["?O)h 0`@daQ1 I,/++#-& @!`$ppmTT@b_PXDL\v+)kZ45
u5Ue +r +4 +Lu'STRQ'h[XYw8t?]]99pgkmeanjblEPWQRKD +!<s..>1BJjZzFfVvvv +~fef\HJ;}:2"D()$qC]Mu ᄉ,fRpI)hhY"/`RɈظԴ+WKJ+*Q(/+-Z| ?7'+#-59)!.6:*d()8쒖d_ޏZI+ ,WR ;'7O߀`3s/].ZRV~f}C㭦۔n56߬V^Vr쌋ɉc#Ï}<\a$5%yY +SǞ9v,$Dlhk(+YRF
dSXθPT!Y;D<I +sN!Q1\+,.khjik ~:ltlJp=599||lKoOwg{[KS}]
d$Łȱo7'{k3#]X,NTX0:@a-[JP'Q:H<q*:.bVuM-w:`hxdtϼ?PWfg_
r܌į#N}+HȦZńBBZNI]"E'7ģAp~1_XxwH
~[\|͛`f^LON`[o7U"9c"HANVZjx)1!4KFXefWT20w/OF&d_)Fc}b98DÇ?B
݀c4fTf_|$tuk%W2SbH>nv{0~nV+-8yĤ4t-|I@uv[Gw'C179,0?C+m[Csd2?$Ϟ=jomRVjBld1P[m7 ۃbX K˫hZ;x"UuwL l=ʖRhM~x5?;3=9> +"{mn*/s*4>=zJrp`rmXU\|B2 +چ{C#Ƨ u-]" 8 VVV,lxL(?Bܙ/]ؘhKXg_kEP1?@<y&BF~QY}<8261=3o
OrƅB&A"d{rnZRlÎv{Ud$NQ恆B,,xIs_ܗ?hnsgJ)eI +aR +PMԡ\#ʈk%)#eKٳɚc> s络Tw9O<~@?8e C +l ٴ丘ۡ.gľ56c<|}5GtK +Xw,t-)nqIJ4L`B},8dzdldpWWLMy1'tKjpMK00urIJF^qEu=Jd's"$I$0'=9~D#X_D<x="$,xxIgw(jBZVA٫֎~H; +!2# Ã}oj*J^d<pˇ*a>^T)oyxWIJG,<|&r^V74wP ?13@|Vz]UYAvZ5*, +O_+n,Vqn:~mY)Hcp @yoTV۽99ƢݏbQAB^[YGps<avHwVE\W\Ā%$%e!nX_$?+yUGoXX`=(@X2:FyDx_65ܣa-3|" )U
¢/1UAx<wB\Noi+`/GN)?,)Ymz&V\CPYY E1b,@P@Ɔ0Źć<Zއt +9.sJ`lP28{~H-DY0b6,<E,IuPGa)11P0Ե
Z;^S;=G0{ޮ+=M5%<h\qRX0`3sjgY5>dncX]{'GOg;Tx,ۃʜԷ<˅İ`0=q%8_^vgѡ<Z>Qeo+R-_f<4)WiU
-]}C<~h,]]:gP[Z"K VR28jx/$QJVQe]SG =_&Gk^g$Qo]vw8~Do6-mšjVڨ +FLBƶ#p_p4sػ]
U%iO!:ʲb8<X-ek+(ů[&&CY@̧ _3۹Yn+26R2J۴
(~QCoI(7~Ejk|]GxVUv.Kɩ5:}7(2S^of}6/LF\jgqhMbB`T~aR6Na4hwzx}6o.M}|78JUNjՊWʀOZYZ̢ER3>6o(xs3ISͭ%ʃ.V4<v98*.5!)v]u$;%6'W;:۔rZy0X&sEu0Q0KRΦ'wC[j@y˃Spcj!\24
LO!,o1F`p#xLO¸n,Hq~<-^8Иk6(i (574JfcluISj5sc[֭ƺp0ИZ).w{ƦD<*/-.*)sCm5
kifę`Q5>#&)Y +)RпxeR_Q~u%iq4؍ʎ}&6^ԧ%1K1`/l$TǍt+p +ooTCcvD;Yiʉ3;h~@4~L! yk{L.?3hawGCmY1n`{.4cLfvAq9FyHmj?#?+)5Z2sؘh=;:- &mh}`FeI[
.@StCG`tҡ_L>
4f-lQK
̷G&eݸG3&,/#1wK%A3OIc>3w7ӫvK }\HA\ݩ̠J}{|3*;z~K3T[^zbQ]6V!H>JpAqyu];c oWԽBf>Q8#[iŊ6ЀuJuNиWK6xOf=_V?u%dŚe狀bHt`eh+8\N`F?])чݶlX$5|{ߊV|C2n|^AVRġMC$h!L\x
O:|jy^y'/qg[Hr1Zcrj:&.3>mh~;8LfqvXi)K +\G nKAiv5V mg&+o +Ǵiа~&^Ջ:ʲK G1tb9e,>ie- +xA v| +#++L[Pk`>a_W
n93 6.go=k&Էߪyo4Pf6Y!ȿWSdU=m
eɊΦ"3sH`Sykw{O}[
\(;9gR%)!n6&[nAIEMK`j
w7v0:O{lY.;p[Y5]S{p|jf|[ȋDX-[ %CU8P4ظa5]!GȟݿA EcSyw1w<a i}^QfւyԄ~YF\).oNA0uyPꅅpFCnGϐ:hP47-oZ 씆
a`J)@8@0t~'Tcb4B2Լ$N0yXy15
~iOvK>~$aaTh, +4>WύZF
ەP(0$rP9`4,i"9R9(Jˌl\EѠZx8px9AI8M3VPhfPO7)!n6Fr#+RCkgH5@£_Qr`q,3Ì*p&v^iUS#7ѠZuh$ +Y + +vHRR
4. ![dD88Z~<ػJϿW7HD:hkxRle(!Jg0'&x%g?o!Aqƥ`wS9d\Rno@̅w^vh[:aik(r6gq~NrJ.b8zM\h
1^~zlfpqpV. EMCkCiW˞h}G0zp
֗N:mJ +0*;|^)y
I G7Nzn]*#Bbd/ "J/w=ߺ"sf2EdȔS&D ET +4g*J +XeCDGwf6znkӽz>|k|QcRѷtO)G-|?c#hɽsx\Q}:#cc.+
px4p8?l(M<.<>թ"
~:<|KjHC*-UEmMEǧ<U!jﯗ" ZrI?s +:`=UHe}!#}qve&6.g?H/F<T=Uhz~ gkÕB\xow {^R~NUokMqo7])'x$ [JzNI卝Q +Ӣю&#BW$m+]?NAN觾vqcow3ՒYDOdmH*[>ۏpFEq> iCXFhSWӊ`7&?%=*
y-S;7cmgЎ;ZU}c\:N.;X(y7I6.h9șZA1xpI$qL8E_6p
&8(C8Pm0)A`(@![SM9z +@gcyNrbj&ER! b`|JTTCRThJe + >6Ry̜BjF[V*4ʂ'.{W)Uh +ɶn>}U+1AhRZUu Jjڻ=] "hVSsVq9*.MM.kTm&Twƌ2)BA!>nfZr47$Vb302%MsGXp $ر^{(+-
07,x]
XFcWSEnRţuy&27$F+D`pDz:@SPZzߩk_V¹APh28& ?d ,msPGaZԕV*J6Gl1V2SD,@[xZCo=|G_{mq&И½dwHa87 yվz{㬋59-2w
.m㏨`go;DCV8S iFGq]mj,[
h̚@Gtz;M]QAP(T1LcFWLaǣ*>Ai|hٛjQLl:vH
i@gCYv|-l|+t͝<#S^mAHX4
vJz.{RP38 +iF@#};Bc15
ƅt?]WH|
)+Wӊ[{Rq aR]cԵ/k
2-
FfNAF[Qh|iicQ3q9[vHiХ1%$nlrFS@AX(:H@?S2
h4Ӏ
\?SɫiEխat?]BߐNK,v{]gpt &CObg`dʋ-݃#A!h +q7Ք[2?0<˵;xI-o;q0F#Oīuh&͚'RPi>IRH(!J!q"RB(,%24i"͉QyNu߰wQ~{uz]z磩0yH*j<ZP۠W~;h"}t![c
mH,2s?wyJ~e#l^h(MzjNC^0<v>qM|oFϯhm'h}e9Q78o3Re;ЧIy
m!Xe OjXgD
^1yum.gnD%w6_c\q픓+6xDn=x:82!A6@uşޅy9ZQf%ͦ#GDNԵr[P8ڢ7طy"!.m&r˨t + +1۠Gn
t|{҂ژ%Hy}'<|QT3 +` EsB|\miȋ31lcKLnFGN.͎W60XgF'Ɋp#Gry_*%CKF>4 +p|A`tս +pU8F&n:pT!
u}⨁;f>O)lmQXCA|&>M-oAno$+>76pyG\ntpdB6U +oE> +oFEVT#``$ȪX9FU+8lG'UK{,V;bRa'QLU5-pTtRw6UܦL*NmPFDž;/>W5uQExp7罏fg5vR
*Emn=Kίh':ɥgˋTbWXgs#*1UTt4P503ʭ\ktwumW +;T<Sn?OΫ +8'?T`@C\^ +8A/x8XVec6hcF9e䶞[En7FeAkAC5PY`%`*
rʩ"x'&%g=JB}×Kq +F[|r|Z,6ȉOةgbQFnbvi4if2>v[)A)19H,Z6O=z~k%ܹwlNPG&aϒrf\5]D2i!.֩1]mb}5/MuPF9wS_0JvT/ee93Hd+%C +cI)$5B"(k%$%dm""ٲk_R~<"MssݟbbUes9>1}+hb +\ln3:#*,4`4Ʒ(1,@~11E>#m
R4TW.`w4p̙Ǿ U'p0 +zq Fcl8Z=0)`k1h|` 9Jk[x8&p +jo(vb cw;C$vT?6FZJh2h`uB +F$ =q@m$#>\w?'+UIJOx}G݃SU9Tpg\w>dUQRw<A\6nA-΄%d"ߍek&#h`dbXR +L@׀k)GYghK,:H8@8p nifw*B>:}{??5|Xg-wL
ptֺr',f+劬EE~=QIM3}؟no,zo^-%K8X,"8q:4>q96:^ +
6:׀_ew%9E}+G*-C>$(X%Q{PԭlcmAV?k#m⟚ԠAUw[؟ +MEяFХmCeQ c:>T *ث:_}?"ܴo{Sԧ8^% *aaW +u8H +q_72ufPՁ@38\4] #*쾩S
Wp,Yj 1iU7L@
>(;%*uxϙ?ဿj@'}4#ç8}]UrRc.T\eJv(?3bkP_DRrX;bҼq>'Qi,\ +Zʁ#9_g#*
N-
JuH)m5rI)l ++BF8aRcuԅ[O_%10"mwk*J /uf1|>;A!1UԷh3#=,(Is:ECiq2wF<s)6-5vƽA-*u'@L +{c}Co8ϊx|qK-嵠۲48V@X; +=X
vm<;rH#8}/zPP^9q`0z m
եF0~@MC +!6Tg&EN/bpH)jgt?nl#A,H8>0H8RؤcrU
μx`~R_Qx4jy?:x_M{V3PDe\(NBDʴ\}{G`xARhMl-6.r
{D)̉Tذs*O;2yl|Bɪn3sG
pyόV#ɇqVJ%_#f(v$N0:snr+!2C.!TT5$CҀB,)ǐ"t3ʐ1CVfLd.i{=ޜug=-[o.#t
LZ(Rggiϟ$߸bk,IcK~ +?mA9
Pj\8ni]AJd=/αG@TZqN.]WZm9:bj0wK +_M0ǒiZ;{!,^* +V<F0L`-Uy7:ف*r֬ZW6XDVKVꮃv.烣ϯoǃj8`zIk}p^N@t7oob}3j|jnI
9Ϋ+@iT=Iww4١(S +&Eu]#n~72јwx +XǃAmȿ-jm +? +J@cOൄg(p\}$<M'mLd%.[ ?<`Sol{/1W"- ƀS#3z!]u%Y
lLyZQܡchaz>Gc[=gb``%En`܊v5߳MAJTp7uRjLOUPQ7jrb_|0K ++gNV͆ÿQbjJk ++މaSU[mzaIX<\|#Sr7ukm=-LӆzʼlPWma~r̝⪆fjߋQ6gyT('ֵ`߳Gv|}KV0<lB@p +>jsCU1*P?k+mRlAoӃoWGEL/" ;QE?RS0#)6"ȡ}Zf"?xqons/$VJvA.F>>B(iHVJMY~V ߳ǭLޮ(+ZX!IJ^UkiOTVx|[+Gx={,d܊ +s2* /S08cs<(.
RXԜB}52az7Y ѡ,PI)Hm)-E~xCPDRNyǞ&G]GZ} +2O_<of&hC}혰K^.z$֭``ƁW +\RV.`X{
/\W2[Դt
̑OaicӬ /?R`k1`/Y]e̔hdq@WSUA.)#x,_ו,x5D )+[:{G&` X@"1*IEQ^ƽЋ@WKm/YRk+!)Hxz_w?53m2$`P@C
v>*-IOrq\Y A+8br'Dx~8xDM/4w
a PY"tĄso1 +G2Sn߸wYId
[L>>!~9 eUud{H& Ijٞ@joV' +u?eoiOXxs-%/+^W@BaC&f0n*o~' +:Dޒ$tRKV߱>by9Cmk +r3Sc"}N9XTSƶ{[]a1)9EU=
Mw @R3sJ*([ک}@2jlLf +" ( +j`Q1 +.뵭lu~g$_o`0y}ɛDx2?\_]Y;GMqs-jȏMLgHeu}s[Aȕkӷ?Xx_ϯ +?7wG53=y}&qa6qf O&I%D9,-,#jX4721#YSml$&A.ّWP":TwhۉށsãʱS7gTgBݹ۪S7ǔÊ{;[ApgT*jfX׃tS$16Z1(c3@'5M
+aϲExU^>195}sJu[JukёaŅ}=][VW-) +q +/GS0 gs +398 0 0 398 6557.1162109 7246.1401367 cm +/Im0 Do +Q +
endstream
endobj
862 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
863 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 398>>/Filter/FlateDecode/Height 398/Intent/RelativeColorimetric/Length 20921/Name/X/Subtype/Image/Type/XObject/Width 398>>stream
+Hy<{KeXNǾeɞٲg:gR2D#˄K!B"Iɒ%Fi;s}<u?x>d@}S Z\~vVFFfFV +;KWVeʯ;Ò +dK$AA
m
pq + Pk!!A~>^^n.$.&$["?O)h 0`@daQ1 I,/++#-& @!`$ppmTT@b_PXDL\v+)kZ45
u5Ue +r +4 +Lu'STRQ'h[XYw8t?]]99pgkmeanjblEPWQRKD +!<s..>1BJjZzFfVvvv +~fef\HJ;}:2"D()$qC]Mu ᄉ,fRpI)hhY"/`RɈظԴ+WKJ+*Q(/+-Z| ?7'+#-59)!.6:*d()8쒖d_ޏZI+ ,WR ;'7O߀`3s/].ZRV~f}C㭦۔n56߬V^Vr쌋ɉc#Ï}<\a$5%yY +SǞ9v,$Dlhk(+YRF
dSXθPT!Y;D<I +sN!Q1\+,.khjik ~:ltlJp=599||lKoOwg{[KS}]
d$Łȱo7'{k3#]X,NTX0:@a-[JP'Q:H<q*:.bVuM-w:`hxdtϼ?PWfg_
r܌į#N}+HȦZńBBZNI]"E'7ģAp~1_XxwH
~[\|͛`f^LON`[o7U"9c"HANVZjx)1!4KFXefWT20w/OF&d_)Fc}b98DÇ?B
݀c4fTf_|$tuk%W2SbH>nv{0~nV+-8yĤ4t-|I@uv[Gw'C179,0?C+m[Csd2?$Ϟ=jomRVjBld1P[m7 ۃbX K˫hZ;x"UuwL l=ʖRhM~x5?;3=9> +"{mn*/s*4>=zJrp`rmXU\|B2 +چ{C#Ƨ u-]" 8 VVV,lxL(?Bܙ/]ؘhKXg_kEP1?@<y&BF~QY}<8261=3o
OrƅB&A"d{rnZRlÎv{Ud$NQ恆B,,xIs_ܗ?hnsgJ)eI +aR +PMԡ\#ʈk%)#eKٳɚc> s络Tw9O<~@?8e C +l ٴ丘ۡ.gľ56c<|}5GtK +Xw,t-)nqIJ4L`B},8dzdldpWWLMy1'tKjpMK00urIJF^qEu=Jd's"$I$0'=9~D#X_D<x="$,xxIgw(jBZVA٫֎~H; +!2# Ã}oj*J^d<pˇ*a>^T)oyxWIJG,<|&r^V74wP ?13@|Vz]UYAvZ5*, +O_+n,Vqn:~mY)Hcp @yoTV۽99ƢݏbQAB^[YGps<avHwVE\W\Ā%$%e!nX_$?+yUGoXX`=(@X2:FyDx_65ܣa-3|" )U
¢/1UAx<wB\Noi+`/GN)?,)Ymz&V\CPYY E1b,@P@Ɔ0Źć<Zއt +9.sJ`lP28{~H-DY0b6,<E,IuPGa)11P0Ե
Z;^S;=G0{ޮ+=M5%<h\qRX0`3sjgY5>dncX]{'GOg;Tx,ۃʜԷ<˅İ`0=q%8_^vgѡ<Z>Qeo+R-_f<4)WiU
-]}C<~h,]]:gP[Z"K VR28jx/$QJVQe]SG =_&Gk^g$Qo]vw8~Do6-mšjVڨ +FLBƶ#p_p4sػ]
U%iO!:ʲb8<X-ek+(ů[&&CY@̧ _3۹Yn+26R2J۴
(~QCoI(7~Ejk|]GxVUv.Kɩ5:}7(2S^of}6/LF\jgqhMbB`T~aR6Na4hwzx}6o.M}|78JUNjՊWʀOZYZ̢ER3>6o(xs3ISͭ%ʃ.V4<v98*.5!)v]u$;%6'W;:۔rZy0X&sEu0Q0KRΦ'wC[j@y˃Spcj!\24
LO!,o1F`p#xLO¸n,Hq~<-^8Иk6(i (574JfcluISj5sc[֭ƺp0ИZ).w{ƦD<*/-.*)sCm5
kifę`Q5>#&)Y +)RпxeR_Q~u%iq4؍ʎ}&6^ԧ%1K1`/l$TǍt+p +ooTCcvD;Yiʉ3;h~@4~L! yk{L.?3hawGCmY1n`{.4cLfvAq9FyHmj?#?+)5Z2sؘh=;:- &mh}`FeI[
.@StCG`tҡ_L>
4f-lQK
̷G&eݸG3&,/#1wK%A3OIc>3w7ӫvK }\HA\ݩ̠J}{|3*;z~K3T[^zbQ]6V!H>JpAqyu];c oWԽBf>Q8#[iŊ6ЀuJuNиWK6xOf=_V?u%dŚe狀bHt`eh+8\N`F?])чݶlX$5|{ߊV|C2n|^AVRġMC$h!L\x
O:|jy^y'/qg[Hr1Zcrj:&.3>mh~;8LfqvXi)K +\G nKAiv5V mg&+o +Ǵiа~&^Ջ:ʲK G1tb9e,>ie- +xA v| +#++L[Pk`>a_W
n93 6.go=k&Էߪyo4Pf6Y!ȿWSdU=m
eɊΦ"3sH`Sykw{O}[
\(;9gR%)!n6&[nAIEMK`j
w7v0:O{lY.;p[Y5]S{p|jf|[ȋDX-[ %CU8P4ظa5]!GȟݿA EcSyw1w<a i}^QfւyԄ~YF\).oNA0uyPꅅpFCnGϐ:hP47-oZ 씆
a`J)@8@0t~'Tcb4B2Լ$N0yXy15
~iOvK>~$aaTh, +4>WύZF
ەP(0$rP9`4,i"9R9(Jˌl\EѠZx8px9AI8M3VPhfPO7)!n6Fr#+RCkgH5@£_Qr`q,3Ì*p&v^iUS#7ѠZuh$ +Y + +vHRR
4. ![dD88Z~<ػJϿW7HD:hkxRle(!Jg0'&x%g?o!Aqƥ`wS9d\Rno@̅w^vh[:aik(r6gq~NrJ.b8zM\h
1^~zlfpqpV. EMCkCiW˞h}G0zp
֗N:mJ +0*;|^)y
I G7Nzn]*#Bbd/ "J/w=ߺ"sf2EdȔS&D ET +4g*J +XeCDGwf6znkӽz>|k|QcRѷtO)G-|?c#hɽsx\Q}:#cc.+
px4p8?l(M<.<>թ"
~:<|KjHC*-UEmMEǧ<U!jﯗ" ZrI?s +:`=UHe}!#}qve&6.g?H/F<T=Uhz~ gkÕB\xow {^R~NUokMqo7])'x$ [JzNI卝Q +Ӣю&#BW$m+]?NAN觾vqcow3ՒYDOdmH*[>ۏpFEq> iCXFhSWӊ`7&?%=*
y-S;7cmgЎ;ZU}c\:N.;X(y7I6.h9șZA1xpI$qL8E_6p
&8(C8Pm0)A`(@![SM9z +@gcyNrbj&ER! b`|JTTCRThJe + >6Ry̜BjF[V*4ʂ'.{W)Uh +ɶn>}U+1AhRZUu Jjڻ=] "hVSsVq9*.MM.kTm&Twƌ2)BA!>nfZr47$Vb302%MsGXp $ر^{(+-
07,x]
XFcWSEnRţuy&27$F+D`pDz:@SPZzߩk_V¹APh28& ?d ,msPGaZԕV*J6Gl1V2SD,@[xZCo=|G_{mq&И½dwHa87 yվz{㬋59-2w
.m㏨`go;DCV8S iFGq]mj,[
h̚@Gtz;M]QAP(T1LcFWLaǣ*>Ai|hٛjQLl:vH
i@gCYv|-l|+t͝<#S^mAHX4
vJz.{RP38 +iF@#};Bc15
ƅt?]WH|
)+Wӊ[{Rq aR]cԵ/k
2-
FfNAF[Qh|iicQ3q9[vHiХ1%$nlrFS@AX(:H@?S2
h4Ӏ
\?SɫiEխat?]BߐNK,v{]gpt &CObg`dʋ-݃#A!h +q7Ք[2?0<˵;xI-o;q0F#Oīuh&͚'RPi>IRH(!J!q"RB(,%24i"͉QyNu߰wQ~{uz]z磩0yH*j<ZP۠W~;h"}t![c
mH,2s?wyJ~e#l^h(MzjNC^0<v>qM|oFϯhm'h}e9Q78o3Re;ЧIy
m!Xe OjXgD
^1yum.gnD%w6_c\q픓+6xDn=x:82!A6@uşޅy9ZQf%ͦ#GDNԵr[P8ڢ7طy"!.m&r˨t + +1۠Gn
t|{҂ژ%Hy}'<|QT3 +` EsB|\miȋ31lcKLnFGN.͎W60XgF'Ɋp#Gry_*%CKF>4 +p|A`tս +pU8F&n:pT!
u}⨁;f>O)lmQXCA|&>M-oAno$+>76pyG\ntpdB6U +oE> +oFEVT#``$ȪX9FU+8lG'UK{,V;bRa'QLU5-pTtRw6UܦL*NmPFDž;/>W5uQExp7罏fg5vR
*Emn=Kίh':ɥgˋTbWXgs#*1UTt4P503ʭ\ktwumW +;T<Sn?OΫ +8'?T`@C\^ +8A/x8XVec6hcF9e䶞[En7FeAkAC5PY`%`*
rʩ"x'&%g=JB}×Kq +F[|r|Z,6ȉOةgbQFnbvi4if2>v[)A)19H,Z6O=z~k%ܹwlNPG&aϒrf\5]D2i!.֩1]mb}5/MuPF9wS_0JvT/ee93Hd+%C +cI)$5B"(k%$%dm""ٲk_R~<"MssݟbbUes9>1}+hb +\ln3:#*,4`4Ʒ(1,@~11E>#m
R4TW.`w4p̙Ǿ U'p0 +zq Fcl8Z=0)`k1h|` 9Jk[x8&p +jo(vb cw;C$vT?6FZJh2h`uB +F$ =q@m$#>\w?'+UIJOx}G݃SU9Tpg\w>dUQRw<A\6nA-΄%d"ߍek&#h`dbXR +L@׀k)GYghK,:H8@8p nifw*B>:}{??5|Xg-wL
ptֺr',f+劬EE~=QIM3}؟no,zo^-%K8X,"8q:4>q96:^ +
6:׀_ew%9E}+G*-C>$(X%Q{PԭlcmAV?k#m⟚ԠAUw[؟ +MEяFХmCeQ c:>T *ث:_}?"ܴo{Sԧ8^% *aaW +u8H +q_72ufPՁ@38\4] #*쾩S
Wp,Yj 1iU7L@
>(;%*uxϙ?ဿj@'}4#ç8}]UrRc.T\eJv(?3bkP_DRrX;bҼq>'Qi,\ +Zʁ#9_g#*
N-
JuH)m5rI)l ++BF8aRcuԅ[O_%10"mwk*J /uf1|>;A!1UԷh3#=,(Is:ECiq2wF<s)6-5vƽA-*u'@L +{c}Co8ϊx|qK-嵠۲48V@X; +=X
vm<;rH#8}/zPP^9q`0z m
եF0~@MC +!6Tg&EN/bpH)jgt?nl#A,H8>0H8RؤcrU
μx`~R_Qx4jy?:x_M{V3PDe\(NBDʴ\}{G`xARhMl-6.r
{D)̉Tذs*O;2yl|Bɪn3sG
pyόV#ɇqVJ%_#f(v$N0:snr+!2C.!TT5$CҀB,)ǐ"t3ʐ1CVfLd.i{=ޜug=-[o.#t
LZ(Rggiϟ$߸bk,IcK~ +?mA9
Pj\8ni]AJd=/αG@TZqN.]WZm9:bj0wK +_M0ǒiZ;{!,^* +V<F0L`-Uy7:ف*r֬ZW6XDVKVꮃv.烣ϯoǃj8`zIk}p^N@t7oob}3j|jnI
9Ϋ+@iT=Iww4١(S +&Eu]#n~72јwx +XǃAmȿ-jm +? +J@cOൄg(p\}$<M'mLd%.[ ?<`Sol{/1W"- ƀS#3z!]u%Y
lLyZQܡchaz>Gc[=gb``%En`܊v5߳MAJTp7uRjLOUPQ7jrb_|0K ++gNV͆ÿQbjJk ++މaSU[mzaIX<\|#Sr7ukm=-LӆzʼlPWma~r̝⪆fjߋQ6gyT('ֵ`߳Gv|}KV0<lB@p +>jsCU1*P?k+mRlAoӃoWGEL/" ;QE?RS0#)6"ȡ}Zf"?xqons/$VJvA.F>>B(iHVJMY~V ߳ǭLޮ(+ZX!IJ^UkiOTVx|[+Gx={,d܊ +s2* /S08cs<(.
RXԜB}52az7Y ѡ,PI)Hm)-E~xCPDRNyǞ&G]GZ} +2O_<of&hC}혰K^.z$֭``ƁW +\RV.`X{
/\W2[Դt
̑OaicӬ /?R`k1`/Y]e̔hdq@WSUA.)#x,_ו,x5D )+[:{G&` X@"1*IEQ^ƽЋ@WKm/YRk+!)Hxz_w?53m2$`P@C
v>*-IOrq\Y A+8br'Dx~8xDM/4w
a PY"tĄso1 +G2Sn߸wYId
[L>>!~9 eUud{H& Ijٞ@joV' +u?eoiOXxs-%/+^W@BaC&f0n*o~' +:Dޒ$tRKV߱>by9Cmk +r3Sc"}N9XTSƶ{[]a1)9EU=
Mw @R3sJ*([ک}@2jlLf +" ( +j`Q1 +.뵭lu~g$_o`0y}ɛDx2?\_]Y;GMqs-jȏMLgHeu}s[Aȕkӷ?Xx_ϯ +?7wG53=y}&qa6qf O&I%D9,-,#jX4721#YSml$&A.ّWP":TwhۉށsãʱS7gTgBݹ۪S7ǔÊ{;[ApgT*jfX׃tS$16Z1(c3@'5M
+aϲExU^>195}sJu[JukёaŅ}=][VW-) +H #7 +d}4 !#чCۧOD_ϟGxt/p+u%nD_:M[C$ч2}0#_.N3}<c/.4 +E6(BPd"+٠ +UV(BlPd*+Y +E6BPd*+YUV(BlPd"TY +E6BlPd"TY +U6(B
Pd*+YEVAlPd*YEVAlPd*+٠ +U6(B
Pe"TYEVA
Pe"TYE6B
lPe*٠ +U6(AlPe"TYU6(AlPe"TYU6(AlPe"TYU6A
Pe*T٠U6B
lPe"T٠ +U6A
lPe"T٠UVA
lPe*T٠U6A
lPe*T٠U6A
lPe*T٠U6A
lPeGlPe*T٠U6A
lPe*T٠U6AUU6A
lPe*<,(@
lPe*<,(@
lPe*<e>_
lPe*<e>_
lPeG~\U٠D\T٠Dq>\T٠F.alPe*|GA>\T٠_D.1lPe/#U67|*TYe>@
\=ʻS˼*<Y;{6rwVe#ycU۪eӵUeUQ.G.,B2o/E.f,b2oe둫7*yD.ު\=[x3rPe#y*yu#"WyuC*ymc"Wyi"yi*y]"WyY#yYC+yMc#WyM+yE#WyA#yA7T.j\k)rr[2U^ƍ˼[+y
Fn\P̴̬IˌV̜yˌʐ̘̈<e\ɘU +\晰e\Ye\)eNсEp8:G~`tڟ_+CYGq$:GD'q_93/Gc~~ tʯ5CCs:@'|~ϢyS]A-:5[]E#+϶C{
f\ +usK~otwm>хƠ_\gǕmF\f,EYF\ˡ܃~u-t.Nq+EF:8:,yASgCg&ПϧзOE
>E?2.p?5E}gBz"я24G.3}D
u[c~mCt6Egjkt'7.-S@_sSDG}oE-*lQ`{أ&k- +Hy<swk)a,c[ckK]R%e)BTԡVɉD*ʖȖ%[,Jۭ{۹7تxt۹ן13;"""~wLMEp;M&}Mk.Gz#ut8|2",&_A|;Ff`/6^ZdYY988vnbggceE∛Ƭ y!L/Z/]66v]ĹDEvDZ̹h"mcv1]n/ +5!K5E㙕tET\Bre2T'Reee-^")!.&JAHq)Ԅ/,80' +.WT)khjjiiϙ2MIQA +Rb"B|<܋9'Y$=ůY1`د0EL\RjU^q9073562]B[(Olq&3IwbOb$)()͚vN]\]]f
.8oprt_gfJKsScC}]mMu<u5:pi8IDd!1 eJ4U
&f\7{nz5oV/M]{N-8YzrJB1Ii9E + +aѲA~vr\3fZPD\ZVQy70$<Sgf^UR^USSF.ȾÇBd{$0X?{P[]y0*l:Ña^ +ʚz&Vk\=}ęW߫mlFa/../ Y#W/FA6pNQA^vFZm.-/灃ZSfK1u:B£q +qk{W/( +_BFXaAvzr|LxϒLhBEΙcAq)m*
w
+OF)FZ;CfwO܌i ֦U +#C\[LPٶIFJ\DP +wil/ݼUPu1#oIcҟnoixV]Qs#츾pFq?j50»XȸGP]}Q3!fH1Ն]{;⺯
@[m'\Ӧr YO5_?͆i['Ok[;{̈'0#i0Ɔ=
u%yB=lMDAx3gi"k`ÇE0sUXVY1Sç5]4huqC<lOg5"KA<S8cH<|.GZ
3vƌӲ@
0SO*QA&
nyQJF(+%|ɂ3ygpi;s%ťdaWcƗqbZNqyu}S[5xQO&'aƺ4 +gsc}mUM2Bmeb!QIMʪq,f\SO$c47zd8YOkvuW.[4m63uY#Y[lu +jۢ>MS\QSߌSb<iG7744ww{S}
✈9ېTQ -LhL25%H6xs7"UOjč97T<{8Z2صYVJLig:[0d-,&%y)kGP\?c|@#qqt5M둶g+13y?MjZMg<@0z ~čV4ΰR[߃
k03<TQ.XH{Xػ\JH.*G}EbL?8WeEz9ےtam̛Ǟqf<XHֺF$[g";]Me<MT_ # +U߉sw{c]ei^zbLV9)1lgF KmQ3w
IL+k0FęP$'5>2Xa+hq31̓Yd:eCf6^A9%kں#ar_gˋLmwZ{A;&&l=k:L֢',ܑ3k^tن1:緯G^v4W'Fؑ#cG63fχiوd_VUrh[aq=@w[#vkV'8C;qfukSm
kB?n٭yfuc[mÜ?9k;#)693%p'aq
ٹ#y7NC8gvܴΞv*')l1X3<|hv^=3WoƧ>kYBg+YӏA
O+2oQ⬮qy,nmg΅%%W)1IO٭g;aOhuB_sf<Xڸ +f>wK-I52vXaD]nbikJ'dټ{ɳN^ףQ#C{Pٲeyi<ءV[%1*5Z^I!/vJ42dUmXa(I1֦GTڰg`9+_@H\Za>]3"?y'r+975.AU%yrLx`kSkg'Ϛ: SVD#G65
O$F]v4>OyТM]W,uC97<vsH#3EAu%n\65ܥNBX6]SPPTjvuQ ++j{_9<4psfjقʙ6Z$$wuv ]dhq!v4H +<oq쐪䊥<9S*.auPG~uWVf79!4ɱW.X@9oX# +56m\%e!jl^ Gy=aکB9SAZl`0'7fvcPAIOꚻ Ga>dnvM`GE@z6u]cWBn!5ؚ_ĝ|OSݤ#GBCYBEc\ELddI] +-x ),Ckɾc_ᖚ~s;LWg{~ޯ7ZW^9>줷 +Tm^Td2k-wG'n_k~?5?I/#
xcJU% dƂRrxW\Rƃe-}CΘkӛ^,{+!/e&Uj +҂j>s?^Ϧu
ךjOh.}{fc`QE%!kZ;yx#!T~p_Ɔ;[k˞=H
=vp:lQ "fdrw5@%k֬`=62VW^LJ}YTys
3;d-3[>A@n`V2~YA6, +jϩڠ$')&"p?lnL=IiMKg?dd~? YhUܸ b-d_p̵@[Y`Qwrw1T& O!oؤ|(w{UWE~Jlkf,0 + dC +nAUi_9YPL ǥdP+h=\<8@XTOg\ #`ePAsrv.(\95,膋*?3)6 +CY0iO~phMZr + +EA1Ow4UQ)jamU3ryA^kȩ/[ +L)CemvպmιmX{W?x<tV_CQ0z;}ZPdjA舢2
幉Of{R[MVaFQ/ai&w%w~
zB춚1\-T~elz̝}gU^͠M4WF]xZoJ |1xOD_Oce xX#La`FՋi ^nc3ն}
P^Ő:{(ogs
Eq~Q/Z6^Q9TR*[z?gzj`' o"SFLZQ̜BR5I98> kw@{mI&"`Fvnزk4:y?ի
5poseAJdUS?o[5^QZ'l܋HzתqHJܭ~9c+'2,8s.7Cb^gNަG~^y1^6e e.aUvW4yCܗ69)NfeLFYBiøA2Wσ}5fQQ1Z@X|OӇ<xMٜRb߰tI(s(.kEtM3)J/fMʲj:fW +ZfFTE~ʳvڪ2\,KyY^ +|lZʞCYFMc3t!="%DGʞ~#K72 欸\hȋ2lXHQgQ(c+$Z+Rfcrx[_k֬zZeqF9e
@Y;K3cB|+u"TBҪڦv^ +p0n\̰{e`Sw
(c)fOvԕ~#",q/:-[vkZXPNʞ8M;V(Io^#*OجR2W@{mq`&#LOWFf#+T +.l:&NhF9\ڎA +h_kUAӀ+7fWL^M=f>i"a]fL2(9ڥibͨmc{a+0S#y1_q3,^>!Ȍ30/_<=9>){SmȿP_pae`똕݈ʖQ00*ωu9_6^[TM!zUR@[!g.FGTn99(c,15U_ +
Sj2BU6Z؛<d|᷀gm{1fȿZ`la!/JIe)Gz^̬Gzښhࠤf081w&,;6ua.W69$wizFՠ +{Ak2?鉿NA&63N>exȽ>ؿA^'g[Lgd$)o(llЛPeRP + [Z0ȿ*;&,T>q=Pa1g:eG +VvSyŕ"g@ac4KlRas6(l\,{6<İAac:l'e9YHa3oW4J̫'w.[EyXUk@SHZUwHlVYc7(lLi`GmqzTv~v<hY38(16CƲoSB^0#gO\q+iE5ReT%>s;oOn7=ͺ3iG حep҆n<(ll\5EiWTccX0#gKXV][a {G&᠕n(|-(#Kw䂪IPsbCnBحKge-L:;C;*:s@Prʞh*Hw4BԂ>9T.vT(lz"a8#RƇvJ/x./߀&{-|㢙 ƯfYVYy}RX>023u1?yY6 +~URׅ'y|0]+#>˴Ig;$6>`Ga?Ȗ"J^Ey٘|0~J +(KM%˂tDMPXJzAP09{Mt=g3s]W%! \]H,ffzrż\<jsXM.^[3jyXƇԲk@tJ~eXB(ў/:b'[ZƱʪygעZ%\j"MA-S2mݱ"n5tZ#\[eg&+Ê[_cd{{Gk< }!pgCYNRĹ'ޱ)(c0olf2ʋߔ?w#{/L-,z#(6m`2$yaf2/9 +,Rm[h*ogVѳrI+i@\R3zKN +|쌴N)23Q tHA)R0 +RPJM[bߴVy3>q#萂)xwCu%Bl)MLu!~z7=H;JɊ0eU[9l{^nESۙH +|MyQYOxRNR1_Dy#
GDVlxbvYC0R._WބcU3K.Ŕ!e *;,!)Õ`xe[[YÓH|2Nգ^!2K;2t!*`kLsPDʐf2=5eϐ:_EqAz*҂dxg eW#(YH*Sc=r;Q1=Pn)L tUƔ*3q +JӳvK/m@ʐei3Ph.HpQ +i*Р
RH$dPdqoEua۶}zps}m7Аo?*hZ;܈{KkhW[*?h.+H&6^y
;^M/n[Dl}yb7aj<K4e^Us7ydZFj}s]UiA.?ѵ#$;~&"ZSЖ:˲߽tbeR9Yh-(gaD ZO;J p۳yRI-s!k~oPˠfׯ>^[wd?3?Y8T_| -e<}h8;3-sK,]cxے+@ԏ/_u6R]4ef>qUƻ\QT7ZSF{ZcqڱAS^H-g`f_$eh{3/#?d'prwSUWO9ZX"H2$Ll"v8^P38^)R1 +"7%&źB[f^/QG@g}yNRT~s]UiA.yZfd]gpLJnEc -wԕf=s}fmIN.AiU]QIQT{mqfBQ;5,t-wf~ +|1O^OSbB,kztu֯F" .pG]gN7UAKoQI9@pxeć3]Tr&/{"ߖԶ
CFY.HliE/qƻ\zK+f\:Ɗr2Аec&_4_qә`q,e?q`7Y+um^1)?5taCh\lZYf/Ζ}/F>*7Agգ6,<aP^cȅO@ruYh6H0)hmtf"fgb,S
惞90dc;,\Ge.LL7tD$-m3{BnOlYƶnƽ Agy{!ގV2mEWlSs+Hx?kiֳK'f#$\oߕudc;5͓ڙY*)2,<R*kMwKa;)$bSeˇ84R}afW\ih}*$ +@63Ge'EyܦDxYF+RAI(A˘
+VJwcLZ0,S)VnrO,irhO(UJ*#GU0 +ʼs)i8a^U6/19
=ODʬ9u#-
dD823/U~A/8X'{
T"|f +
_;O_SSoY'ポ^ce.+|!c7RsM1:gT_kջCKEjEl +g4uԁV&p/qR7Z?02!UY]'S,)~vR_UzEиpLX@aΨ̤3wLKQBpA\^dɹc +Akzrt=Q[.]{0VշDՔխ}#D3ku=Ou1Ql^lL9y~;#*P+MKfQw/BƔ-:eIM[(q!\Y,9.;]e0R^aLtғOMC_!, +_'^8Ql@yvL.]vG@čԜ5fro碬WnZE3ˡO* , +_ukA63'7ALNhþo>)0@fe͖(r/2\|Rʺ+mw`gvX.(/pF`fc!k߾#)33QV Q9ETt-vhvcI33<e?2Vٜ<@KjF& L^߿}i,ؘ0K0_N|[afj&"(JOD,(c +|f}eW`gfF(7U>K;,(>J[Cgd}v6sq47- /jfm{]KysKW/ݍދ
sݴJ_,|R:ֻ~eui(`&FڪKrRoFz:n0efd˹+ ++{ f&]mg*-B
(l̒JfJL-m!BcBpm*~x="`zcMy*QF- &f`/4OȚWT7ݾtv?5(/WHBqg/K5;Pn,zoGn]k!'NeL +ضXSE 3']S|'͇Q"l%9z6AV7E
,$k ++fTH}=Q1/3)&Q/uZ(3k +]ާrJ[ag3.}]Y<%><`vD$ig*`n~!1Iyag3,}>7-bAg[=zeJ2rv62ƆoY\C^2.`2z>AH};AA<=9u̴i0C:{-wag3,S>.j2bN"RJf u4<3}>婉6}-Ļv}l9
_35ql05fR"l<2y(Pt)|D
T<Z_M}
leggV4v
ag13?5^[&#)&ߍ4U/ҙ):`9%U-=CgGΆO_}^0.٨g$e)mnQ͟epެ_ʳ
<q_@ص:F'
_3=B~Ȉzîk~M^ 2jz6ާ.|)߿Fԓ{qz8l0RO_gfG=[^c{ÿFJs +{̴'ЧF^;uUeDؙT2q\2jL`O>`DٽP]N\^}]lQU]sk'Y#S4n^|9}:cME a>.z5~f
,7E%fU6v
@Ѧ]fuyW~;QҢh_xgs K(j9^sNIUs1|4v)`^
Yϟ8fw1ݞ2~fDqllRҚ^h`̏ʢԄ W5]ż`Nꭴv<~5Iއ:kUЧ<WUIN`?7)"#}l0
V9{ml^=53bne"MEqs"sJQhC/&(hMG&WNzؕ9%"e~;b`i9şpцg:$o/JyҀ1ŐykVY/>_C$*4!a59hTBqNr!#FNIu{!zwxΓy_ИYUh +# te$Dy;W_CIfFL3ň9܋K.nBDc!P=m
;Wmoߨ"')71aL10mcӞא)Amzr4 +̜Q(#
L\FQ]o'_/mB1 +\+h<$R?c_(C]-%y,kMe 6/ +fxŀhoeaM$2~ +99&ݑ`(#!~(I6+ +0kBqw}EDW]IVR@y`c'!C/)kS]
eYI~䇚ì28 +Y@e\Շc~ɐ{;kKEyC@3|~h)L/#-dv<6f9oÌsio. [B.cb&rKk;{Q9z(`_7A_htRfnIMSGQPYq՞`̘G`V +q32d +f.~aemuߑ_+@omi6[W.L~Q]Xl\<},$tM{\}>My_ZZ{j6?d\?a[GCE^z237+T5t|~,ƶ)ôm]_QlJInٜc&yb@́]NL)mǙ|H[זgq~íd +U
f/dwukrOGK}%Xku?SN25q/&-~sl6wCcIۦ2R֍e"]p1߭qj$OcI48sk/sxaQos+[
~fyF1k+ +ߓR?loo6uKā[Sښi!cZ6JZuY3 ֕s>.Eb[3w[6<yWKBPжYi) Fʚ(8 +]s[3y[6*a9X:y\u?&XXq%mlY3-ζr#9<dE9]8Zo)x;ͬ흽V7 <LǸ8AN +t)O5E9I1~6:ʲbdidbq ;{E%=y_UC>Nh<kˋr3co^dki|`ʶpZO 3!Ϋ *Z;܁\PF"jMO0n(~(%."sPZjқ7ii`+ +lݦqūask(US%Ќjq|d*kMy5cXq^x9\ajw\Co")hfUxwv`7#yW-[4Lf;3OKW&&[ #R#>ګ<{Y +RsqΛ}=cEV +KG;(2HAX{fV5Zcoq\錅\֨?̓L`<N.\ۢT1qp ̜
-=zq^b۱#h}]m턌YˣZdŅ&A&g߸EZAuG7ȸҪ&8Ġf4"u<Hlm$\.e9 aXe=L3\aеf&1E5M}IzqoM1FUMh~Yݰn0^9Cʚ9sJ+ <&1-YQYu}3[߸ĸxb ;#9.\"1^3YCmz^+("!ͭ=oNHɄ⮬%@
= [H3A1li)ARlD7lƫZgF뙓k 7ᬩgdfuop$*rRF47)b:1jA*1TunVZbLx\ctX/@y&5k03>ð>]CNnѿ>A
=I4xxPS9TuxF5kpm0|2;5t<w<uMmw|}-OBtk+K!Fz\>oejjB6z<|bwkYx+/I +YtCSk!=4 &s鼄/FQ@Bz:Ôب@kΎv͎bMJLXe~1kC8\Wشy=z-9\
.-F]=}PT#kYX3~"^(vwkK +'D@hVQ^h!|Ɵ3ZШqŤU59aim}_IstUM=Acӵ7cuO1z
/;48@ :ReYIa~{_Z{v*In$ȿf%T5ճ_97 +4|ZJHoYMc?$e~1 IiHT@nnmk5
ڈ{93 U=w +OLNz_X\ZVQE얶vOPTm +4jnWTٵwacK+[K.^a0:t)I +;%M_TRQ
7nv#cS3s^Y[{? u~ +/劁#B ldLu5ɪJ %ZV^QIM5064ٍ&E.Ҋm7;:{zo3844<<<44xޞΎm7Z[j*J +sEDpqdl43c4b5 +XVKM+
_:Hưa;s|B"qI_PxjuMm]Ʀ렎jmxjUeEyYI,MMJD==\ٶL+lFFN%&hbim9 +9N\/ 84<2:6>19Ux.(3+BNnKEWKJJK\s!;+St>0591>6:2<48;[Zji a|؝5A/
c4tLiVLՃ
'$&%gD"QFzlJrRbB|\\l *2<,48כpuvd۱lЂ:tB{o >$'-!B¨̂ΰl).ϋ +9-QaC|}|/2t3`pц&IHKaԪd!`e[3OG'gW7w'˛yr=\]8l{;I%x
'zXFR'wi1"]#llC#cS3sn`XY0Yp8lPe1mK:faanfjldhGP_Uetw0A|ƩѪedp) :T>B7u`AQ: 4_^`BoWzF*$L!tL"V5 +L&TUU.|ߨmi]#lX".V'U1VE'ee?&]85k1n +q +/GS0 gs +486 0 0 480 6513.1162109 7207.1401367 cm +/Im0 Do +Q +
endstream
endobj
868 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
869 0 obj
<</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 486>>/Filter/FlateDecode/Height 480/Intent/RelativeColorimetric/Length 25812/Name/X/Subtype/Image/Type/XObject/Width 486>>stream
+Hy<swk)a,c[ckK]R%e)BTԡVɉD*ʖȖ%[,Jۭ{۹7تxt۹ן13;"""~wLMEp;M&}Mk.Gz#ut8|2",&_A|;Ff`/6^ZdYY988vnbggceE∛Ƭ y!L/Z/]66v]ĹDEvDZ̹h"mcv1]n/ +5!K5E㙕tET\Bre2T'Reee-^")!.&JAHq)Ԅ/,80' +.WT)khjjiiϙ2MIQA +Rb"B|<܋9'Y$=ůY1`د0EL\RjU^q9073562]B[(Olq&3IwbOb$)()͚vN]\]]f
.8oprt_gfJKsScC}]mMu<u5:pi8IDd!1 eJ4U
&f\7{nz5oV/M]{N-8YzrJB1Ii9E + +aѲA~vr\3fZPD\ZVQy70$<Sgf^UR^USSF.ȾÇBd{$0X?{P[]y0*l:Ña^ +ʚz&Vk\=}ęW߫mlFa/../ Y#W/FA6pNQA^vFZm.-/灃ZSfK1u:B£q +qk{W/( +_BFXaAvzr|LxϒLhBEΙcAq)m*
w
+OF)FZ;CfwO܌i ֦U +#C\[LPٶIFJ\DP +wil/ݼUPu1#oIcҟnoixV]Qs#츾pFq?j50»XȸGP]}Q3!fH1Ն]{;⺯
@[m'\Ӧr YO5_?͆i['Ok[;{̈'0#i0Ɔ=
u%yB=lMDAx3gi"k`ÇE0sUXVY1Sç5]4huqC<lOg5"KA<S8cH<|.GZ
3vƌӲ@
0SO*QA&
nyQJF(+%|ɂ3ygpi;s%ťdaWcƗqbZNqyu}S[5xQO&'aƺ4 +gsc}mUM2Bmeb!QIMʪq,f\SO$c47zd8YOkvuW.[4m63uY#Y[lu +jۢ>MS\QSߌSb<iG7744ww{S}
✈9ېTQ -LhL25%H6xs7"UOjč97T<{8Z2صYVJLig:[0d-,&%y)kGP\?c|@#qqt5M둶g+13y?MjZMg<@0z ~čV4ΰR[߃
k03<TQ.XH{Xػ\JH.*G}EbL?8WeEz9ےtam̛Ǟqf<XHֺF$[g";]Me<MT_ # +U߉sw{c]ei^zbLV9)1lgF KmQ3w
IL+k0FęP$'5>2Xa+hq31̓Yd:eCf6^A9%kں#ar_gˋLmwZ{A;&&l=k:L֢',ܑ3k^tن1:緯G^v4W'Fؑ#cG63fχiوd_VUrh[aq=@w[#vkV'8C;qfukSm
kB?n٭yfuc[mÜ?9k;#)693%p'aq
ٹ#y7NC8gvܴΞv*')l1X3<|hv^=3WoƧ>kYBg+YӏA
O+2oQ⬮qy,nmg΅%%W)1IO٭g;aOhuB_sf<Xڸ +f>wK-I52vXaD]nbikJ'dټ{ɳN^ףQ#C{Pٲeyi<ءV[%1*5Z^I!/vJ42dUmXa(I1֦GTڰg`9+_@H\Za>]3"?y'r+975.AU%yrLx`kSkg'Ϛ: SVD#G65
O$F]v4>OyТM]W,uC97<vsH#3EAu%n\65ܥNBX6]SPPTjvuQ ++j{_9<4psfjقʙ6Z$$wuv ]dhq!v4H +<oq쐪䊥<9S*.auPG~uWVf79!4ɱW.X@9oX# +56m\%e!jl^ Gy=aکB9SAZl`0'7fvcPAIOꚻ Ga>dnvM`GE@z6u]cWBn!5ؚ_ĝ|OSݤ#GBCYBEc\ELddI] +-x ),Ckɾc_ᖚ~s;LWg{~ޯ7ZW^9>줷 +Tm^Td2k-wG'n_k~?5?I/#
xcJU% dƂRrxW\Rƃe-}CΘkӛ^,{+!/e&Uj +҂j>s?^Ϧu
ךjOh.}{fc`QE%!kZ;yx#!T~p_Ɔ;[k˞=H
=vp:lQ "fdrw5@%k֬`=62VW^LJ}YTys
3;d-3[>A@n`V2~YA6, +jϩڠ$')&"p?lnL=IiMKg?dd~? YhUܸ b-d_p̵@[Y`Qwrw1T& O!oؤ|(w{UWE~Jlkf,0 + dC +nAUi_9YPL ǥdP+h=\<8@XTOg\ #`ePAsrv.(\95,膋*?3)6 +CY0iO~phMZr + +EA1Ow4UQ)jamU3ryA^kȩ/[ +L)CemvպmιmX{W?x<tV_CQ0z;}ZPdjA舢2
幉Of{R[MVaFQ/ai&w%w~
zB춚1\-T~elz̝}gU^͠M4WF]xZoJ |1xOD_Oce xX#La`FՋi ^nc3ն}
P^Ő:{(ogs
Eq~Q/Z6^Q9TR*[z?gzj`' o"SFLZQ̜BR5I98> kw@{mI&"`Fvnزk4:y?ի
5poseAJdUS?o[5^QZ'l܋HzתqHJܭ~9c+'2,8s.7Cb^gNަG~^y1^6e e.aUvW4yCܗ69)NfeLFYBiøA2Wσ}5fQQ1Z@X|OӇ<xMٜRb߰tI(s(.kEtM3)J/fMʲj:fW +ZfFTE~ʳvڪ2\,KyY^ +|lZʞCYFMc3t!="%DGʞ~#K72 欸\hȋ2lXHQgQ(c+$Z+Rfcrx[_k֬zZeqF9e
@Y;K3cB|+u"TBҪڦv^ +p0n\̰{e`Sw
(c)fOvԕ~#",q/:-[vkZXPNʞ8M;V(Io^#*OجR2W@{mq`&#LOWFf#+T +.l:&NhF9\ڎA +h_kUAӀ+7fWL^M=f>i"a]fL2(9ڥibͨmc{a+0S#y1_q3,^>!Ȍ30/_<=9>){SmȿP_pae`똕݈ʖQ00*ωu9_6^[TM!zUR@[!g.FGTn99(c,15U_ +
Sj2BU6Z؛<d|᷀gm{1fȿZ`la!/JIe)Gz^̬Gzښhࠤf081w&,;6ua.W69$wizFՠ +{Ak2?鉿NA&63N>exȽ>ؿA^'g[Lgd$)o(llЛPeRP + [Z0ȿ*;&,T>q=Pa1g:eG +VvSyŕ"g@ac4KlRas6(l\,{6<İAac:l'e9YHa3oW4J̫'w.[EyXUk@SHZUwHlVYc7(lLi`GmqzTv~v<hY38(16CƲoSB^0#gO\q+iE5ReT%>s;oOn7=ͺ3iG حep҆n<(ll\5EiWTccX0#gKXV][a {G&᠕n(|-(#Kw䂪IPsbCnBحKge-L:;C;*:s@Prʞh*Hw4BԂ>9T.vT(lz"a8#RƇvJ/x./߀&{-|㢙 ƯfYVYy}RX>023u1?yY6 +~URׅ'y|0]+#>˴Ig;$6>`Ga?Ȗ"J^Ey٘|0~J +(KM%˂tDMPXJzAP09{Mt=g3s]W%! \]H,ffzrż\<jsXM.^[3jyXƇԲk@tJ~eXB(ў/:b'[ZƱʪygעZ%\j"MA-S2mݱ"n5tZ#\[eg&+Ê[_cd{{Gk< }!pgCYNRĹ'ޱ)(c0olf2ʋߔ?w#{/L-,z#(6m`2$yaf2/9 +,Rm[h*ogVѳrI+i@\R3zKN +|쌴N)23Q tHA)R0 +RPJM[bߴVy3>q#萂)xwCu%Bl)MLu!~z7=H;JɊ0eU[9l{^nESۙH +|MyQYOxRNR1_Dy#
GDVlxbvYC0R._WބcU3K.Ŕ!e *;,!)Õ`xe[[YÓH|2Nգ^!2K;2t!*`kLsPDʐf2=5eϐ:_EqAz*҂dxg eW#(YH*Sc=r;Q1=Pn)L tUƔ*3q +JӳvK/m@ʐei3Ph.HpQ +i*Р
RH$dPdqoEua۶}zps}m7Аo?*hZ;܈{KkhW[*?h.+H&6^y
;^M/n[Dl}yb7aj<K4e^Us7ydZFj}s]UiA.?ѵ#$;~&"ZSЖ:˲߽tbeR9Yh-(gaD ZO;J p۳yRI-s!k~oPˠfׯ>^[wd?3?Y8T_| -e<}h8;3-sK,]cxے+@ԏ/_u6R]4ef>qUƻ\QT7ZSF{ZcqڱAS^H-g`f_$eh{3/#?d'prwSUWO9ZX"H2$Ll"v8^P38^)R1 +"7%&źB[f^/QG@g}yNRT~s]UiA.yZfd]gpLJnEc -wԕf=s}fmIN.AiU]QIQT{mqfBQ;5,t-wf~ +|1O^OSbB,kztu֯F" .pG]gN7UAKoQI9@pxeć3]Tr&/{"ߖԶ
CFY.HliE/qƻ\zK+f\:Ɗr2Аec&_4_qә`q,e?q`7Y+um^1)?5taCh\lZYf/Ζ}/F>*7Agգ6,<aP^cȅO@ruYh6H0)hmtf"fgb,S
惞90dc;,\Ge.LL7tD$-m3{BnOlYƶnƽ Agy{!ގV2mEWlSs+Hx?kiֳK'f#$\oߕudc;5͓ڙY*)2,<R*kMwKa;)$bSeˇ84R}afW\ih}*$ +@63Ge'EyܦDxYF+RAI(A˘
+VJwcLZ0,S)VnrO,irhO(UJ*#GU0 +ʼs)i8a^U6/19
=ODʬ9u#-
dD823/U~A/8X'{
T"|f +
_;O_SSoY'ポ^ce.+|!c7RsM1:gT_kջCKEjEl +g4uԁV&p/qR7Z?02!UY]'S,)~vR_UzEиpLX@aΨ̤3wLKQBpA\^dɹc +Akzrt=Q[.]{0VշDՔխ}#D3ku=Ou1Ql^lL9y~;#*P+MKfQw/BƔ-:eIM[(q!\Y,9.;]e0R^aLtғOMC_!, +_'^8Ql@yvL.]vG@čԜ5fro碬WnZE3ˡO* , +_ukA63'7ALNhþo>)0@fe͖(r/2\|Rʺ+mw`gvX.(/pF`fc!k߾#)33QV Q9ETt-vhvcI33<e?2Vٜ<@KjF& L^߿}i,ؘ0K0_N|[afj&"(JOD,(c +|f}eW`gfF(7U>K;,(>J[Cgd}v6sq47- /jfm{]KysKW/ݍދ
sݴJ_,|R:ֻ~eui(`&FڪKrRoFz:n0efd˹+ ++{ f&]mg*-B
(l̒JfJL-m!BcBpm*~x="`zcMy*QF- &f`/4OȚWT7ݾtv?5(/WHBqg/K5;Pn,zoGn]k!'NeL +ضXSE 3']S|'͇Q"l%9z6AV7E
,$k ++fTH}=Q1/3)&Q/uZ(3k +]ާrJ[ag3.}]Y<%><`vD$ig*`n~!1Iyag3,}>7-bAg[=zeJ2rv62ƆoY\C^2.`2z>AH};AA<=9u̴i0C:{-wag3,S>.j2bN"RJf u4<3}>婉6}-Ļv}l9
_35ql05fR"l<2y(Pt)|D
T<Z_M}
leggV4v
ag13?5^[&#)&ߍ4U/ҙ):`9%U-=CgGΆO_}^0.٨g$e)mnQ͟epެ_ʳ
<q_@ص:F'
_3=B~Ȉzîk~M^ 2jz6ާ.|)߿Fԓ{qz8l0RO_gfG=[^c{ÿFJs +{̴'ЧF^;uUeDؙT2q\2jL`O>`DٽP]N\^}]lQU]sk'Y#S4n^|9}:cME a>.z5~f
,7E%fU6v
@Ѧ]fuyW~;QҢh_xgs K(j9^sNIUs1|4v)`^
Yϟ8fw1ݞ2~fDqllRҚ^h`̏ʢԄ W5]ż`Nꭴv<~5Iއ:kUЧ<WUIN`?7)"#}l0
V9{ml^=53bne"MEqs"sJQhC/&(hMG&WNzؕ9%"e~;b`i9şpцg:$o/JyҀ1ŐykVY/>_C$*4!a59hTBqNr!#FNIu{!zwxΓy_ИYUh +# te$Dy;W_CIfFL3ň9܋K.nBDc!P=m
;Wmoߨ"')71aL10mcӞא)Amzr4 +̜Q(#
L\FQ]o'_/mB1 +\+h<$R?c_(C]-%y,kMe 6/ +fxŀhoeaM$2~ +99&ݑ`(#!~(I6+ +0kBqw}EDW]IVR@y`c'!C/)kS]
eYI~䇚ì28 +Y@e\Շc~ɐ{;kKEyC@3|~h)L/#-dv<6f9oÌsio. [B.cb&rKk;{Q9z(`_7A_htRfnIMSGQPYq՞`̘G`V +q32d +f.~aemuߑ_+@omi6[W.L~Q]Xl\<},$tM{\}>My_ZZ{j6?d\?a[GCE^z237+T5t|~,ƶ)ôm]_QlJInٜc&yb@́]NL)mǙ|H[זgq~íd +U
f/dwukrOGK}%Xku?SN25q/&-~sl6wCcIۦ2R֍e"]p1߭qj$OcI48sk/sxaQos+[
~fyF1k+ +ߓR?loo6uKā[Sښi!cZ6JZuY3 ֕s>.Eb[3w[6<yWKBPжYi) Fʚ(8 +]s[3y[6*a9X:y\u?&XXq%mlY3-ζr#9<dE9]8Zo)x;ͬ흽V7 <LǸ8AN +t)O5E9I1~6:ʲbdidbq ;{E%=y_UC>Nh<kˋr3co^dki|`ʶpZO 3!Ϋ *Z;܁\PF"jMO0n(~(%."sPZjқ7ii`+ +lݦqūask(US%Ќjq|d*kMy5cXq^x9\ajw\Co")hfUxwv`7#yW-[4Lf;3OKW&&[ #R#>ګ<{Y +RsqΛ}=cEV +KG;(2HAX{fV5Zcoq\錅\֨?̓L`<N.\ۢT1qp ̜
-=zq^b۱#h}]m턌YˣZdŅ&A&g߸EZAuG7ȸҪ&8Ġf4"u<Hlm$\.e9 aXe=L3\aеf&1E5M}IzqoM1FUMh~Yݰn0^9Cʚ9sJ+ <&1-YQYu}3[߸ĸxb ;#9.\"1^3YCmz^+("!ͭ=oNHɄ⮬%@
= [H3A1li)ARlD7lƫZgF뙓k 7ᬩgdfuop$*rRF47)b:1jA*1TunVZbLx\ctX/@y&5k03>ð>]CNnѿ>A
=I4xxPS9TuxF5kpm0|2;5t<w<uMmw|}-OBtk+K!Fz\>oejjB6z<|bwkYx+/I +YtCSk!=4 &s鼄/FQ@Bz:Ôب@kΎv͎bMJLXe~1kC8\Wشy=z-9\
.-F]=}PT#kYX3~"^(vwkK +'D@hVQ^h!|Ɵ3ZШqŤU59aim}_IstUM=Acӵ7cuO1z
/;48@ :ReYIa~{_Z{v*In$ȿf%T5ճ_97 +4|ZJHoYMc?$e~1 IiHT@nnmk5
ڈ{93 U=w +OLNz_X\ZVQE얶vOPTm +4jnWTٵwacK+[K.^a0:t)I +;%M_TRQ
7nv#cS3s^Y[{? u~ +/劁#B ldLu5ɪJ %ZV^QIM5064ٍ&E.Ҋm7;:{zo3844<<<44xޞΎm7Z[j*J +sEDpqdl43c4b5 +XVKM+
_:Hưa;s|B"qI_PxjuMm]Ʀ렎jmxjUeEyYI,MMJD==\ٶL+lFFN%&hbim9 +9N\/ 84<2:6>19Ux.(3+BNnKEWKJJK\s!;+St>0591>6:2<48;[Zji a|؝5A/
c4tLiVLՃ
'$&%gD"QFzlJrRbB|\\l *2<,48כpuvd۱lЂ:tB{o >$'-!B¨̂ΰl).ϋ +9-QaC|}|/2t3`pц&IHKaԪd!`e[3OG'gW7w'˛yr=\]8l{;I%x
'zXFR'wi1"]#llC#cS3sn`XY0Yp8lPe1mK:faanfjldhGP_Uetw0A|ƩѪedp) :T>B7u`AQ: 4_^`BoWzF*$L!tL"V5 +L&TUU.|ߨmi]#lX".V'U1VE'ee?&]85k1n +HV{|SnUHpKK%-J +i£ +0Фm +*/E|Tz, S`2>g:n0@vn}>cs}so.E{Z>w +w.BS0 H)+g+w]:J`ܟ(\&-$NTT1S(H&C1j(CPn1^@Q1dEK!P3.s""FRǶOhA
I;SL|`]һf<͖ޣg9}?h2t_9h3W&>ׇ2Ɂ)SULQ9s̝7O,|rQ⧖<tYڕV?~uyao_|W_ݰqS-moλw~v]?l}?C>rϞ|˅ᨷNS"O"zl,yIw;ȝW4!E0V!]!Yt+gUY;Xu$klYZVڡ+PH- 6yB|!_z(._Z櫌|P(g =Zr![ƹDuqŵ淬mRl!jho#{z|= +qDܷnklF<7K,j%M +A
KPXuXpel5\ſo -kPO'8c8#8qoc2ó88 +!IF1Ym3Y,OVNi-tw)1SmH %: +dǍpU^b rAFβr9EJvydQ juM%1>!:qYh'40ҭld,p.gF2%MҜ2K^BDmISy"Y+`LR9$u80EI3+4(2rl"(r#8BPx.npXMvICHcȂAq ` Տv)wcN3g`:ў9S +eW68!7wH+DYQĄ%%R1n+0EF9p +9a@J c"7.xYݶiQ^ļKws|Q*Ms2]`% +7k +UF6+^Z&ZZ4 +[ˊo44gACo\a7.C2s~Ήb7=ENd_Q.*A@~EͶtjVPgجu0w6sfXg+4
ZunHM
FuE0^Mw~w~ŏyG_.&89 I!44 +Re(U`BmQ +zyv~9Oclw\'//w`O5kC + +7Vʊ+{/Fw۫Eu/
:1+yT&t`lZلaXy-Ó9|!ҁ,t 04@~WuZu:HÞw)wl8 71<_hh6&m +zQdr8",߂QcdP<d(i89TJL\Մ#5ᕎz +lZJe$xۘAcFFKlaY",F!2&DAuE4p#wjWG aF>FA0|\o,O뭌I-u;o~nEyi;i-7۶}C +vڲyk ?u@A;1ɂ19O.Nې$qS
1:nQGhf$
sې}I3BY6X;>z݁+OM/Wx2Yp>+01n`3KӚQQ>2}j\Uj:n:kL7yɇd@i33٬/=ڵkNW~.2Ķ}?yHoⷉ5_. +uxtésկx[-"Ql3}pEYD0*yRakL"a4BD0_jLJRKh\Jܻ[{N*>r6a` Ʈ:RN q +VoTz8 Fj jwq5KWNxyuƍպNKGbop>d +9B!_ț4b +U]j7'\q#Qx&/QA5׃t`k@ =#=@= +\w1Y
3'$B 2_;Nza_w|x乒olzKc_:CҡC]CDy0<~:G1w|$g<a1V!igE4DS
3}]eFliP[MM=zG<ۻ7''o7nIv"yIf̀49JeNQ((7zc27n +Kxjd*<ܪ`M&H+/w7^v$)bFoe@c5 +,Yҡ' ZuK6W&'d;T{؆QO6`5m77y5ZZ-49{_^^Z|?}Bدب+|wއ{Ư+c11]6i]55j֡+Bip*H.U%MK.QSEP(
m$h!Xn#}wΜ93sΙsΩԲBu(432t0f7lj2Y_ʮj?ϏĜq9YX+f,fve>|^MbUG{pzvhq=ҩ4qyG2 '>3۬IiЕLy(>zcGa;hƘUFN~hHV%?4m?ԛX}]qR!2=l_\'pVDf_VNU5;TEt8N(G`,Cqrmf?a4oB߉gK:~cEԐ-ܺ'%! wŸg2lȕWdkTYzP'YBdSR:>*o:Z=f%e7-FlZj6xZiYǮ*Gf]18!)k9<ֹ,&Λ;1?#K+*yy>ɗːH!Dr#RtYdOY{Ÿ_WgK_h})Q|܋
,
~&rO֚W`uLkhMZW|ĽȨh|g_1(B]w:
â;g}b{6;1zxi^yo~~1T8a50rqW482D\Ԑrh YY*l (=L\kŵDl
?q&Zvً-%u<uʘ8'?&ʃCasYj*c.N4ujcV(BaQ^H^}xb5w9Gs:0HdU5sߒ}sR'][hBMiJ.uY@}J'y1N +-$֑(m?қ4"\*t04%ͦ iGyj>ÎQ-GG=J;*7Ej^+e]KTh>j{Nx}žƱ, +VkBPlWz,[H'O7ݫoCkKx B;P튆n.?PٶeږbD--@0)v +>BNQ<^#p#5 -SzDCJ*T?2J=ͭmiUsi,2^pBqޠs14ƆI|U#e%4X !|,#о@#!NU|7=W/d&kQЃ=T2CДn?̪FArVi
|CCȶJ7
>pX``O#t+o';q$-q# +H|kxW;ϝ}ξ76<XH4!dBTZb(j +jVjVm->CZ̒ٹs +i%"00qg_#~۸ubn +<[݇gqN]bXɜ
+[ eܸ"Arp&Y@Vr{4p$Zad*QU]S9+Wohl60cήK.[bWW֬]7~phxƑMha?ۭ@w3;> +w]|ⓟO|_~xC_z_yWڡǾ~7o>q[ǿS:~8?O:Og쳿xz~
,I600XO!G)8籢.U0rG7͈icbKi$C8p +U@_nK|ϼlm^44onf^0ϛ5_6cC];zK +g +l'8ESpwIvMY"5QSctTw_hL&ncd`Ħ Ó:7ἢ?Vj/n,Zzز"j<asY*L\MQbҽK:Cb(3JgzKg:Cb'Ex&<3_UWaz1fYig>RQWӽJt^`}QF%()_ +$ED<r
tSA._Sm6fWL2m^&!,0#$kKb+ /qZ1E,vC$=p(xRzV<G)R^-#ILWMUL\]}b4ȋveqйt~NSeYWX֨s{WK'vX +u^V<$W,Ӑolʧ9 +찫ѰOd+N"H28yV܂ZE*B[i^J82$1R2Es.˟cUAE6ˡ9]v!OD4CxS9y(.q9⏗Rkm +Bnqyz_^d5'##"7:I$ O@C4!E_=ؖκULeu\Pg4c
!:z :Uѡ(Av!nաHT`7)Q.͛o +#$BU9sE | -[yTbʇ*K[W2,tr0nB1ż(va"ي&ͱ+͢fZ9gL:h줟a܉V,cHFoT1*8+s/v(f_3srX|6Mnuҍ2kՎэ.zanbx@<GqU5Mt! j/Rq{E?e1@Hʧ%|Y87&mulK
x\z6jU̳4sx*P;;(BэՐ+UNÿȥ6A; KR\B㳸ZYz#|G[ZGZE8z+ߘyܼcwM9mbM{ɓS9, S*ٿ~
yr#7?VLwW]}&X%kr@ +q +0 g +/GS0 gs +15.8993082 0 0 -16.1025677 7632.3847656 6956.671875 cm +BX /Sh0 sh EX Q +
endstream
endobj
891 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
892 0 obj
<</AntiAlias false/ColorSpace/DeviceGray/Coords[0.0 0.0 0.0 0.0 0.0 1.0]/Domain[0.0 1.0]/Extend[true true]/Function 893 0 R/ShadingType 3>>
endobj
893 0 obj
<</Bounds[0.210899 0.350209 0.494759 0.604521 0.917524]/Domain[0.0 1.0]/Encode[0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0]/FunctionType 3/Functions[894 0 R 894 0 R 894 0 R 894 0 R 895 0 R 896 0 R]>>
endobj
894 0 obj
<</C0[0.5]/C1[0.5]/Domain[0.0 1.0]/FunctionType 2/N 1.0>>
endobj
895 0 obj
<</C0[0.5]/C1[0.987469]/Domain[0.0 1.0]/FunctionType 2/N 1.0>>
endobj
896 0 obj
<</C0[0.987469]/C1[1.0]/Domain[0.0 1.0]/FunctionType 2/N 1.0>>
endobj
888 0 obj
<</G 897 0 R/S/Luminosity/Type/Mask>>
endobj
897 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 898 0 R/Length 86/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +11.9217148 0 0 -12.0797682 7632.2294922 6995.9882813 cm +BX /Sh0 sh EX Q +
endstream
endobj
898 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
887 0 obj
<</G 899 0 R/S/Luminosity/Type/Mask>>
endobj
899 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 900 0 R/Length 88/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +225.9361572 0 0 -225.9361572 7629.4453125 7389.2958984 cm +BX /Sh0 sh EX Q +
endstream
endobj
900 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
886 0 obj
<</G 901 0 R/S/Luminosity/Type/Mask>>
endobj
901 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 902 0 R/Length 84/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +7.9551892 0 0 -8.0455885 7632.2294922 7026.9384766 cm +BX /Sh0 sh EX Q +
endstream
endobj
902 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
885 0 obj
<</G 903 0 R/S/Luminosity/Type/Mask>>
endobj
903 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 904 0 R/Length 86/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +225.9359131 0 0 -225.9359131 6749.46875 7454.5385742 cm +BX /Sh0 sh EX Q +
endstream
endobj
904 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
734 0 obj
<</LastModified(D:20140918115258+02'00')/Private 905 0 R>>
endobj
905 0 obj
<</AIMetaData 906 0 R/AIPrivateData1 907 0 R/AIPrivateData2 908 0 R/AIPrivateData3 909 0 R/AIPrivateData4 910 0 R/AIPrivateData5 911 0 R/ContainerVersion 11/CreatorVersion 16/NumBlock 5/RoundtripStreamType 1/RoundtripVersion 16>>
endobj
906 0 obj
<</Length 960>>stream
+%!PS-Adobe-3.0
%%Creator: Adobe Illustrator(R) 16.0
%%AI8_CreatorVersion: 16.0.0
%%For: (Coraline Lafon) ()
%%Title: (logotalerv2.ai)
%%CreationDate: 18/09/2014 11:52
%%Canvassize: 16383
%%BoundingBox: 224 -1014 1618 26
%%HiResBoundingBox: 224.5146 -1013.7627 1617.3057 26
%%DocumentProcessColors: Cyan Magenta Yellow Black
%AI5_FileFormat 12.0
%AI12_BuildNumber: 682
%AI3_ColorUsage: Color
%AI7_ImageSettings: 0
%%RGBProcessColor: 0 0 0 ([Repérage])
%AI3_Cropmarks: 0 -1080 1920 0
%AI3_TemplateBox: 960.5 -540.5 960.5 -540.5
%AI3_TileBox: 582 -828 1316 -252
%AI3_DocumentPreview: None
%AI5_ArtSize: 14400 14400
%AI5_RulerUnits: 6
%AI9_ColorModel: 1
%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0
%AI5_TargetResolution: 800
%AI5_NumLayers: 1
%AI9_OpenToView: -307 125 0.5 1389 670 18 0 0 163 161 0 0 0 1 1 1 1 1 1 0
%AI5_OpenViewLayers: 7
%%PageOrigin:524 -780
%AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9
%AI9_Flatten: 1
%AI12_CMSettings: 00.MS
%%EndComments
endstream
endobj
907 0 obj
<</Length 10404>>stream
+%%BoundingBox: 224 -1014 1618 26
%%HiResBoundingBox: 224.5146 -1013.7627 1617.3057 26
%AI7_Thumbnail: 128 96 8
%%BeginData: 10254 Hex Bytes
%0000330000660000990000CC0033000033330033660033990033CC0033FF
%0066000066330066660066990066CC0066FF009900009933009966009999
%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66
%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333
%3333663333993333CC3333FF3366003366333366663366993366CC3366FF
%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99
%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033
%6600666600996600CC6600FF6633006633336633666633996633CC6633FF
%6666006666336666666666996666CC6666FF669900669933669966669999
%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33
%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF
%9933009933339933669933999933CC9933FF996600996633996666996699
%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33
%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF
%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399
%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933
%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF
%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC
%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699
%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33
%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100
%000011111111220000002200000022222222440000004400000044444444
%550000005500000055555555770000007700000077777777880000008800
%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB
%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF
%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF
%524C45FDFCFFFDFCFFFDFCFFFDA4FFA8FFA8FFA8FD77FFA87D5227272752
%2752527D7DFD71FFA85227F826F82752277D27527DF8F8F8527DFD6CFFA8
%52F82727527DA87DA8277D52A87D7D7D52F827277DFD69FF5227F8527DA8
%7D7D527D5252527D52FD057DF827F8277DFD66FF27F8277D7D7D52527DA8
%A8FFCAFFFD04A8527D27522727F8277DFD64FF26F852A87D5252A7A8FD05
%FFCFFD05FFA87D27522727F82652FD41FFA8FFA8FD1EFFF8277DA87D7DA8
%FD11FF52522727262752FD3BFFA87D5252FD05275252A8A8FD18FFF8277D
%A85252A8FFA8FFA8FFCFFFA8FFA8FFA8FFFFFFA8FFFFFF7D522727F8F852
%FD37FF7D52F827F8F8F827F827F827F8F8F82727A8FD15FF525252A8527D
%FFFFA8512752A8FD04FF7D76FFFFA8A8FFCFFFFFFFA852275227277DFD34
%FFA85227F8FD042752275227522752FD0427F8527DFD12FF7DF87DA85252
%CAFFFFCF522752FFA8527DFF7D52CF7DF852FF5227A8CFFFA8522727F827
%A8FD32FF7CF827F82727522752527D527D527D525227522727F82751FD10
%FFA827F8525252A8FD04FFA827A8FFA8F852A87D52FF7D2752FF527DA8FF
%FFFF7D522727F852FD30FFA852F8272752275252A8A8FFCFFFFFFFA8FFA8
%7D5252FD0527A8FD0EFF7DF87D5252A8FFA8FFFFFF7D527DFF7D5252CA7D
%7DA8FF527DA87D7DFFFFFFA8FF52272727F8A8FD2EFFA827F8FD04277DA8
%FD05FFA8FFCFFFCFFFFFA87D7DFD0427F87DFD0DFF527C7D7D7DFD05FFA8
%FFA8A8A8FFA8FFA8FFA8CFA8FFA8FFCFFFA8FFA8FFFFFF2752272752FD2D
%FFA827F827275252FD11FFA87D52522727F8A8FD0BFF7D277DA852FD04FF
%52FD07F827F8F8F827F827F827FD05F827A8FFFF7D2727F8277DFD2BFFA8
%27F82727527DFD05FFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFA852522727F8
%A8FD0AFF527DA8527DFD04FF7D522727275227522727F8F8F82727522752
%2751272727A8FFFFA8522727F87DFD2BFF52F82727527DFD16FFA8525227
%27F8FD0AFFF852A8527DFFA8FFFF7D2752272727512752FD05F8FD042752
%2727277DA8FFA8FF52FD0427FD2AFF7DF82727527DFFA8FFCFFFA8FFCFFF
%A8FFCFFFA8FFCFFFA8FFCFFFA8FFFFA8525227F827FD08FF7D27527D52FD
%05FF7D272727F8FD0527F827F827F827F8272727F82752A8FFFFFFA82752
%2727A8FD29FFF82727527DFD1AFFA8525227277DFD07FF7DF87D527DFFFF
%A8FFCFFFFD08A827F82727F87DFD06A8A7A8A8FFA8FFA8272727F87DFD28
%FF5226272752FFFFFFCFFD18FF7D5227F827FD07FF52277D527DFD0EFF7D
%F8272727A7FD0DFF275227277DFD27FFA827F82752A8FD04FFA8FFCFFFA8
%FFA8FFA8FFCFFFA8FFA8FFA8FFCFFFA8FFFFFFA87D5227F8A8FD06FF7D52
%A8527DFFFFCFFFA8FFCFFFA8FFCFFFA8FF5227272726A8A8FFCFFFA8FFCF
%FFA8FFCFFFA8522727F87DFD27FF7DF827277DFFFFA8FF52FD15F852FFCF
%FF7D52272627FD06FF52A87D527DFD0EFF7D275227277DFD0DFF52522727
%52FD27FF27272752A8FD04FFA852FD047DA17DA827F8F8F852A8FD067D52
%A8FD04FF52522727A8FD05FF52272727A8FD04FFA8FFFFFFA8FFFFFFA8FF
%5227525227A8CFFFFFFFA8FFFFFFA8FFFFFFA8522727F852FD26FFA827F8
%2752FFFFFFA8FF7D52527D5252527D5227F827F827527D5252527D527DCF
%FFFFFFCF7D2727F87DFD05FF52F82752A8FD0EFF7D277D5227A7FD0DFF27
%52272752FD26FF7DF827277DFD05FFA8FD07527D27F8F82727FD0852FD05
%FFA752272752FD05FF7DF827277DFFFFA8FFCFFFA8FFCFFFA8FFCFFF5252
%7D5227A8FFFFA8FFCFFFA8FFCFFFA8FFA8272727F87DA8FD25FF52F8F852
%7DFFA8FFCFFD0AFF7DF827F87DFD08FFCFFFA8FFFFA85252F827FD05FF7D
%2727527DFD0EFF7D52A87D52A7FD0CFFA8277D52277DFD26FF52F82752A8
%FD0EFF5227272752FD0DFFA87D272727FD05FFA8F8272752CFFFA8FFFFFF
%A8FFFFFFA8FFFFFF52527D7D27A8FFFFA8FFFFFFA8FFFFFFA8FF7D27A87D
%F87DFD26FF27272752A8FFA8FFFFFFA8FFFFFFA8FD04FF7DF827F87DFFFF
%A8FFFFFFA8FFFFFFA8FFFFFF52522727FD06FF27F82727A8FD0DFF7D52A8
%7D277DFD0CFF5252A82727A8FD26FF52F82752FD0FFF5227522752FD0EFF
%7D272727FD06FF52F8272752FFCFFFA8FFCFFFA8FFCFFFA8FFFD045227A8
%A8FFCFFFA8FFCFFFA8FFFFA8277D272727FD27FF27272752A8FFCFFFA8FF
%CFFFA8FFCFFFA8FFFF7C2752277DFFFFCFFFA8FFCFFFA8FFCFFFA8FF5252
%F827FD06FFA8F8272752A8FD0CFF7D277D52277DFD0BFF5252A852F87DFD
%27FF52F82752FD0FFFFD0552FD0EFF7D272727FD07FF52F827277DFFFFA8
%FFFFFFA8FFFFFFA8FF5227525227A8CFFFFFFFA8FFFFFFA8FFA8277D7DF8
%27A8FD27FF52272752A7FFFFFFA8FFFFFFA8FFFFFFA8FFFF7D277D527DFD
%04FFA8FFFFFFA8FFFFFFCFFF5252F827FD07FF7DFD0427A8FD0BFF7D2752
%2727A7FD0AFF5252A8272752FD28FF7DF82727A8FD0EFF52527D7D7CFD0D
%FFA87D272752FD08FF27F8272727FFFFFFA8FFCFFFA8FFCFFF52272727F8
%A8FFFFA8FFCFFFA8FFFF7DF87D7D2727A8FD28FF7DF8F82752FFA8FFCFFF
%A8FFCFFFA8FFCFFFFF7D52A8527DFFFFA8FFCFFFA8FFCFFFA8FFFFA82727
%F87DFD08FFA82727275252FD0AFF7DF827F827A8FD08FFA8527DA852F87D
%FD2AFF27272752A8FD0DFF52527D7D7DFD0DFF7D522727A8FD09FF7DF827
%272752FD05FFA8FFFFFF52FD04F8A8FFFFA8FD04FF7D52A87D7DF852A8FD
%2AFF52F82727A8FD04FFA8FFFFFFA8FD04FF7D277D277DFFFFA8FFFFFFA8
%FFFFFFA8FFA852272727FD0BFF7DF827275252A8FD07FF7DF827F8277DFD
%06FF5227525252F852A8FD2BFF7D27275252FD0DFFFD0552FD0CFFA85227
%F87DFD0BFFA852F8FD04277DA8FFFFFFA8FF52FD04F8A8CAFFFFFF7D5227
%7D5227F8277DFD2DFF272627527DFFA8FFCFFFA8FFCFFFA8FFFF76275227
%7DFFFFCFFFA8FFCFFFA8FFFFA85252F827A8FD0DFF7DF827275227527DCF
%FFFFFF7DF827F827A8FFA8A8272752A87D5227527DFD2EFFA8F827277CA8
%FD0BFF5227522752FD0BFF7D52272752FD0FFF7DF827F8FD04275252A852
%FD04F852525227527DA87D27F8527DFD30FF52F827277DCFFFFFFFA8FFFF
%FFA8FFFF7CF852F87CFD04FFA8FFFFFFCFFF7D52272727FD11FFA82727F8
%2727522727277D527D272727FD057D2727277DA8FD31FFA827F827527DFD
%0AFF52F8272752FD09FF7D7D2727F8A8FD12FFA85227F827F82727277D7D
%7DA87D7D527D52FD04277DA8FD33FF7DF8F827277DA8FFCFFFA8FFCFFFFF
%7DF827F87DFFFFA8FFCFFFCFFF7D522727F87DFD15FFA87DFD0427F87D52
%7C525252FD0427527DFD37FF7D272752277DA8FD07FF52F8F8F852FD07FF
%7D522751F852FD18FFA87D7D52522752FD0527527DA8A8FD39FF5227F827
%27527DFD06FF7DF8F8F87DFFFFCFFFFFA852522727F852FD1DFFFD07A8FD
%3FFF7D27F827275252A8A8FFFFFF52F8F8F852FFFFFFA87D52522727F87D
%FD65FF7D27F8272752275252A8A852F8F8F852A87D5252275227F8F87DFD
%68FF7DFD0527FD04527DFD06522727F82752FD6CFF5227F8F8F8FD042752
%FD0427F8F82652A8FD6FFFA852522727F827F8FD0427527DFD1CFFA8A8A8
%FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8FFA8FFA8FD3DFF7D
%A87D7D7DA8A8FD1EFFA8FD0FFFA8FD09FF52FFFFFFA8FD56FFA8FD19FF7D
%2727A8FD06FFA827A8FD58FFA8272752FD08FFA8FD0EFFA8277DFD08FF27
%FFFFFFA8FD55FF7DA8FF2752FD07FFA8A8FFFFA8A8FFA8A8A8FFA8FFA8FF
%FF7DA8FFA8FFFFA8FFFFA827A8FFA8FD0BFFA8FD4AFFA8FFFFFFF8FFFFFF
%7DA8FFFFA8FFFF7D7D7DA852A8A8A852527D7DFF52FF27FD057D52FF27FF
%FFFFA8FD09FF7DFD4BFFA8A8FFFF527DFFFF7DA8FD04FF7D52FFFF5252FF
%7D7D27A8FF7DA87D7D27FFA87DFF7DF8A827FFFFA8FFFFA8A8FD05FFA87D
%FD4BFFA8FFFFFF5252FD06FFA8FF7D7DFFFF7D52A8A8A852FFFF7DFF7DA8
%27FF7DFFFFFFF8A827FFFFFFA8FF7DFD53FFA8A8FFFF7D52FF52A8A8FD04
%FF7D52FFFF7D27FFFFFFF8FFFF7DA87D7D27FF7D7DFF7DF8A827FFFFA8FF
%FF7DA87D527DA8A87DA8A8FD4AFFA8FFFFFF527D52A8FF7DA8FFA8FFFF7D
%7D7DA87D7D7DA87DA8FF7DA852FF52FFFF7D7D7D52A852A8FFFFA8FF52A8
%52FFFFFF27FFA87DFD4AFFA8A8FFFF277D527D7D52FD0BFFA8FD05FFA8FD
%06FFA8FD04FFA8FD04FF52A8FD047D527D7D7DFD4AFF7DFFFFA827FF27FD
%05FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF
%A8FFFFFF52FFFFFFA85252FD4DFF7D277DF8A8FF527DFF7DFD07FFA8FFFF
%FFA8FFFFFFA8A8A8FFA8FFFFFFA8FFFFFFA8FD06FF52A8FFA87D52527DA8
%A8FD33FFA8A8FD15FFA87D52FD04FF7D7DFFFFFFA8A8FFFFA8FFFFFFA8FF
%FFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFF7DA8FFFFA8FFA87D52FFFF7D
%7DFD35FF7DFD21FF5227FD0B7DA8FFFFA852FD0A7D5227FD3FFFA87DA8FD
%20FFA8A8FD0CFFA8FFA8FD0BFFA8A8FD3FFF7D7DFD30FFA8FD4DFF7DFD80
%FFA8FD14FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8
%FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8
%FFA8FD31FF7D7D7DFD13FF52F8272727F8272727F8272727F8272727F827
%2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727
%F8272727F8272727F8A8FD2EFFFD04A8FD13FF27FD39F87DFD2FFF7DA8A8
%FD13FF52FD0FF827F8272727F8272727F8272727F827F827F8272727F827
%2727F8272727FD0DF8A8FD45FF27FD0EF827F8F8F827F8F8F827F8F8F827
%F8F8F827F8F8F827FD04F82727F827FD0EF87DFD45FF52FD1DF8525227FD
%07F87D52F8F827FD0DF8A8FD45FF27F8F8527D52FD09F827FD0DF827A8FF
%52FD07F8527DFD10F8A8FD45FF52F8277D7DA8A8FD07F8272727F8F8F827
%FD09F85252FD08F87D52F8F827FD0DF8A8FD45FF27F8F827F8F8A852F8F8
%F827F8F8F827F8275252F8275227F827525227F82752277D272727522727
%527DFD0DF827F8F8A8FD45FF52F82727F8F852A8F8F87D27F8F827F8277D
%2727527D52F8527DFF275227277DA82727525227FF527D52F8F827FD09F8
%7D27F8F8A8FD45FF27F8F827F8F8F8A8FD07F827F8A8F8F8F8A82752527D
%52F8F852F87D7DF8F87DF8F87D7D527DFD04F82752FD0AF87DFD45FF52F8
%2727F8F827A827F827F8F8F827F827A827F8F8A827F8F8527DF82752277D
%A8F82752F8F8A87D7D52F8F827F82727F8F82727F8F827F8F8F8A8FD45FF
%27F8F852F8F8F8A8277DF852F8F8F827F8A82727277D5227277D7DF82752
%277DA827F87D2727A87D527DFD04F82752277D5227277DF852F8F87DFD45
%FF52F82727F8F8277D7D52277D27FD04F827525227F8525227F827F82727
%522752F8F8F852522727275227F827F8FD045227F87D7D277D27F8A8FD45
%FF27F8F827F8F87D52527D2727F8F8F827FD1EF82752F852527D7D7D2727
%F8F8A8FD45FF52F82752F827FFF87D52F8F827F8F8F8272727F8272727F8
%272727F8272727F8272727F8272727F8272727F827F8527DF8F8F852A852
%F8F827F8A8FD45FF27F8F87DA8A827F8F87D5252F8F8F827FD0DF827FD0D
%F827F8F82752F8527D7D277D5252F8F8A8FD45FF52F8F8F827FD05F827F8
%F8F827A8A8527D527D527D527D527D7D27F8F8277D527D527D527D527D52
%7DA8A8F8F8F8272727F8F8F827F8F8F8A8FD45FF27FD0EF87D52FD0A2752
%F8F8F8FD0C277D52FD0DF87DFD45FF52FD1BF827F827FD1BF8A8FD45FF27
%FD39F87DFD45FF27FD39F8A8FD45FFFD3A7DA8FD44FFFF
%%EndData
endstream
endobj
908 0 obj
<</Length 65536>>stream
+%AI12_CompressedDataxr$7 m#ͶH^۶fdl$+Uul혌dJ%ɬjww!MYW8s_q3m.o~WW>arc?Hxy{;~<ûޝ_]\lޜrŗ/.ǫ۟nϯ.b/8'q:c~,v8Ǐ?G~ywync|ehF_.pq ;H&trwwo/>~^}f?/vquun9^?/.>B}݇O/w^6ᇳ˫Xpt%f֟>|`j__C0 +zW.߿?\
yݧ /a{0||3)DI:< +h9&twD@Tf`x1s:l +G[:r\ +l0 qO~` +};oܩ;q[w&]pYggԞح=dM6dYG֨b(|\<Xg$1{JdE2ELK䖨8ycDSZ3sEAANv[QEB9KrgB9$
f!N аPqB2)=DZ255E@Q +:r"LDÉ8{Dr%Tuq2`,NF$["2c|04X"3XZ'Ds +(I>BD3 +;*,14R̠'LO;`́qʥE@%(;ju<PBB[*߱F-eB]?_m8QSle1Y-7si6Ɗ`f=ٸz,ᩦUkYv@a&'>, 2KԊhwUt"[3*:G&bis8s0=&?*6"ġIz5-}qnA5ױG7=
ڝݲiy<hgesVюݟyּUpw4N6k.Ӳݮrd[*iq+vw;QirDڔY<7*F2Ƿ-6
f^o ކ'Q5T쩛]W$R(Ϋs:4!J6DYl]TՖH۩"Zz*= -b?kKD^/<-v
o6I!7{%.TpJlCvo[ncc0s|{℞LKl4qMzC-AGVMqx%%v1n뒶qDףBv%e{t2SdJ6Ua-mokwf(o[5;#FCPx|hBCvWROِaX;<KbSJyҦ})նmL"7ښٟP6d-fyņa㨡
=GOrsul:zζ#w<"zTuTix1U_Hgo$u} +]g>a;ׇjqua3>P_vSOZަ}}ہ_K:?uIlog늙nrSS#s凋%ebåƑ)|l|Z^[wS-09U +qgqjJ J]~JISt5'<1Dɂ5cdb!ؑyĺ &O1)F4 ++~r1YѲjy\Ee\xH#%պe].Nv8-δ +,1Jvf]b"CIsaBEVlhs,;Bౄ6F]6ҭ;G
JG#] +C +=뎯%Tb>c[NM~frĝyyxr'(7cgڱe +ۮ~3=[~1vyV}S1K0\K]_TqQ|f9t4=)69=Òg_`~USY51#iD!T9WL 7О#\c]33 +/ 2
4ܿ-YM;Z`|P 2کpi)蛆{W>FŇj^tpsrOz;=l|ZZ$C/:t9Z\ iXE^{p٧Uz +">c(p,/D8 o9A Zp;ht}ςu:l*!Бf>-VqSQw33w?ѓ%!4XZo,M,m3)s0Q ϹV'F)%fR/^}dPR + :.h %7"bAFLԴVjׇ
2$,Cd,K8BJIy]mO*ʴ]i=#RZֵ4I,/_.YVdP4WK)Q$,ԉJ
J\^*he>=v@V
b.,s{r
K{9⠘^οY1QvL69.V,/bTpn1a{0H +bPѲ
Ea֬}a[omLc +MhF#ΘaۦN28㪘:5h7G:S9 0V cwzLg'F-P{`rK,^i-UaJe")/{*ǖrlKvSo"CWdۺ1;Λ(s鋢Lg֖Ly}+wZV0/ +Zȉ +f*w垏j?밽922kMv-T#{`'L=ªE״ZjPpM\Kc9ZEX0ڲ$Dځl%Yj@9Iw$/(zaԢȯ'vSb( u ^lwm~ fYNUۑ.;;H'v#t}Q개Ta2TPF/߽Zc$nt]sO@9Աgc_M17V?ʂ +D~6r+H5|%khy'v{04h *u +
Pkxb5{POUGK]OCRegہ}+H1j|a&lCPIaVzhӲay, v +d+J^TYj^;(>^ +>(jutFGTA`s/o0>ƨst1)kOvâg.&ОsugSuο:rEڋwϒ-8u&A>LL|vqY{
e|SA`U~:WjJSёDvQGT\vrH=PRrLG%!?:jN03ʁ<VɱC꩞1OeIG;4,0L'c9e#0c=Q61qYG:wX!ʑݓP7FէAWl)ؤw Zdp,̃^M1$ +P"7&z}dL:$Bxch
W>[q݉t,WЫ/;%y߾CqZz}RJ3 ֒ +HرS +5F8ZU8i|uLi.ˎFꊥEД-Us{y[h)*iXg;Ю:n<
Zuuh"/ +:w}\{/:'WIcQV(z0 +RNFQ<PRq=Oi2#R۷GGJXMձ)mq{F2ONqL^ }N^Ob5 u|q4ll)7"P*Q2h +-^L܁)>øa}iWF)n时d|fy<F~藎P +~滻˛˛g?|_6_]___|r(6mɚ1t`M;02W>/jo~7?}Ճ6oЛjncs
/ᰛ۫G_:~8}wy{˫p
L[/H<=`c| +p'L#D2<4Mfi"Pq'-ƺB#q&d LL}DMj`ByDǒ<"79 + +fTIE,nȶɡAY6ʓ +LK +H
bQ +^EICD0ٍ +F% Lv lD6CcР"A1,pWB̈́4 `EHlQfAs<4*3&@UrQ#܄aУ-T9ދőXMG` 5^{KO1Bܠ>".A_Rbk +ΆGE +/|8q@l45QwBT& 48S I
E5_p6dF@p!Ĉ +,>m$ OF
pVPڡ-PmhF!c0Xd6lxbVX +iRY! +|nUiVMVm(X ELm(mu2rHLBVq+헀HCUhRGP4DXXӄ>TJxt%a5Rx T¹S#':nT[Bd(A:BJ ԥ-20@SԩNyt!}cSCERuh +j
LF60#%O!KE40 +J%1JP +>]sL +D1J&$ _"R菂#RkQ +!EyH*u +aPGwQ(hH
BA2RPbP2EEن +B_'3s~3!JrL|-&*dJ
;H #6c 2Ɔ2 ml\˻Lx*B}K#ALO +p4VJJ7&8|#hIiѡtFs +otNΒotpf4߈T)F*
P7:bp_o6KHoП}oDC1ohd4ΉM8z4@,pqFHaL"iN8:PT@Quq9k/YqY`\&<kڄ#>2PO$]@uqtfMȩw80gf6G&4QɬԔ\rt7!{HSqI9n2ȳi8("GjBi2r4S +snեa68:qƈf~#Vqnx՜qKmэ0_#%s(uUkm\,@79^q`(ò%u,bՌXFi'M9r/6=>`g[(u;bev +M&3#IkbFcᭉG1$%hA@ I< +ML%>2Q(J3\%Qm$'Kd+9t%l[6Z K\lkk}f +◌%d81%gi1%H%q$XmҢ%!92Ҵ%t$VD-P5- +6iK&mi& KQWJk%aWѴ% -=V`JgW'GoSڲ }iڲ4#}F
}iڲD4m4mY5mY^l^,A/M[-눗f-KK%ޥY&ܥY*N4kYǺ4iYB],.MZց.Y8,KJsMKrMs%%9&9ҜeoiβoIʲ4g4gYb[,-Y6-Y,K\KsMXK%IՒeҤeiiҲ$i4iYY,,MZ,IZ,IZ.Y,,ZPf-Hf-K K%YEssKҖmKҖs +^_e +6y]l+mBIr~j!y[ȵ'1'Ҥ0*T3pbDɊhd3 |Hp<Hba$lIm LzB!HYE?54Я)x[0nE}4)Lzor 3p]#:yaCJ" &!X3XL\'rj?oG$,AasLơާe6ElӤ +a܊P1@nb7ܳAG\C'<9%X̄D<LjA,ohB>T-+Θ +[4BpE >0V^>(_]k- Ԃt6Y$W""+CIaD3f3=RBlPJmP(|lPiaܧ+nB-'jkO +0ckɰ
*0\zETQi%B60DZIfO-<g3Oopecl)PA+m +6qs|(DN[ӲFcF@qN\# +8A@Z@=,V:8pA>
+w|etյMlZWMoEx%ߊ|
"f,DM#c̔ムo̔rf1fV(ݤ*vvT_{o"V +kjJiH`j1JciY +{6E4~͍,E@V֪Elߪ
kaɵYvpS|Gߊgi~%I5ib~m +%ME1<57Q'R*+Ԏ,bWQ`Wqq/rS(*qf|.s8t|9>i,2;wsčuG{\z'9մm1覴dٮ,ey9̲]/&҈{@LOd,]/FF}T_z\6bF|L4:ϫ2Uf$4w^
xn- ++p=Dzp):[ispRgB\|&CvvpphyC/0l턋yR,o˯ LΧ=n*s +Q{ >kPSYͶ#n[ + +,m$5,ϓuf0DFjQONE\=;q\tnF_rc5HZq9D -ȉe6/aZt/(Dbܬ"!Ȭ=T@{ubOv$nԅ]m5zk
rKXF=a'Mzgu\Nzz{+|$n^Sl +Ipcqi{xkZ~J ֳ ή\߫ͬwzċ liԣfCWT$xGs@4nd̊m:n()Uqdlx +ϯYS;5
`x
X>nȲ}Usxhģ@<I7MTIHmPO~'tJ3x9? 7o:65tV/Vòoe5xx^۴nj\FCx%R)v:D +"-{-HC!RX):DJ"bH^KCC!Ri9k44u;Dk"M}"'ƞɻ[X%#WZvBG=Б#C{m#WN`!ksv#F{m#ZEءS)t:tZ/:whuNCԡS)vo_5tkH^[H}ii)w;tZ'`cGBO.Q"1w,HNSؼg;Xk^'`cG0Ŏ;lzήC&סr} 5,CСr} #L{m#`tJ:bNCx#`kXrNCx#`SGbG.Ta F_O-GGqE7-[i7I{R=)tQ4u4{Oʝ'-G&$>R?
ל4XO˅L8g4iUGiB f;M3j7t"[uEk{^dǕoay
{c5܇=:i
r6ˈ_jk4=F:L:\wl5efǜ^|zYVu=V#SXgjkV4=쩹?:;ӋUb 5+_scA/
ܿg?f*bWܿ),}ָ=7& +S;AWc]xQ̓*\ɴΡTG3!~H<$vI^sǓnpWahzC&AuȲ+:"\2"CߡCu3zR\>GixCF)v+;Jbb@"yC!F-v3{Jbb@Gª6E]:>o%۪s\A+W=){O'-G\Kyc{]SksN1W_J/ +ſ<Q:#pHU<D-M35{<`4*R\mqąC/S^irHfQi20d0e{[o6jƢ0{@6 +_\jm=!8OolV|m[_PǞٵ<y dRyt͘zr~q
쳄~Ю`%*^- +ܤ̔+⢏+<-__t ݃kghT>ڇz]uզ!Cܹ)!NXAj1j/;'>@ezܫ#^lo:LNUkNP ޯFe%˹,ͺY i/m;]`jxh/fMe6n_AA̹%f +O<&vSnsm;X͙ڦgM'BSۡ<64{}V ._n6N.O2cr"O5oSy:f䚆MG3~ł~>bZ5KztڟͶn퉍Gc|(t/!`a1dڦ-ܲOxmF-;JZvY^gP4qTp +Yt=S89Y`e)Ə`]%aߎ衏ٵuȳ?lZrɰVPD[6Pl$ffZMAW +vŵYbEmfe'Dq3}."/,:U/o>>W96 +sRڳǖ} +UҘXasDG&k,9G66O۳} +2}*n9}ZHYU( +ZTC{fG1Q\wt٧_+$96bWyZ֭ +NȺa!}Z]\YZQ:ɥX<qAfc3CbϮy~f +FK +^ϭsVt,Z?q;a3:WAKayIcz_ٟE%=s葵4[{vK/:I kdS{I oK/)W~/Dy +}eآW%1t{I.ZIOIgL|wBxm_C6槯f8[0|ݧ?8_|sײ}/K{c?
;XUnܤ0U2@<1-o`zSZ]78y
hA4J"rP%qS#<nGzN:#QPͿR`|/8~9㣷o?]~{?
Yb +GN!'ȉ= = } =rb9C +i/r&.%iZ#("-rH`}$XҌ3o얭$C)"GXH`-pU")E_w5jH +B[UF]@FN\W@5r\Fז W֍3r2y4Ox ș*p"YTO$ٱzD5@Aj`ԫ-UHvFFGUGenSq\\IZa)HVlv[j3?abhvtۃR_Kc +IOK87gdM
?|wW +)Ƚ,<I_&|33AbH! +ЧLSa,H +Dd +>nY4 +D+ +NҘ@,8*xwʬp3r"_ڲ5F54f|gxif%aakhN9Lxoa*Ef8#ve~U+[g_7'.l?+d}Xvކl:TvcGeNacxH +ҙBvX;=}-͘!f#6V)<쥑,4y`0E
fqA`#ƞzesD + +YH$E%5xp +45@I +2 +6% Z*Q(< +rOU@Ţ|)^+k +74UT gѤ(/"jp#,GaZx~LBwhmYD +'tԓ9eXA*])</&~*"jX1psjΓRq$aǃ +ABH0;)9iz7j,}>BPlݙџiM~OyBZ!֒6KFlL,3&4H AΩ$$,~Ty-]`ɩ$?ZnA)()V|ȖpRqL23rx$Rب"e2^xJ< +\T/fkhd1JsfCv
|p,)p$Q!Ҕ=. jX5줂j1ɫƮQ+T$;_M\ȑ &v'^ +k
uh(stvDy"tgOhi J:.
D +RR^<h7txF,$Rd;S-ńwV>Mqo*0<HT!kRWAc Ƌ + +s/2FSb"ҴY`? (06)wDz}:H'
fUy +ɩKJWpɟA Msiàq2%P?Y`<7F1w{
(iN2eZP%,sF8vRUOr|Ip)]
:%5Ʊ;MQw&-EPޕűQ"qVs?&H]5F1s}ɒ֩U֩,2*nд0;`)X%PVT3UFTX(yմS&_faJ00 +K +,I#pq +,@@$Af2d&{W;>#VR8 +؉-W*MfLuYP){1CoMmV߰+XcH<"C,qHR@F +z ղ+Xs}8|{q8 KhlrtP)J]5iHAPj-TDVl.S-'F1!8+H 4ic@0v;nhZpY۳A7tѱ +Zc +QLB( +o9=;$y\?/(лb|6wnt'sž|bu0㯤w +YX%b5*\`0/tTd\ԣ$o7" +$*B;(q-Bn\ QJ,",&c!`ֈ,01-B3UrK|,d1 YbƀIe`hS/)\RY7j#x +K&]0Bs'Pߺz"S^{nXӃW=~%e#J%AT5^lZڴF{>ElZ=FAEAC!dޢۆ*4Ch(%1/xW&F@Ћ3co1越Yu1dOtdS1hc"i~28yAkY(ڍL).S~o~(I7{%̫m%`Q7_yߓ-Wޘd~;slwlˣ٬D:3&?M Ql<WoĒ'\/)DWtXqͽ'-[)lA(h}AsPQ5,iڌUVRz<rqϒz_qpzU%.z*|c`8z_ᬙϦǎzW\v}=~ R
^#&O2U Ac +m=,(7Xd)`"ESY^'7NklLBnL5bf%4e3nB쭛iўggw| +3!t_8x\Fp&Eqfg(.Z/ DotgQ|p&*^^8Bv_XQ, )Cf
Ԕe^d0zXcA%+GÖ1
+"^Znh9FT?FZPfG0Sc6.Fl\кlqJNb oQcߘ[/1Ko̾mzK*48#td^:fY' +NtY'b(.diR/>$ݐѢCbrX5|H~U2z2:EUF]((h|v'>6O=[ҷcŢq +k=;m +ZJ'xKHuo_Ġͧ翾Rdԍ!
ju6%|FB0/*}F5&ԛ_
:+D$tWZR
!~czfYCȼ'=E{bSĜT+ǮD㰙)k%[bYP['~TaTC+rΊQ],pVDf/{EtVoVSQ~in,rW1XʆBsQ>_6F[E|U\ÆцFCqxFIU6D}mvᣬ#nBbI"WSZ壣{piߜ
ۑy;־=?=>V>WO$f#?cNFn
⣺gpFiTonlG73EvhPzIYszG[(ch78O +Rc5>k}ԣFk]*=Fi3E>/;т+Ʉ +O;8f=1]֣8?gTܪs1uIu3ږUH<<wŋZ*`IH;*a,s@d5^M-Ng2Zah>c%s.9h/V?!;} +\d=b WpѕOBa[ +
n
SR(: +ϘyKGFׁyʽ=ʼ`s$(VƐt2/*9ZoQ^ASQ|;D(x\uwb8Ab5yoZeY^(*!vdp-r,ش%tLSY)6o\`+q*㢼&'v"Kd }Q]P[RB"HQ6Q/7gݓ + +b +"]
1x5QJ[q +]# + +٠#;FkhWy`ϞT0il+(;R1Q`У +YȢ@(EQR;A`},da kC$V8"%K#ȡT-;<CJ_WkTx]sys;s%2U?deAa$ƺ$W?lUW'*.^psU\dw[%xbU~OG*J(DpWSEQ0cQ&Re*.hAO-IrYHKg'Wc'%.)mz1tSFܜ&(5]Ajߤ8QpF +]V5Mtū,u\n]2@ꂀyM>+pkJʏ9$)+) r ͩ*
oD@S)D8ʚ;d@*2l5$QT"[3&H몽?BxdJЄVv!+ʦ " Ds+5(۪"n^t*pHjQᏺF(H@URpokxE䨊
ĭ +&C +0$^e
~PuNU^Ē/+:AqVQQG,ep*jT +ϋ"TJWxM (h<ϐPZr<<@&2: +9x*XOJ +OhQ6]HC(X`9n- l@NFfUmfRdCt`d4z+]|(г_AHb"ǀ̓9'R&0dFyND +rIK8a`xrB M"$E[ԈyuAm҆c:"A-\vSHnUۥø#(J,?q12@A96NFG1h1e`B9:+:ȍߩQ4P=C̃Gg>~ d @Oh2)M-5-%*`<zxe9L1Z51MZ1)E4b j O?ɚ9*kJY"skEύJv/4L^ǥƵXVi],.pz]E%mr!u oۉ +*P)N& +VomdhoTYpOC/˓9"p +nTCgpBRIH/B;$ +$Tq9e Nl@ +hsE5͉xE3:3 b>}A$XKExݕ!XO@ ^ +&uT\0Lyt7.
l
ƅ4Za)":iYx&R)b55Ѡ =!4҄(*:e"<HGiC"4@1(bdF8eg$vuhoʣ1Ɉ!/mK.2Ms tU7)T? +LHxh9 +Ku1#-<T&
#M8Dm#CMъ[^56|H0H4%VGUȈk&p")IY4Y<2,߀ +,3fEG˯Qڊm?ɵ5S`Ȱ1+^TJF:q"Hl
"R +D28$&_7$V[*m+$0[0 +c:uvDxU!3$5s`{AXqdHwvpGhTP"'n4MY-LmFaz!/0H9)Ӵ]2l6ZGL(0*
tq>Qx'cPt`hHGQS~G>NP&"a$ .P<T 1aJB{!IԺP>AS7 +;B>N8\<$sa洡 +ICE=.0"Ow~7*4XW*q*+6Q dBSiOP^%] X2eäA~F*D +4T)GN F
PNѫ%~]`LGǁGixq +O[h52*8΄=#qS}\ahP0_hLЬ@M59Nl>f21y}0-d.&*-ލ~y@mO{)i|@f4HD$ݒsVP +Ō0D'@tlseAq7`X'smF5@qu "7槈 Ԑ25> ="=шQ&`BwI'3D@gCY"Dn&]sdH +)G)I6bSovEH. ;FhX'!G}7(:g0ʑ ^ +]Y+LHd2N1 *EDxF2g$c0%A]D\C7OLB&e$`B9\2O!+$N +bp"o0o8AJ<OpD2I +_T**ۡCV]2YXy%$wc9}1T*\=y\$*pFzC&'McdTMb.n!Ih8TAD!ILfܱtľec'tIPZ+$$
F, /0 +BL$ּHBP$:H\dTp9&0{ 3pFA$v3\ͅփ'YǠI;dqbh +^$YH+a+bK\Y[QuEJC{52 +g"IRױ}ÅNgpa2FIS5߮KK"Sq't``.p/^`(ux**qD2YNp9D(+.b + ]}"S^'C?O(s]ARIնwoY.$~_i? +ͫ3SIM.Ϲ?dAxƃ\("#x v]A0҇xFE 44d\9WȼWstIՆg@2{_7A6kޢhѼy]{Uq7Y\㮼kif͚5'hyͥuͥM +_عy;/w0.zҗ6g*0v8y_}uqy6rn.
vwΐ' +羘1?9|~y/?;u^kIVۺЯg7>nt@U0{_CΠ.3wϷ;{8éE?/vpo:%p3J)FXÞ[Lnwv7nqíj_];(o߾m۶m7mX:;NGL}/'Ϝcs:Kkl;wpBum
oЗͱ
oڱm57nGэd-]>`{kKμhH& +ނ|rgnΰAz&vk* V-[:k({}SYS`݇7.mxe<8ixHͣzr3.BW\ +0FC1sUsc2$>,:6 +0c*tYYx& +=YC}M5,'_ZLOx +nh:٢سwn;7ES8hTa1+owOJnoa0|yk۠a#|̾ w\հy\vS%)ηi/*%&2S5ĥ- ]v:f1|/3Ǔ5-`̣
wz. f×wM<lIK3MZk :Xu>4'gDfpxW61Aoiz ]eegMO>QڷdxȰ쬴⇊Up}RFfV"<٭0,*ggv1_"<-)*/Iʆ-`2uƑ.gXFnMO`s\vNїCowʰ?;<'+WB|ƗWUܡ}:wfi
cԜdf!G@*c^"#'/?/'fe2 Yj4m]a!fnA3tPwpXC{G
K!P×YB߈촞ʭP#b*c8<dxм#Gzs{1Bw847jdac8<01<fa&[Gxaf{G}>zpX"_^&c8\\bp]Fe`_\42//c8Lؙ +pQI(p śy ppݠI[1|p cФmnIeGײM\S3Y)ojåԖ1Iڛ3#Ksú\f0\\Zg
`NAʰ3ZpcNpIS;c8@d(kf1>Ñc8`GH1i0#
p4Ñc8`GH1i0#
p4Ñc8`GH1i0#
p4ÑpGp4Ñc8`GHHaGH1i0#
pAÑc8`GHbXdG&Úh1\'0#<XTÑ0/ȰPp]|%pdb;vaܱs'u й3~՝1 4iцS;u钀\e%\;u5Aqڌ1\ڲرKbN*c8"U7)'$&%vV;0#^:^-6ppI[;h]ws5g=Y;&v=Q0#]HHѳ{bGpD;N={HJ4oB:DJ39Ac䆶rIdGMnhtѧOopФ%0ܳO;w0#&-T{Mӽt2+%opdФe{-wJ=m@V0;<|\ڪܧ~v7 +{3#Koؽ$w2#Kotw2pЀ>ڝ-
L馷ou)2ءSiu +%#[z + +<Cuo_n0\L +۶V {F1
BC\{c>d؎-(y#F9=a+*r,OoDAvZ1nӁUxGCgakFCz1[!f6QY~ނYvƍV +QU dNf>j +9dޝ;l0]xi=edྑ9kw6xpZmZ2p{{ԔzpP3-RJfajd 2wPZɌ0İw`Z>.;Z߉蓤c3g +&åO\maOؿ0|=c8
:ß1<1\`G`ec8l\a1Ñc8`GH1i0#
p4.pWp`GH1i0#
p4Ñc8`GE`1)0#
p4Ñc8Ҹl#\0#
p4ÑaÑp0p$Q;1q;s8o3|'cw{axt^]jfk;Õ0`xDv5u +}s?0q⽕nÝz
<dH^^a:'=QY!BACf܁}.,%CSO}a=7q]eegMOqU>8gN4Wk|rٙgBM]J6k'?P6g2,$MʾU{wSΟcW1x;;<oW[y\ݴY-P'hѼuWGʹoruF[l&3s_\4)DRlH(okH]s(k٪u;^V :uFt+:uJ訫2ucK-8,:kbbReT>_^b٢3& 5%|\qsp_R
a%4fV7+izMHԹKR2d
K1<<O6,kHFڀ>t/4eOg@̬Pt,'/=n̹^Y[oZG
Qozg[^12ڌj'j%]Ik; 5=##3sH
%W8xtŘq &k|Ą s壋|@Сڬ +z +|Ecaѡ?ć'O{/.֭{W=9(MݦA,}߄1ŞAnYKAA[ueTOЊmI0;p'//;bTQj;v'>2ef=SO͞=t] +lKTCT
>裍7~
gm/y`Jힽ?p;u|ԁ_d{͛fفS:|O?9CUKWZj/ +i{`b5vbΟ wiInd,w8
H_`G +o:$UtWJ=e]AĻ:$]m{xW?O?߮|^d=!W<[`I[Qwq.9~_ƺx54s8? N1^nIMn%s
J~B,*]*u^]E U$Lseŭ ޭȂU-k<d7/+n˂[/'3xxGn'Mtz/\(Te47%wگ݂"+.,C!t7벫z-H$? +unY4nN%ey@/ +,o%7'p2qF/a V(FoE[ST(2,IH/x%ޣr!x /ܒFkD*uM%Ttݺ[eȎ*I.Ip@^P]<ѼUssbgCa$.-K{]x|&$eyhZKƁjDq+p&B+nl:yh1YD:u/R^&r>Pqh:zIyۦ{2O[\RM:Rv+IeDP.QDlX#?i<ޯc˪M2-$H8 A܊EÅD}!)A/P@5T5+*PD{UWQjGՀ4蹲tsD(ɜ[I_5( +UU7)Ԥw듔V)ݰ5^y`,]%.j}!W\hӂ<o.y1ނܾr&O,.)vIHc%G
693C\I"U%ڙ@)` +Uhn@=
,y^Q1/Uhh1nj9fB+,v8U*t-3+g+E/lÐUϤ-3k- + +bEMӿM
4N9W6t<Epz$6ՌYB5/Ppj9VKtZ7[U_a̢$ěE]9efM"t|ޠOin3=K%ti Z/)*ONֽVX:efM$tl*Mft{7~%I4E +MU?ֻY%tgºӪtK`Gռm,}12Kfx`{LW~-d-UG +hٟ͚8Q[Go6$ +OUKȨTŞY>!0B끶bY"fc[/Y +gfY4l lƪRՇtfQ%360ӵ3g=XY"0KdzYs6aVl¬"-MX5^2[e0`z^e{fQfzpt2KdSf̚Y5nئ*MU|K5O%6iչ!I!ӛq2fLLwɤ~i^ifcpSmś-eVg[XwpgAUN-j<%Քh[1
+/..(j +;dU}]_UΨ]2O<ye-r>cz˸&EρIk{PAvWPYQI,CZ}m},G4gSS`gBChiv{ӓEI0:J[͠AwMz:6ߺA5>["͠m`jCF8QoNͪp{;p!|Ј!'7mrDwwA[f͘K:xIfBf+_3憛esgtG}ɧvꝇc=h9SO?N>ྲྀF:<ISgv͘6C~@9O#'Õn"sþɞMen7~LDiܨ{rT]Gs;1Kg믹useÁ>.sos̳N8rNq3g^9o4KΚrW&sx#N82g5ͷ6!A&1uɬkLDnkQS622L~sxѧ;<c>892Qamxt7>м[?«o=̜ќyE6ab>4̍}<be$s#gȑ'_<9 GٮM4skod^tґ2s]ǝk//Y8%ۥ5v ]y,}7_ynx؝7Nut^^y㭷x9#s洱;u< 6Nc^|ü^$̹=esQH[rKn\2ͳ_9/>ǜ?ů'|W?쉃*C&_G^x냏?לQb g\},[ɪU\Sٜ2{T/dn59~uهpGkw{}ε~Lgç\~ok^W6>_I9y˯z-Oҷ;>^t//_fukZ[gN.gI+4wo*~.|2
![/os.}ѫd2Isz/ۯ?}wɼ+PR>G^]a˦+a6yK]1yzUSO"$8bڕ,L0[#e6a>ks>˼섡mGq7?]+;W|O:` +lsOj2WU'؝́'_+||\f7fs=?Jod'9S%YK̲/4O^STRgRc|k^{JsۃO7$ٓoXfd>08ו<jZ߭E7~%ꭅ7Vl}MO
go?Q.0o3I&2ϺAfr62!2TL}]p+ҟݧn>st[I:.Ō<yܮгoyzm=$39fO2Ϲ72-ѽK? +r?kLi;vcϽW21{22{#|go9{L{IGo,,z2,aWN_8W?{9W`gJvK>ٝٓz-QfcYR]SM +Aެ- 34&}EÖll +<ThJ,[ߤ)}`3ݘ__=ˤjyףpٓzcGԘ2^_evLzיf\A-ѯձ
Uӹ@7,t&R>+219^Dz3g)R,˞<*/ps\[N=Tu2{;kse>eb,_ohrؠ4hi.id>z]驻7pڨ*hȤ֙v[|ל|`I|W`~9rG>2gF/ޣ{-2sMDo`W[uYG[~<qdtŴ2g\˖/72Sd:'T{AQ\K?G<uk>~{W:,]o<u_x]rߑE 'ç^&z]ן/9`+oͧܽ2KY"JvӯX嚯VѻK5US/?5k\cְhU.]kj
dIe2iGI抷{`yڻOwf?b>\هMOeLŽieޠ27xw?Xr{o螫s +;,_b?]\| Ͼ{oUfl:+W,%~ũGZ8{Aw:Ȳ5zγwҷTcdnDbW<r ^.~>#w_7sZMm(1,|K_XăwιhalٝΟsO=Y&y8'hd>ۮ:Cv,"qa
/ɇ悮;2{+s]NQqǏ.cna2?ff;|߮xw|ŧ=rMK*0'ngw7κiM;֍w SvN~w//#s_'^8;ϻcO0w2#qEWd7R
<,/?w}9ؓϝy٬}_/<sQ{By'3oWΞ=K?}JA27+If灿f#N;g͞}e3|qGa2+ÜuF)iOc&LzaߝFu,<y7~Jv>casgL05|ร'L;v1vno9iӧOr1G:lWo2I9xzI'a߮3l!9eډӧPCRD ?u'Nrܟ&ҜK
a#Ua;[I6;=(0G;hDlږaSt}oI31%#G>hp~y,3_Ws?aޝ2qK-[8mʾ__~d6r~f}7MdoC~&cdnla&קuos`3ۃVz=ɫT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJURT*UJ?*j͠mv6v_5elUf|/sf,s#2x^7jD;={/"5vcȈ=zmnP*b͜?r!csRpsw=y}h=0j- +7ܾwW~aںvM8yOއdpޟʘ[
<ϼG>d>{g99~>5Ly
w'Ze<5oٝz5wϘOϿew'uTNfS^sVۀg__~nݺ>_wK*p']u>]v7'u'gŷ.|egk~ ~Kn_}y{Lbޖcwf2\vݺWΜ<o s.2Z.}/2oxҾ9SW}XRr_h0?{WN>;q)W[l߂́#]>[]N2ĜܲϾΖsi#zb~黋|ʰ2i;rU.wE݉|gMf6欛|{:.w_}sfMIz-O2|Cל|@ sO桗WVe~L栃fyd=Q/=xIe21W6N:q$Daԇ]o8mԠ +lwǒ3]jsۃO7>]LjT߉<Tf#Ǜl`~k^wJƻz'sUA"sϿ~;Ot!ۖTv_p+^4Kmϼw>eR*APdTfn[V*čwr1I
"w=/ZeO|趒 +t]tϋ<yә0ͷ2ۨ{~9<ޗFf#sSi]O|錿ޥj18y/eo\a칷=_&1=\dRJ&{RT-nzΘ +t9fkۙnur+ ~dK5wL_Ϊ5/CU`[zx~ݘOfi62P*pe6}_LfIև̞^ʊ?Ef.e60DVmdM_?DO[C6{C#dO٧QVF}Z?|:FEfo/Kٺ1t6ݒn[3_fs~ռ
mЯȇ2L\ÜA;._5ܮ̘$tMsssfE/.ѣLhY|uLO֘"s'715ZcĜn7?Bfr62q̘_Le_o_}#Mc1].7TnܶAd2_25Ok[Ĥ?Zϖ=7KpBV XfIl9fO2 ?Mfy98#4236fy埯5VwtQ.]F7_uZTڿؔ}Ozңç^~b\Gw_ۃLüO_ sĴ+dgůKdk)|5?c)Y2rO_^ŧ+\Be}9]Ge23s͚/{ieFřU?@?t2WycKLKgc.{3|cw:H^l}9y?w^]mWZG6뮅/ފFeҪUw/|eX'yUw/z2`%\=2(| e2rҳ?|e3[mK_\ăw\}wܼm܌4g]xm\?yГ-}en.Q&3z)?^';c^t?<~-W3 #tڥrς^q#^R'#xڥsn{}Ϳ/;w}2f=jSλr +kRa#vR-ݝw#Goݶo߬?.?|ĈCks
wsȰ#O:{e2s 9t[pv}o\KmٴOЯo{melF&9`=D9j&>H-O?T1KXT=ӯnu줱-~;;i1LuBm҄'=u{rw>uۚg1nI[g#I /?f'L<j4rZV3>\i.4ǵN>qZ7mesxK$7.9w5o6Ϳ\?mw?m5뤭ӞF^//i} +v+`G0jO({3G~dDn{n^9Lr3HrncR:qҚ/b,7{Yn̙n{eMN48uO<zbkJ֝JYӸ"lo~2:^Y9ݠ=sq{QAaT+>g石w+kY({B;ي*)QmN°s5d0nWa䴻iBb݉n-4H-Zna-+L波*>{H+ +{uCŻ~*>sz{{^&^RP +]gl^F|:ACg։ 8o4mxi1}q'!J|PZaYI:_)Հ!*{5i3Tk"2m"5uzPׁ釽Qx;dH34_9Tj"VV|]gutf5w!^=.\P<'__cӠ=LSNMo'CΡRk;+uEVz<:^ +`[ᦵk4)?.@(ZC<Ȱ +kMUB^\qUmVղs5Ap-UIVa0/Ը}>ցE +ݭ{k;C>To*S6_٪ Zf'*u&jVP +ڧm
*ؖPln)o:(jۛI_(IP_Չ5͙װEUlQ[V%[b#{
}\!O3i2Bgԃ*RQWU\+*Vhւ>UcoW[|GQ> +@Nԅ*@yz9vT1Uj*U5W ՃUUjA}j+Z>_*}Xn|v^΅:͡ZJTUTsڬ@ZT+ՂV}Rm1j6q*=Cڽz!5C^UmԊU2"Zj>Ui3Wom-RjؖPln)qzk^ې5ĐocwܺIGN:f8чC&!;5k͙݆4;v`CC:;]ƍ0QZ+x&3?[֝\d?Nn1
75^j0ʝ?4_I"G8q:倝nHC0i=5(=F Fadp[趧iZr ARdZEۍnLGK0
]*;ȤiirLh`N6Ej&c0)6HQJa520 +MCq.8C-^/V4E8[{,r@Bm.ORʐ*4b3 5O +d7@/> #s'XS1B "uf9IXd郅IFЄ*j;lgm6˧<+ :Wqb kaKӕ/u;%u9(r-e "I]re VRC9ѮRLEGK\ec _MԙET +QW2̹8kz>KVsQ((h +V\VE e<x<ae +Ji| +d@s3jOz=b@k0GEsdZR֕aveFrRRB:bVkLjf^Dp3UR@U0 [/0b\1!E8E8<P79m$γH 4_hȗ̈ErQ*` +njY @r#t /*kʗaDԪ4ED{ga!`t\L1XJ0%M,V/1/Y31Y萩QxԒ;~z$לiQwFeK0f +R2O(H!(` [ Su`z$2.Fu
Y\7|g<*D ++U)N`e-2T +yXѓ)dH7u(`5-IÆ5eeH +OQsHaYd2?d͋c[(!#U3S ˡFǑ,,ފ +w-ߍiE[!!g,iH%1`{0-\Cu|SܞHȆ@%]k+N-rFK`#\Ix0 /)3Y7?plim.U_`
Z~2UO"Jhm:ƹv +q͓O_ųrLG/Z +qc9bz3_neSjWZ`fqeI\߅2qWtH{m`#Xe\ @2cZjp+ElKpJa9(h܌CP7`+S֒aMGqJNDlcKXکC4,帼H֭$esԘeAx,? /3AcO#v!2Ac#VD4c:7@bo3}d,i2rM^x-Y3u
LF +ك.gdKBZx$ǣ*C9Eż` Kck`NS8<\v"Ds50*S7.T;ǟ3ʂ>Dv.!#$h}sEӏY\8\+G:8fWqa3F~S##aѥ^א'q4kc:Xae> +tPILwǃ%! + |2r\lfX|}#߽,pIvdH-̋tQ+,J4T=cZDbq?"8VD]YO4Mgic`I棐@d:<VwpΦf +g$̄x@$_p,I3|4/,ҕr|~YNB/h.3%8(À^{j2rQh$F +/SzfHJ[@'8-4VKI3OKQ4(ܷ_?Zy%i9 3PDe$j6"5d43 ")D7AkL8OwO5zOמ3zM_}NDa" hl:uiY3h\aBmnľ +]`/eG͍5EC5m12ʉcD1 H$I9phmRsT|q(E4F~F0HHP! +Թs8d.y]~Q|%FWR%* k!$,c.4]>ԳNr#RdZW +$bC,,pir 9% "Ѽ4,rAЉvûZ,Fr +zfR
rF,^cb>! j:wiY2a({z^ +hNrnf",w +<sjʕΡ7qy[h"\p .6\Zah3X
)| ߦǮ\A!,>0IaJ̐Xa&cfa&ǥA/G?+R#EIZc#ORp%E8?l欔4mN@uBJ䒂NʁoZ|/T,rsE轅4IH>D +(burZk$A:"~[a" +]Z+4o"%- $%Uԟbڸ
} +l3*;5]5%ƈ6?ְV1Po$s6Q(Õa(ve0a+bs<B÷_cn!cY˃}N=0I|BJ=CQN^^^)ȣ&e͜K|xD<rao,tx +H<+`&5X6{*xHψ +@{H;d J +2E~LQ +,gH0Z"S`Eaؽ{qةH'y!
4:^]#pϴJֳͬv|(Po[z|3'`8' +pH%YY.v0bq`/pvlk^E1xl,NľΓ(` +Łn8ތ){ +t +Ȝ +ӍYYƧ?U~X䤲jh.ZQ9b*@ADxE~l!iKѬuj4y1'PdNoET%\JU(cP5FeO +[8f{s{ri$pF#:/`L
8!dIa3X<0!'_Lc2,"T,Z<K *tߌ""'Fw'p jfbKB$"{n(iTv +a\l@g/$wW3'ŷd$ԥ3z0uf0-E ysbB +@ce*L$$Y~,л#[G'E\e)\CmeH$_رyٷ״Q7?\J +18l5vNlhw,d<@(WCk9\XGۗ8tMѿ6XiĊBC0hs"Hd:vsݣij/D--\
d~C۟-4qYfj\l-y;Xs I~<y9Bmz$]k.Ԑ6g$Z㥦M:XhpKh!@:pe,x0.3 +F==YY} Br,TKu˱X=VZжHBZ!C"Y@#AߙJ0nh9L/kȮgeA8&BHN+izhFR) NĖAEp&w%'ʗ +Q%ho!1$B!ˡʄՂ%?$(F@dRz0:G[,S"V@@8P/NYlmu
wc8QC9d!c@;6"C~x\![àbŒ-qLoZrДhsvN}BAQp +/6si3!"
x+B4]ƫYC|?'gI%\A0 2(DQ3x^t_֖m`eafЙAm|_+"Թ)5#>:u]O;?9r@ +x}9x65,'q#
d+ccdL|s@5<1JF8
2xߤTKf!Q"I!.W0rB(]#["$dEr#y9ry#k!ˡYLWT~~:\4Aᓄc5K1.qThQvQ9-b&S0É!ZO%M6
mf8|Үb9t@L`9u6hCHawZ'u +^d$ +Y(҈*dlra<|96E,"e +ZR,`׃ += +8 0GQ[R "8&ź.<J:-àqN9#BThڦzHhˢ$bXY<zM/VO jGez64}LcT)hU3[rf$m1y+7_nu~Ȏ%:!}=̾OdWB +X+5T:Q^pLCET}䩫+$Be5GTb+u6 7{) 6iPg +Cc) +e<M"c)n<d:Xa/3c焳P46,GTW@+͉g.6E]v@DfZ-"u)B(#+d9V,Q㢲+:612] +d9miNZK];5sz +K4yjG{1B_,5i&j-vg]CTk|͠P9I*)quLϢ
L7fry^g +Xӏb=.FS/#YƍC^5O;dG>pF+m9O\RD3.5Ej48GH
+PAr=5 +d d(jgIs1bVZm0|!=xP1)0,╅m`3]rPc/|@Cd$mڔ5$ \"Kd{Q-Pgyz,RUY!iC<(dLo(d1hJ:O#a)donpζ77`sS:-B4;Z(XӸKJAHP ҂*xAW##) +Q +Q2aQ +5KenE&ώrr퍗K8բQq
+kA[L"YafJ(eSb ,g(>D7&@Ci^a5 'K] +IX8`v:Qd]cѡ5b]_ⱺЩ[b^$r8'-DK=Z }w\BZUZ&Iu1E<(/p*Vhk g$d/z82]\Z8Ý"lmVD3cKe$x&,72x֏͡(G (IO*Y,l)6:-Oz@.8cPfgQ[ErO6XӋy 2gqI#Ȓ :z^&l䏑d -Q z]'ˌFV?l(bthH+49|.yS;t,GY&=tÉƼa\e`ND[p7t䚊Xc +UcsZ@Z
'A"^[7*oD0S"rku+"Ƕ)!!2ld5C98\h,<y:H#5 TDpqP)_Zi#Bayk[!D f Y,<1by"y7ZV쉷&Y, +3$F(' +'`i#vE:da4
8r+OA1IGMOÍ}~@A%s&9&F Oq=cN!oQ6&?ta-vqtG9M(@q0B[ChkǹMb
NXFyp0C3W +Hz
|PfƐ04 3mlzȟc$s;,Q6`iI1bH k8wFh +' "c?\( i +Y,<| QQ:#*(}fwGdC2,`2yH^lFA|Cdp" ` +u鱥!3ψDS^(eQv%L_&z9@`%klXy-P :%nhjeX`=o!]>zK13g80Cy,Q)11Cñ֓8yz]gsG`wj"4{ +两I*T/DX +~-8 +X + ++Bԅ❅@MRVX}^ng/GҖT$:qx,5p,UQL5"jiVL93J8^mJ +{N5}&1*\C[TӦx͓pK@bz8M6%E0k> / +Kj.WJW$Q:y8R,,{02c4іsuDc>
[Zd@wkEIYգpkX=֗:knG\b1Iʇ0aKʇ#Dl)Lr*
k0{Â[dbJd9Py56BEpoˮwDoEldecDtNjA~.iX +o8E.XvcʔXnGauGK3/48͡5vI܌"u7$Y)nF:ƘwsXB*b9#t0>ԮBX +jQw Jk(;H)Ov;qGT|<uӖ
||5t>-28Apl@NSGrEA.oQK%_$|;,P|ztb;0Ӽ\2E" +Vc* +F,@ 17e/@<#+mrBˇżЦk^(cY{W-7l3!-XN +?z))=IJ.#ʀ'|;}+B1pl}ҘԝKuE)plT?q8AsJ\[@3vCaM2=xQw˨8XC̈pG=NX +m)^h-2(@p*Q,@">(b<egl
Y#hKOXB J0hGp$rLBN?7ݨbx 2ޠgV0R+ +m4S+%!V?qfZ
@MEC+0:aέKB~e2t)J1P@ chIe8~F (~OPBq"J+0H)t*nDq!.%(۫o"x7>8o%H4=_W fګ0$ Pl<R`g,
3hўuT=Fˮk-~3,h- +}jT1p_Lfuư}m +gdJ-c,@?evGCxFKOzJ``14-:`(bNd,s%-Pf
d.O`qd81`60hqҸ^ЯK3F@7`h4ړ́@f=K:q4)P6)XqP cܞ. YYhހva]S*,,йxu|FV[Px6fц/VGF +.f /Бi?N4[5tm?[b
\e yjIN/1 "Y}kEJܞ3JB2sѼIN +| /5=ٔ`r!_Por
fa'^x=8_ş] +טQElI::"6s^?IxuO_7tL}F h`IM!G0"bv2c;[gVĆGL +4"h +"2dn0 +lj#bǚ\b Ʉ-߹tGZlkb{.R<.GVgݛn
j,u]kkwS?O!s(K>
ߟC?'`[LnLѧG!Sz Yb*d/9H"앭9;u-j.=P$wt0ɬ*~U}Mjwћhhџd +-_'pi6ހ7M-ކ4m`JZGFreYI6sЧF}Q$u-B@pBb`9x4S*
ExdO_4Οh?}FY~O[ikik?m&M?>ZJ,'SO>ei?>gT="0l[[5Qsw_]Z34l{6?Ra>oS>W>||*$ ¯Hөx.4?翳PJ(TE jm!xW+;#09[0)r]<OR(AQ<-PŠ-HNa?SPJ8K~ft6k*<eADT_UO]vo6E$py'6`6[De-P~W[UZnh*J5uɋ28y1,'4 +?Z+& +{*bXd8Cr 5P5i!q._dIcmV3+4,[\~֖XimIf~|f`fOg[SSC^YBy-Jp<gVUgKNrV,Dht'p?y:ZqRm8-qCK$M(ɀہK(8%vhlޱ͢~ <-!nFC͆XC ^4A$ +H!;fDA*шLF[)F1xP/!K1!$` + M=⌆x@l`b, +-" B^w1hPp
E +< V_uq#yZ$^,H))>6tgXU"OI8fEMPnw,zT,f1uxCun`
3 T8ضq74L@ִph"mZ-^h@) DX"`e%!(bZ4K Ϙd(4;V +fw 7],r,;JӋjJ#m'K#8hCޮuޕހ],Ue@}6.@pڊ +
9lྨ-&,6Vނ,`0^~_M)TODu8&89?t-Ņ"hZwȺ/"ЌcAWVo2}4%P*(^E*%mMhA'7U$+2Hw1,TXkN@1e/C +
;ѣ'Pe2b(`)$A|7)^!,L]eYO]pX[0o5P&ӅM^ھ("0<c$l+M t"a_I3IR$ cR6X1d_E#<ӆcAYJuc(pÑ +1WeNmoزsfg[eg;AU봫N`3ω4 +ͥDylKʙRM4Y_]$iםm@{ـ#U\Pl6j{a#@?mr={+T[jIGtL,6,ꩆ^"'<aCCƏSW +8zKc> +ik*iv\w1E)Q$ì
VѴVzbG+Qq+~G&]5 co?S<D"mDht.i&/iL+[U^l
il
kFj9_-?cTչ4樫Û~ڕ&
y,ֶ'kfiKӼG[-"7%kw:!@KM鮖x!rS?&~BH6yd_:q-b_քD=p3=ufL>0)xǔ1 `踺ʬ:J16 `Bc?AJ5~EtrTDV:s5ٯB)SXq/ԕ,]4XL]SfڰQU/RI|C"EGëC?' +9m|m8NMyR@CUԞ4iąed,VHL
@&0&,vhj21~x+ftaN5a2N\:y=NؼԓSd[o(W>X`lBb5 +mE vVY,2i8Kb.iBUnNZD<,{!Q$sU{siuar-pr?mڣ
^ϔ!as(CܰV2WF )3gXbE+'+I=7nYƼꌔިe"~
27;,Ol[Ug.%p6ekPq^$8m<1&_DrR{Z<~rU%ͮe8_mֽ + +je*5@sY洓~x:'쀸l|!ʄZᢏɳq1]lе{+>;xIrrG̈#7[./2O'reK=^OS͐sp#W'.z73bcg)//ds+}z=i)ܠ2I""rU.T~%c#ڏԬX>,tJ8J-6 MJ&"ո7>#ҙt9|F} +Q|D&f.0wBsjH}\HvBj6"pjĠ߁DLuɕ1x&{QT
H$rA*{>tga^-X +ͷi9U{JW@n?OoBz;붹e%~.>@/Z0v +'d1\]N\e˟u:DÐ{Lnzp^eW0:]$ <%&r1e2}- +5VKdLì5QlרE bG1ˉE-j_1c1r
1Z/{ν^'L̠CD/糾~yN0@wO 0:vQXfǁr +Q뙵5by%=3@̔tn]<]L34AH9aÀ/O)1VLqO|/6Km͘f->+:f& +YWY迂)Qk:z<ǽ+Jq{_+De!ތ.fa']7fC 4h!|2G3^(8m|r'A~;F"0Rvrd +\_礗a k
/,w5-"YOV4'z\ʝ2rsK^]CArsZ;2-c~C5w^KG̀ +y}ä'tDc@BS}\b[oHb;Z0Fƴ*Uā
}qt;|6`w[_Z-V(}bqI5Z7__ưɏ)ih2]0#:(AǚlDeYУY.룁&[ًU06(vh'IC<;&ƤnpJ*j3[MrLtn8GܧM9O)H/VpJF/_y9h*f,j}N+ o!x?4+#Ͷ@EIGÏ, e9}3$G0[2 +GN$,# A6V +eՖc|QN'_+`FX˹%9~%A9
lϭQ
ʘ9Q`vSj][HjKW\VI5ɽ|ɉ{]&tݿbЪ-cjtSY*L4Tӻ\Vp_y +\tfgǺ1
mioO*`RM2dXIRSWUìǿHs0EVeJl`.Qbdd*F +\@q{6~|z̽J1Ӵ;5ܺEc=+z~xP_8||No?r4 +%
~/aH|DeZ!gd
>^P%?$GdԨG4c@>ƿ1'*+~: S7i?h}uOp;+[^OdSD-U*{z%^d7 _Z.! +Q9{(M'.2z3?Å xF6( +R=EB@*K>]}/@b:fQ}cѰ|0a F9Hy`=y՛*P!oU|>k i>q!㜞p\] +Aٶcf/Tkw`5Ⱦ'Jɽݗkv9i{Զrp7GJNLWvrvڹ<ӫkκ.\dg.$]3E{dzCړ32`OGY{lϹS{]{6wˡU^\^ws%U7؛*c&nի~s`yOWK
ڻFD>R}&iͮ[f_^V<]uN;v2#p8sduzrp:bك#I͑5SqKGXsUј\vqKvRivt +NMTZ8^bgz^^Ԝ&wV:㸳p8[8Ź)%)S+U~=q9gȹpOO)+v(,9-ҩi5aOEN"uޟe:/a=}sYqV]J3L\g+Mj䪖>W']i2~s_Rwޗgܿrӹ1wNJ{rv]-w0*t0gNؽ +:ȍi?=']VO{sOG=OQ3niY@yq7<oEWac7*Њ֬><H=ob^N]'-|L+i_=<n.2ߋj/D`Mʾa=fEf/ҧʭJ[U~w'҃<}>(A}Ċn B$Щhɱp^B8t4WL|2xRE^ zgN{/t.8;!Ǭ5\@R['TP}C/jhnBaݝ9.prvb68.HFdVŧH-#wO l%A_;9+剋eIѽYbD$y鋐oD$J^<EvɌJΊ6zSRP-u䩷C1:þ\u>
O4IVԈYfLw3s?]2YЧ60/Y;B&b#5
-9?} K˧#Mh>s݉w½np=+<ov}M>-ZG8+B9fDRPWYKR"%bݞWљ".~*;@ghUZ+vZ1&SDzY">Vb΅'ug̫rVJg;{ܽC緙x/Q/>-,Z">&.R ++sy('.ڼR?6dV$S<I6.{?SqT;LuA#@9~MWi<'SLϵ^ +FջiW'}>_gȸ<hO\hYyT[8S~7xRxs:\sfx1KfF_=}Γt7U~Ս5n߮w5.W {j$#}<:7x4Ey8^}5i{4k<bu^]V{C`:c=?^ΥYJܮHy긙&ɳtD/f<ì!j/Ջ"QGٜ"jy2WpMxIotX֒Ƕk nDuhQ7>Tfb7)6xF[mϥq[=\1sQ
vsZ*3x2}h-8ow#D$P
GK(m21LgbapHq0Rx}ʄGw
"N>Z+9$9쒀>xZzT+ldoopduw0ɸnSEnP1i+fً)nY֞pߩ +,gB~֢)`9pL{XYXDUY@wbx,Lt\;qiZlq=5"4VF U;>ŽH95bGHX6B])Fz9.ޅx +Sݍ4zD{SGvKj4#{<[\eZ|am"}R(m#;>Z{Aӳݧ- ɻZ...<[rRvôREYl=V|V]H|*)t{ˎ2D^mv#-ytiJho!EX0=<Hs'\@wHS?' +9S^.N9Gb߿7w=xuV$c53,aVU%v?o+Ş=Sʞ}yZv4kΖwkS㻸Do)O[|~kj>brb?c.^-3!y@Z]\b3&:)
NN#ՓUi9JFLuf],ʈ|&e2H9߀fpM`ohςI{P(sX*<D$~s:#Jsȑ[W'^!Պ5(HG]+\+Rf?RGً9uhAw%!+E`y^+RyF +A2Ꮩh1)n.H'#O]c H߇TFHMlcJ_G +}>R8+uWu.#bk84A2Zd|uU6BگfN +ڞ:|8\ʮg|^AϽ5X׳lM%0M4,)DݭAo&uΎLd{h+uvǬ̌dqQ0ĒUS[>0
7td$g]sdvuћ\IJ"ʐ :;W߉f0\+1>c1]L&![u~rKZ ")E-!wR8V39
b7a
7ߣzÍF҄
s#MrkVN]+v}7+qchdCV.<[X7^MIB}kT؊eލG[?y1|6%Φwy7ؔlŴGV]r,#ufSYrݼRo5ͺ<L!+AS+yW^vn}8~&mjƂi%k{'A֮vya{Y¼Մ
=VQ`{|5Zu,o@JzOs,?"V=KTNg+ҍE.Y>c<X'>r鎞!K[M}nUn/<_5iP~l/'QlWDw?b[yem+p7O'|6ER<^읾fe'P}+Whcz}GR;>ms4¾R +eOW6;rtԲu
w-pTG-?~;2>̖Z&[ȰK7toy-- G'A=.f>.o>r;tN-G~x-m)[<jl~]+ݞ<7${ؾ^I2=I2)I$'9$99 +4*" EiRD Eݫzw.۔$3g[yӝɋdxTMbTXU6qi-*2 +KC
|5pfnեXkOsӵay%R_9 V+$Kфvͣ)ھeXURSJ(R0֯}麏A/6 uZ,q>(krfA3kf(q[,&ә`p5&7mz?vF#nP1R#<Qw~uQM屽M.ug@MnRW +j܍O'
{*z1VVXOSSilii]Vzl&rl:*ë4Nr,],T*Ž^igD5✛f{a *w7^3213Djw%Ez1ཝL]^ԨhF(ڬf:3{hb6r\nE +sӍxc%+ߍb®cGCF+5콲˝IcWFNdo2Vnu(Wٻy|Z.7;ހM3YfEV\+zs{Ԙ年XkP"*u^E3Ru'Mcg6$W}vUn7g}J֚GJzS4sVطbU
`֚ҋZM<u[S'k#T`A*zC/*OHY85t1yJv1|`x_ƻodPZ|)bnv1ömkmRbk20K+W64dQ#K1E惚IZza4o6I\;cqCFyҋw-7b5Tbonf7db
VAc_?1sUnM-.47,ـ8NNߝ][ɴlesrn:95kr7&oAW3<F6"Kt:т5ЙGI9UꎡR947<S:/-GGVCgA'CgArw4 </
+I~fklfo tT-iVzMY9n؊R-1=k2ѯ|ɍ2jIexŔ$̖?6_Ŭl?a[ >TMREʕ-ɑ\VAsf9v9l@5a86vѝek\6{46RxMn`7'dM4~cfdc*k:淳Ԓ3r"b9
ϒƭ/vJG4å5y=zfz+'=V,\(m5d!i{1X:3\8#݀z$O&j0QՎfޤg3+E~^i,|{#/`*Vf>S:!g9"OYKe,lY=j.foS]^Pb=dS/)"UNz<fk,[i떖r@h#Y,[vdO^O6؞]*s%'(%;ƌUi9aes-IT(%LGFa6raが5̱6*vd4Ӭ&eH-(!FUbU-^.)Ɣփr2XT˕+;q$_ֹԳ[ҩMfI9U"cNo-WdO+swАF+25{Z{<vWd^ipcVd|n"hGSZ[ձ"#Gս"Ct݃+"R-jYѬ<{9V9lgZmyI+ˇ#߸HU^F +sD#60nSբΈl#y4o=Nrb2b9ʸ:Me\]mNp9WWB)l B댓TU^64s#ٮu#px8uUx8ժB9"pxVxx6و?N\kt<sϭ"8K@#t#tc +6"MkӮs#0UffW*RלP^Hhc^Z1̼6pmlDlleR6Y76;zuT!}=S8.2^u+.H +dn7).`'i3 +#ṽ.;?SriI1 +7ܘry~}ӻ%U3>oV}>sB<gDqKYj??~{XuSU3Z=utY9tFF Qtz#ⵞGi5 NRG)Nt;#Ӌ>Noзh:X: +F
n|J
Ӌ6_w4*27.NopYm@4T'L5$N/NLz.nt4?N/FUYb5FX58뱂zlوh:X:Eܠh:ݒHat&F44Τi`4xc +tNbF:?9L +Cz-YntChk겜: +crS5HfZ:/tS^X=#Έ*ݬ5mtg#f_q.Wy]9J@dtWk̘)Uq̝Ɋ|]m vkNfk;7[G_h1f;yFgq8 %h
勡:=]v}'yL31ξa_Ic茝8ZGs"e&<aS9Qx:7AWG +쒼ȍvXbcFyC)^|!QZc\JTsgN-PA$:R:]X3 [-$5bBv¾v|Z_˰(o3,J^M}g+tߌ<
fF>#E4ÙF`8c#F۟=Y;xWg5ש?KxzOS;gIb3/{U6C7+ƜpGQϭ;Ǻ愻NʳOdyR^CNf9:~{*>{-qh98ZD̲e3Re`l+V%Y +u.F>n'߲h,n+vW9"n6$\:yt]+k<.Onhs
1l1U11^~S!
ӝ3?%0;6LFqלܽyqAL+D!%:6\U>u]*ͳ6ًq<32(MɉtE7[?iqRtzENyjj0ق+s'yMglJnNh2.,{D=l0zr|=mB4ojraؗf}hl6I?BӎO}B:= څ6xՐq/=q)L6:os+~i$w{iaW6t/#Q-wd%ZO폪_QSe <mwQ./Vg
}u(/>M7n'3IᗺRKigڭDO![*olX|egrvn;RI"8tVd<rY˝R6
N18i7g\+dBbr*k-gt2lʝXeηE1;Ekc%i!cõtXJ* +n_9ogDǞex·)VP>z(5P>o:SP>Ӳr(uʧ%e~SXjgS
<ׯf_3 707ɩ~mT߹~sCg*z~P5gR:峝sEzC5\?-gdw3d]s|~epɹ~8OeSj65I6HasuhĹ~F7~
J,l3~S]tKs̯b|_u]oo~Wijй~*rDj}3_CR~[Xz3?O*:,p~:32\?]'~ugkwy|ugk~xY=xxH^<կ"|Ŷiw?|8_y~s,z+~IC<Tz?6Py_UGUD6\?-T?:kābUV0G[j=Oʳڳ6ys̯B}q y|Dղ\?qU1O1S,Wmgޥ<ѩ:Z~LۍىyRW˺gk^Y~ISj=|`H~#3$V}i</>KRT?xs'gYb
9ozntm
wQ՟g9l#Xuuj2F$Rgzn1iEUm:dZn%/=e!0rZTKI[J'vܩi}F=ٜaoTڱ˽osх Wr+Կ;M%}S靽H6dw=L$w3_O#mRbwDnldlndv,r38 +x#Kۑid56Y\?>>ΆOzτ{g+ݞ6: :Mfa0]&[{'o칛qO
w#6;B]gÎUf1m07=펴nmM0\KONh@+84ߡ8R:GgȠz:HaEVAY;sX{*noG'-˝4r%}_(_s+.x=ZaE[S2ͧMU<zGYm.ݤ+x"
^woyRL
q+nw+iYj;j!ϼv]݁{tOUZXϷBjr*un +$iM#t5xBa+G|~䧿ٯO!(>!]ivc( Njѓ7"\nŻ0""VK9gP, +{aRHv#!`=^bod)-gonrӷ/垾K`:m4,];u_g>w D[䟮{bP>'՝{T$t.6secD[E!dbWZ~"$}Kl;ݻ콝әҭ'sgwӮ$zkr~ř|ΙL[jNཛMD|MnVYލc\eUi@`kmߦB4+<d?"c?Q[̓@_$6v~}4M$uȵ=Ok~n֢Y'hr^([frtLJ^̄z.O] 1h6.=6.ş}c鼵_>!T\\i)NFEшX[T"`RJ-˚ZAٌBC +XVî]Enc349#'|b+Vi2xzPj<.]cfG<tdSRhG3?@ +P$c/tą Wrsau&"zPONvx(d{R)!tx6uD~-# +}=LG2} eYUь~@W3crvb7!{O8k^ܒ.?%Wnr:#* +cBi:ڧV.wGn{&zb}qrnWhfshUR~lT~+mZ&6x|<'RcIgUŊ[:UWd4h/zSz7CD7wh|ngx'SƾUN%v\g|?Fddx>勵bl03l{@D?@KFHIQRSFU}\,Mz!vl!Mݟ?ؓPV}lA 7I3: +xT9[ oܞmMG}K4 3l%Unq2TNt5E1QH\ Rb:֖Y[O&3͌v@
-yi{IG)!68+Dq즊%a#yr+m]B^JqgndK_|"O_rg}7Ūtc$#Mt71/it,NoNiOx}xхLt5[TP~vR>F
>HԄ[fH&##%AU?ا*[#m5DT'[H'ltsdʵ!UW>xs6iox"gQ)£B9A*tH(}Z$մ )Ijz<T!a}y[D6@zacjhyvvEF<ԃ~:ͺ"YR^Pi"N$eoty|4S@1ah?dC/;^ַI7w_zp4uf
wkTޫ4~',Rwu^'8[y} +܊oYǺJ,̄1/6J38ECLcCF=&$,gJm'ILjg6&űgIvVNX"hLqSl=\ +$hq=yT:Q^:{rJ{}t]yUo[\26G^ uq-j;KubW^Zv(nutP9rמ 3yu#NIܝ |5C:05O]Aژs4-ޓ
-"#='v{a?( +aWwIZoHG8&L52ET`yIΓQaPeU"@q6C1ftA9>C[e*HmL2! C᱃Tx'.Xo66^:m&K^ rsydtup[#j-SCBHr"I(D=Lf0NDb|Ի uz8kY]%Û{ȣh\8xQ( +?'"*p}"I>F:LE>FXMXXf`3eTJ*MWf<h#_MH]sQN-mvXէz7MqFz˶F
QhzzC!u.ʿJ/p>yvlWۭ*?_8Q4(KrA>[ _xt;R&l.fVI8E[v.x5邦̵X`3r_o?'f}+g`b;~<RrCq0\k1̹Yqxh~YoZ$u#OFT'tP*3vu,vs~TzR1E\"E@fO&!sr\B8[(ErR=&TN<c.I`@bjޝQbӧwMIE=E1bxK5A]w9~U"0 +SV.)i?}OPqoH})f@k%-1ֵO鐢e ᖓ>LqM@δGf].wpg:)DDT*|ʞ-W|doly,:NhVeq]B;ϼ>C
ز]o63l\b&{D&j(GWFHkJg7{##=)& ztCSc̉L~^Jh䠷$8ۃ%#iR +k +RnȳDwXRƄ<))h +6gDcdx'N-yѺ6@:z3"ެ?yivCX*f{>&D1~Jl +6_,O7QogUN#^3Dn/DǮhֻGnnVMOJqqSR1'uH=|Qop(~y6i݇nsӾPURHtfy/@:V5Uqir/ǥT bc|nzzI/_oLg<-C1'2Tè
1\tѶSpg ri,?d8y+4)Ks錿ۛefKw%=y#?-4~%U/&)my6)H0`c^Zm.+J1cK13y
t?#ʻO0Z9㈖UɭTtW|0TFa!;]:9P}C0jf?[^_t5J !^ːBtSϑQƮNU[G讈't.d*4BnMT0%䷷鹓q1 +/' +t>Nst>N~uY˻z;DW&K+S;ic +3j$3@*>9e"DAZN
4d-:ӮO>K5-i?dvCQObU3%bxՓ ++4Zyz:rP[twKYbE`K^SNr&多 eAY4y$ +Dwɤ1;LQdv'HqOy@TTO28L;u6N
wX-\tVZc#i앿%CZ0N}y!Kz礒" Fe/!Jn{!K~RLωu,Gg]d>JuFN=T\6D
`ѡX펮>&[C3j:v)T0<%Mg<r{uL[
ߨuhvKGS?2i.DCc>6dxl07UPZ- J1j,TzȤ˟?ʊ(,xiA}#Z3]k`3qpae.JJx@ǑQ']lOeNR&w"'qAi[)e6~K×->8,Q6z諑Jv̝KVB +jYN"yU͛Ԧb!qZjϔʙq[oޭOhC R|E!j(ژWd8$P?9TuU駠bRjIYoO0cd4R
0Vt2Z{'o*t2d9@ꝟ|a:Gg+ +|
I?C2Ԥ;l7a-hcT9Vs[>2%̚NtieXFҘ0P^w:}Q2~}/^"ӑ'Ϣhw/99":˝Lͤ :N6 + ӽ>B91RWLB0LQ$Fn
!2aa-Iv`R\`dP X$\ue^OD].OEȊhI~o[ҍ@P-ʕ<}K3iSkYndD"
00Dbal5 +eKBTlp"bB +I7Z+d:e>70#
|x
q\֚{De>H=e^jKE=v"Wor @CNW)&2,pDg0$ⵐ褭s3C~.#(4]@CÅힵti=!eMS ++/jw?
L
2jͰXfOld4]Sȣa!뢫BԢ.ti亴],Hh(@@ϬDs7\~k NF5Rz"`+w_q+C{J26:3uf"&<+gn=#ܖ`c2d,Z%VK.[r/[j^U&PI *uĂUʣbH*ַ~f<~m鳱ݭmx+wK<)}9Xcu +xNgM&>Y:ʥhhH<GB}"a7_R,]`@o(Oý4qL*
XC7x:wHify$$$ZM)!y1F94'U\VWbW+!uxDr6r!9[wC֭ҭ]iynKAe㖫mn(g,4%>w44E{EM
-2+Rg7ݢ"6WTj6wFsvQA0R+2Zv|Hᰮݢ^wH>gchG':5Eth:{DkY +J3Zl̃-7Y/vs xJ +^*WqiӶO"굠{DԢ<mHSYY#^*%NZSR6ҽR\H_5wwn+;گ`ėDR&ax[T/)帘cvr\4 dSW4&\Sgw'ww]460so w5ֽk=7bw<qu- ntiv)g&rEv"Wh_'+BQAnHpɽudhQW0q#FQݓdtsV_p5wu3,1wݣBxl-?KVS}}:wp.?g+1Bp`:1TZ%u<tޔZYbu:O~T*|.!i4S(ToCK5v8%e^8Wp0)7V[3ir3СKCY|]^$G2T$]RvbXۃ&UX]}\\[b.Yt{ƲȲ_k[rlDejmMyvr",zvYN.c7hƖi?]jaԟϏòU-RbvVd.3Q:dFNoh능eesU}k:SwUzMGYoA:}{wE^k<܊>!gqLXs:k]_&(Z.?ovKHݛ +E~+{"_K^،)^XvVJWǎҳ4,4R
.G$5Ӧ<,ېzFRْLD"y|:5+iMrgt+0kws.-g=bLw56y0VX;:7OD<!BVΑ<Z%j9k]L `ltޙ;
V;?EZc\c}$
J*M&!TH'X{lhE][d5{S3ilt猧Rpowc.7t|՚*
ȌQOb˓F鈕yO==rb ݧ)-=bRPW
њLJwSDR[F辨;='GѼ&Y^T +X,u +I(E*_6G:M*|v]#ԡ$ܾiѳvf4uByȹ
x"%yWg-HudRRuRKsò,?+[R3٪\X݉yau:q1Ңڴ;BK4i$\i{?#7<d#ug+6k/Hx)賤Uҁ)7iAѕI!uH7$%ۙc isA
{T[忳?Xr7ߑ~hZf1AU?إ*WA-i?֖c=꧶9^ +ͣ{h5ͦ%-^H:/'#ej=!Z_;LSQdam#ߖ]ܸ):4YzԚ6Hi+c,,m}9FQ1FPc6M#^ev?MW#QTpPXrnfɛ<FrlJWUq+Az=IUF/uYHff$C_Cܨ|uDzF_[zgOF)4p\1;NWʦ{3cb.P`n/K$uoD)=4TF{c|v^;vFM8塯r[D&)b+.-l+x9Dѓ2F6s/1YԗuKS|ڌ}A@1'1S\lqZ3崓KrsyA&E{L/tdcߨAᝉ{Xk_*vjG<g1hԯOUhecf)*ݹ%E ZV[%-e!QS>OUJm*gTe g`*bŰT◲ v3B~IeSʊ +%؞zBW.&ԼXK.f]ؿOKCztg^I{1,]"^fQM͠uqB=&][Is0TDgo8vx_~+/+Q*:^W[F;Bc˩H}u:FsԖZ?Pc&UDyXSfunvc
%6WVb%az m^4kЉ\WaOuɭ]1 +e%f1 +BKLE-q2,/Q8
).Hl$X,Am ~(WL5XZz!֙o&R_]䋍(5gJw]ۙM/
'JNTP僬\W+nrRgdz7s䊃SBw({>A^Z,~9GHq+|2$AU&fFG2`=u*IåqA bFP>VH t4[^LGeɢ̛ǞYJZzmr(QsFJyNQYLGF<ۗak=d><=f4NEȀEA@KB7||y4P +9F@θ#*4! +t/NB+w U]ኯgyiMmtelFGYMCѩehESOQ9&u 6aEt!qq/VqBƮgYYex~4I`m!2e/&A[Qz.!˭fWGΓ^y5+~֘TS42F.4Ixq%bKGy-SkfZ),־Y.%!.5;Sy!TdpXxMTYP[yXfC<'(M!U1JخėR +ZFnSZQ:ʋ'gNmqʯӳGlٿ+ +4|%֏nbaTTv6FQ(EfRA;RC,:L4J6Kf:¬!i!
J4%ih,Y&LaR(aR&a^ֆ%j Ct@m+ιV9;M=OAw~{杨etv?RX;lY'͕àY_yNIsvj10Sӊ{lgXw̑{h
;jUvtu~2d~Uڨ6JJQkߩ>wG9?jeiBiZ;U,;j#OnMS}=\di]DIu^Jvz9uKәhs}V Ғ9}w'^6ڦtѮ0fSC4"2%xp'Fz`25<R_9r{@gtz|##=N}"B~f}Zzt+"~=$#]R^z-y\Ikoa-c6\?fD)&*SyFj]?A)T1J
RC;=M0zCwz:iCAֱ875ҡН^nC;*zCwz08:0zCw&tuRS OɶN%._1P\i:B@9$m
@R3ޢ/գq=K!3fa44y
ձf[=֙C8R߈>N$g}x.tPCn) ⎀|U>i8H?l%(+{&!}C|ehk{Sk+qMv_8z'vl6zɽ<>l[;~ʵљbZ5+ͽ<~"+5/J\!6H7$ mȵt/96AS/kѐ%kBصȵMzx""B4%$7EqG?vuu]qz}C + +oqZx + +oѭ% +
+ + + +o» +ޒ +xV + + +kw$~>QwGߗ+[WgKf#&m~=z^WySOWoGm͏#pFcG=ơ@vdjnSx>Lo6#jᲁKշQEc+Lo_ϓ&~k^<O9\99&
剏%M;hYFmG<qD#ޡѓX4?O}/o~da3kò?{o㺾Ze4hM!eJQQ(T(CT*) +P2h̋u]}?CB?kN{-98뺟ڢ85#5OiuJo^|!-)>:2<tF/WǥVfǫ)bԸ7x(9eIS.]! dOdt|Rb,q'
M/_PXH{T@nAyJlFAQ|jbܑ7;٣n0QCINzjР3MQ-kdbflЮG''?} ߉Kcn7}[њojnϻv)cQ{wnvu\b9x̨HޠskM55_dcC@h)Yq#O{ߚoR{~)s ёFƭ"?z +h4eakӶ%{5-#1pyJ㒦F]UvޭܧPAnt_ln:UOc9BTi,l\}9qKp/3n'B=|X\w-\r]VXB:bIsU7mʬjFl~XbڅwP3oj?BwBJS%ZܷQA9{pOŬi$o=Imʬ.q8.\ֵwU=~RXbvTZ@h^|GElYviطebxֽl6kEi%trn>@e
MFͮ|?Ʃ97)軷#;7t:73m9Eg16{8T\FYD +5j&-ZC2qyMݳ篰U2Mt)_@pDzZcmK0&uXe-s윽C"%1qO縨<.em{`=&)+,P}b1`ǦQq|)yVͶmgxrXzmXh3&mq +cvl0.YϜovö'_X@c)c><й8?ҹ;ֹ[cFi{~b] +ypY[ۻq 6ܥ\4;јmݾqt.+s=3ѽ!ȶMQV )P!(M24Efɴw$ZPWUZxꅔ=A:,636V+9`GFola토=Q)*,BS#]IO:#rTFʹT322 +f[ڹ<~%/Alz~~N`?3'Ws_̶F?}PUB<f_06묜6|e4tû/Ԕݾv!%@_VsrFaºknsO~QVÅlP\ʅkj4xß]Gcm4?vbPUKp9<| Uڌ[5`1gBH̺?x>*cx/mg&mR(l_Ϣڌ[IJNuᬅv.dozd_ݥ4|nȶQH=kJè&23nK.E
;IȾYXZ7t)#ԞmWfyD59Dܵyng +~{ل]䘽AKϠ3'*JzFz)<6:q +[wB05n?d;z +8js{bD?d#v9qNIe[R xE]@KQfĠȘLMP(\O3E+":O^u{fdϝڹjqGDEL&(95߾XJfGͯy0ݫ'U%2NrcDMTYPdyu=c5Cĥ^+B-pk謍7$ϟ<,ȹd3wDf|ј?s_4*jQKƛpk>LMs{cQ^mkF&*-EFc +we.zk|
jhr~\^u.Kgɑ,,ǖ](\_Pkzk(V'*c=uy.Rd+π=GΣpW!-2W&~L}
y\YXɳG'_ạ.hɭd0*j߹~2j +K}MZfD0!+cNg(,{B̂GdڵԀ+I5#bOg>#rg-HPFIGx:X12se"kODN+/\rr~nn^pdnwDދE.k|נߢFFf́e's@BK7+ИۗjˋXf%';MyPJP'M`jec]EQ^VJLV/$C2Y-f@O$ќ,$25&Cn9|L=gvfTfHV$x T`&*48Ueb`e&3 9u}K/Aa
g,3{[̘2f rI-A_5VMopAȴ3_kg>m<X\VUx +hCδ7.&fdd#>FQ4}˞hXW^{TTF۹ڊ0csLGV0məyEu Ȍvmٽ'_m3gbdllϦ5Kodt#Io"_QDVx2|ZzBdӢY٭5̵uu|naym#0aճq|VZ詳36SPhP֜<{!Od\WVH֚ Ao_4<s%@/3LNm͂3u$VPwx;ۗĆx,cNShE}qiQ"Ze~iYwo^k7%|ܙ $/*^+jG@ı+4x_ A7ϟTeRc<#mE*hwZk$^O:n)nT7K?#릺xlv3YS^J`KApW<CeW=}=hl.q`LQ&Ӛ
$4PuBnQœ "Xfj;s-Q&?uJk2LV3Ty-^y#3ӎbDex M5bHZW߆uxcs}
Rbl^h<N`њ8ym Mrᣆ@vlGLSGvnr LbHgf~Sל2oY8^ +Dn/xc3n\8u(t=J`zjdѭY).rZxFq7xB2/OW~GT ORY)/t~ !#>lS(h_M;|*՚sSQSR?cLxvCwY2wdQܣY_,!5S.~$Lv̤a<r`G.
)I7"w +4]mgS+ffT1FUtʯfDHk<PߕƓd{3Ш,8aC(_S~
"w8<vfZ{#KK)~|.+NTO=f\$zQiÎ)WK!u욒[}L' F/A'/ݯnx ~ݙPw#9q{.;Y}x{05ҮݭxCw(GͥSdEXl[/ׅD%e~5\h~P}?I447Ԓ5]~oSTU\} +<4 +`w.>~I
u6Sʣ䴌̗{o?y^ם?85ƎHra2*]Ï^X^ rCugwnt~l5+ey>!Qɗ==*yddMSL)7f͖\4E|ы%x{4DGYV#t)Jy]ӗꞿ[04HKV#T)F7v͖Űfd*~x̰_<moWp)Á#LSB|,jH1l[ +'ȿ|p+OJ9<2XfBJ*eMiJ ,rk;CMSrqV w ^&>? +s +s#M9b|9mpfٽ3(f,6QGSjBluP\0|ؑľ +>}Ed +Rt17T;zqRCգIΕ[`>L1זJ<i7oz0E`RV5q-p1],gLP6#q_42_BFA)s?izVK諎5'^Rd?<.FA)s +OEc,['.| 2+s_5T;gjo5r>GRV<}A]d07##ya&[;1lD;l +>⽜Tja%cjeRJ[$W[,G^˽CJc:|2dtFfʰ$0~j=sPB)sL`n!;r@[,1liKN)d/nG%y#]'-˲aѩ +aM<rҡ es-6xXv?W{/]OٹHsl[,6a2*syJ_^܄Q#s.fNTleFwƤ]/nx{/mD0왋\6M* +eXvE6[vk]e=ͬD
ux-fټ9Pʦ#_eSF)L"C:H+Ǐ/|KO9bﲝ'(Dl[&7tǨOb]Qn +/b4_1Ry/;p\"TnˣϰBsT
8l,YY곍e|^Ivm`d"O0l#xbf)G}1۲<{#g%PYxf:Jl1ڲ
˽as,ȗoy_hmyބ59PMڰHI[v0G%Yms[nvBc-zSTf)r.tpݘ[e-Q"kYb[xZ,Ѝn_KlS[7mY3o&W2D-ރ,*3lL?&kcQڲhkҫlk+g8|;rZ,e Xb +ČW1;}s_?TtxM~r,EM~NӴ}TgAN_[Yڲ@V^3\$>_;>GS]8[Hce}+ɇܗUj%~Qډ࣊%@[Hc~Yuѵԣ^E/|j5+_^$07dW;E$~-j"(|B+t6 +_+-O_9%rs,x5aX@ +@/`es;՚iSxy&eA`nVM]6;y<UKd_MKnGCOC6u#
9v,xJ,"OX=u[1|1^%;u]mq&G=B=C^-bl|l2IQb/6lź1gUl"/d>c,Ona3<e~Z +Do"~X)2De<HiA/<آ ٘VMVb<p +hAo?8ZL=<0U0HQ&69-9VH[kLg"v扽k˵8e:)VD +z·uOR/'Ru[>D<0= +;-_s6eCh`WtZ=FO~' ND[LW6{ +љc{>5%;QyJmk?Yd$GRaۿs.hU3kB8Ƶ2>^^|.ۿs0UWS{COU"7gW|T9ZSYP2uQ_lƁowx50-|,xU;4:R._Wz~`OXt>*"jU>sxg+*>_SJ +T=ڠ2,M/Ե/<e>8spO$׃ʢ + +|u.U +*<ɣ3On\/[GT,;{UpWDᄛHpJ*}lѥ
V:mxzaQYya#eQ/g>_,\ѧM+$|3 +Ja-BP\ݹPӈMT&߆ݼ_D*|J=m?@UfNM6dӠ@xCҗԧKsȥ _?|eh& +_> _Am>4-| D٘Sk_[pj`/ +K0~[/ezD|K0~QĠܭGрP+~tي.x| kw#4fQ`'B4d-O/~5
>Ɲ~-B'ǎ:|#,lh̢ +µ C'hJ:}l]e2ܷLesa@h6&W>ۖ?K)L0vׂn>xL_ m&bv:92o!id#m<*PDz9*2C)˖Qџ.esۛnvY4s=G}Ve'e^lٵUK~ް_ز9mî(|h'2lU2sY'X6(xRwk[uˆw.Բqʆ]6gJm5l˦S6^]6?r/aK~հ,,F]6$ia-[VG:_NbV"m0lbDZEomwcd({QTq[eP?jߞk_A["1:PCMXvS̖{Iɾ[#3a;YKfZ`|7Fl?qC*es*{~Vs?7MfWf,[l+7={/AeO+f^n6EkBw|~eA.)=셇ؘ(I
iaӖݳ`I<2;eܸ_58@ȵ66eԖSp +P~^aYu4Ș^"tgٺE,`)׳bV[O@26{̣PZrΘ4j}!R{+!;ۢ2scwh5<L̺]01nq٫2߲y#`ƨ{W
u
ًlޑG%_/{Qo]<y p-{k{32Y9")A1zZ쮍,W[U_rZF넇)}kKo1*nfV0=<L;qG?@1sj#CcqC>F XvOjZrԫ*Uoו_J +(|EY6_xR՟=Ld +J87=.e>sfX:m'Sf}J!>+̍41s~{1F}<; yX R~VsFF<و*K%P̽GmF)b|ux#2 +_Fæ/,FmF콶JʺS +>(57e/^1D6#f.r&kN\نJb<*ob7#c5
/'Ō;3,j)oۈ3ތV1Y:3ٕ/6U¥`2e巁rT1fFf +9WxVnR)$;6=͆bfIF-Qg6<uf#Af6rU^{mYcC7SR̨3Oiz㮘ԫw9$z}xT[zRrTÏlb8ryw+X}E}es7㵗we~1y8r%9FX#s[YWi`)31[l1js-n='KSTӊ{bw9/2Qk(eR<DRqB'_{Nz52w.tzœAfr?XMV՛e9:0MQgwnt9IIzmdfΙ嵧~(1jdR~}[ݗ͝1v@_~(ҫw+4aWw.>r ,e`rfvD0E/2EEl^c;[_m̈bqZ1gdlj. +S͗{n;pbBدo^ddvL0әi>Hٝ~}ur%L|9Kl?i,N3uTFoWl(͗{1ix]WVk
}z^B2fk
B
_f|zxE)jTxhg>F xvAYgCk@>Cˆ[YItV>Wʳ(F9;#?gw /^7֖g_cuF/FfϞij}[%5
/q +dpS&Cԓt +-ݷ>W\d^>!兹OEmt;EKAjX:)*S;0*h/X2|* +o\3ԚfFIȋ_cf.tudNhʫ(/3.7&(13DA{L,FĦ\:xaTeP*cFuL'_zGW |"rLxNJ:hB'D
LWzfD
zjPSOä4
fQ2mY"_J`i.'1PlEeZf46#'c]S9T_
lOdM0"C##JkSMyI4(xB2k&3D~
+o*PEe 5
ʬkat#;m=ew/y2y߸ ek" ;O~PB"7(Uf<PeWf}qYL4'ou#E/1W@Oav!47<*{19((23ʌ. +k|K)ok\?ttODnMf13o=nx=έ_?Z ZƩ#x8XrS2|1n7x?% IUɝ'v4-e60rs45O2Xxɕo_9p0oS-<:|*Z~I!]LP7/En\cga./9"YdrwQ'e߾_YH24gtKFE¼3wYj>CWM",?sۦio?~Fe0pmG~j'ؽAmͦMR+1XO,(0Iyu=ceOɺqeugwי^܄uI>lN";̍Wk,3}gߠ1r +V?5^<~.Orcćey2?Tbꤩl|9qN in[WΞ8sլ)Fܗ'sWdCF*O0sˮg*.܀&?ݽ6߭_?)w#MnMeF+EܻJړgYXsiMu(OܵoehP>ϯ?s^dFһ2(jۡ/ŜUO]2qk4%߾x4<p%I!{q^d̿&*u^-7
"ە3]$[?ZSZw9-!jQF<6`Wz朐v)6o?trWuĭS[j6 +do@23l+ԜC;}z>^~,Pȯln]FdD38-ԜE=}Qs4:wr:2T?wv +CX`9wyHGkqy#|]NF-Yb(DKd<`ɦ\|>v]~9w}j+Ks2ܱkrk=ME!Hp-n4g490YvXyٹL4P\VDbn+MS2nɢA 7Rrf[ٯY-*.jyDkT9vmq^ja2YؒaJ涫6S6_}umј,^4>F9;#)f_yu5eF/&-9<RFA]gk6rFap +iאY?;rB@_'Rn-BMν=Kp9&gd*,~҈R؇uL5dYW=w%=1f_JZƌBٚ"+2ݜ}g!5tͱ^ 9+vY!#F)qv[FK^z$^w[3Юy*gMP9{ILwmR{i2JĬNy]l-n@6l7p(VM.o_>$ft,rG.du~5FHrin/xѓiY9w0=iyC]Ui᭫RmXTn@6]Σp9Omi@l7 +J*a<ES?.+s=udQ!5ܚAQFb!>}>=Ҟ_uтD㚲KcXjo=w*A])rhvdh`_pđ30+QXfa'h%BK*iMke%l4jFtF9LԌXH7d +߾,#
1(Zrtg4;W0xb[wF|)73adf{5qog;+lcFFњrWsk~9;pJaض-tXj7Y$ +g5>dǒYˈЧ2ν~?0%-i.9<*tX"`yV!DD5M'cW]#҃ +3`xs흽C"#sV=~،qsQhUvѸe.X +i85)clϞ<xPx;R:xx4̨a&fͣ϶q{:RZNYKw,'Ϝ{A@A3BlS\ĪKu.90\ƪƤ!wI{*.#u^фȸ/#M:tP#Ϩة_2~\YZwbZ4>?1ɺ?mKߠa(M/w06EQA7QM TZiS772FV}:huOܐYk$tL̬V`̉KQ~НGAIQ₼kȪgO4Vf͇$!1lQ2rʚ:SZ.q\{#qIg/f]R^3J$u?~RĨ<**SǢnt_lns5qo#؞FI)iL4>fdžC'Rҳ=@BF֍UҸKnwS +Sǽ㪲;73%'DGvh,ݞ)oKɎS|e+|v?r<17hQ{5͓=($0_aR/7"&#NO9xG&oW%sh*ɐ!#%(ha^hsC@ȞHGuϞ%
X<|Km"njxZ[SYzRFj#wmdbtJrhv\c_g2BBYQq0&!9kyw/ %(k_R +Yx5EY)q2-}&.-kh<wA;OJ=u5ݢeU5OiiO]J_ȥ_D +7<AE\VRTp+';38<,m*'i(9t htgoʸG+65Z9hG3\%ԔHlڌޭh;KKK%)\gO<~@x6u.x!OqP:= nȸ%=VQU[gʌK]<|G9vy7*[X'ϚԤXlD5'5}X],/"pcӺՕeF>wh x&ODex_
θAcD +ZI}E]r%eՏ
MH״شXo89Q;UۈH^Jߗϛak*ݽs3gSNEܻ38`Z"Stƍ1Y5ՠW7nqAJAEcT+Vec`sn[taYEU:5kJl6=K4Nu"$.Ry#IҒ{n\ulJb|{6Zj%PwhɑUvϝ@вQ!D$V\=|@材r|֕k7`!6<kDj#_z5[":V=Qˊt}~իH\TDHߊ)/]HO=}*>pdֹ8-^hf:}x
eԣ2f4n|qC;X$=,Zk`Ȯ1'Sϝϼ|5ƭ;?@bWV74<{9q+*MMWukoN'&;zh@^nN+Z[56Yg(1ϪAӣEAcUPV3y96vk7hlɤ3i2/g_b#K+*k浵Du${C=QZS]]UUYQ^^-.WpV^KϥbD
+rsv?H$PکϔЃuWRӚKz2oGg7/_P$ǐ֧Sf\ȼ}
} + ˰j~qqQaύwn˽彐q{2>(8lV
>klL7ASuh)JbԸC=Zzhړfγspru^e[pخ}QGc'L<}&\ƅY._ɾzy7o1X[7n\vj˗.^8v{<hTA+/]l6ԟ|z8ŔĴSC'Dg?pnTT5 [.Zbs-CwyHt'O9v\XuR5sgRϜI9xD#"E=pVfϜ2NnĈa#kAGKo#%ed啐ғL9kE˖;:yx"w߁ȃ9{<.ĩĤ䤤S'D=r8u_o7'{,lFU":h +Cw<=>U5W1r +㔔UTT5'M44NQQA^Nvh)Q#C-'0i|AGHiJj\ս~L|R{{4},RɎP@ŲJKKIJJHB@ۯoJ_aA·4#55_l
<xȐXt:UŲ2dAե%f FiJj5k,6잽zڻwo11~V}*U=;E矰R`P=(I]Dn$8'V]V26,.^,//-zhMrʼnHt:OD_oD] 0!$6QMD%o 0!_xA@TBڂ"DgTuj + +|iY/U-5E5%%m/h(j(i _[QYCYQ[SESOC_QSR2_Y{cQA2XK~-+4fv+ܖ9;k(ߠLl襢I+ +E^ѿ^cdX#Ҡ mӪ_f=m~@iDUqFwsyxKnK?]\Ez;7y< c\Ot
MkeoCa]UUHT+HhEMuZmT*Sk`љ.0$ Mt\f?w^V7"zh/W"W +_{R7&+l4U{-= +l + +ۯ + + + + + + + + + + + + +T + + +p* + + + + + + + +EBI
H!["$
!"4HysYk}}ܻ%u~%S + + + +-} + + +[B߅n3 + +5t + +0t +ihjM700d``?UOWGKSCMUe +#?INVZJB\LDXHo,RҞ?C6T`ȃܤɊ*ȁ4#f3gYXζa`mm5bL3&FteɓdBJF!m`W01)B^AYE]S[Ox,ֶs~g8i"W77_WE;mlYӍ
5U䑒 JٌVV1|(IB4m;:-\x+Wyy]ooU+=/[uB'G{6-gLJ&I"BX|b>~AJ*fff?R}`p7GDFEEoai!|}n.f3{R6EQ^NZ0r}B@HD\PОjhbD98-Z? ($4,|sԖ;w퉍 `¡C1 쏋ݳk-QBC}Wy/^왦zH 1!T"L!8m.,R!,".%;IQE]{6aVxist̎]{'$>t<3ig3MK=sdݵ#&z
kVXxf&I"B6djBt 2asv)Τ8y1rvNn^ՂBWrs/g]<q6̩߷wa!kVy,uEEbmjDWSUI^Nj0)dUC=h,ߐe+-w8t$)tjY^^|VYy*+n,~ ?SO''9t v舍}<ݑVfL<A-aD^#G*Ե,m-pu_{xԶ]H's +]QrNͽj7 1߫S]UYQ~ky9/O?s{wns˂yFjR"ae1vx*s.^7 dS9~LzfVv^AQqɭj +r]1zUϱ43F$/#!;*bqԢPY%!#5P/h]8u܂ںOft_~wzݻo~ +iFb<n_S}F2RO&ݹushbg9B4U'JEȠrZ,&*hZ*â=p82q)'Z1qC/_ +ӧϟ|7W_|Ǐ
!Mϟ"'߭]Vr0/9d$QaޞZ0UW,+wհ6.pY(ΰ_T۰)zGɩ.^ɿvVEU
+ ǟo@B
Yyes3ޝbdBƙ#cwDo +psi +D|8a
d]JNVќjlnmTK8v2ҊPE`o|%e|MB
INAJ?}~MUyKR%[q4]
e Bȅ9.V*ff_YVYL@EPq?k
^I!|W/>FFTݼw9ccGmXby63Mj%*?`}q!,&9QQMgkB#bJʫ6<&Pk =
>fR'߾*yOyT_{HQ~vfZ=1!뼖Οk9@[UANR!hݣHF6D?UTT{WϤ B/?HRH%H'
UW !ϧ8WtX%NvVft'@g1PFȅrQ;%H=E&0Dm0"F>{F$ܬSIn_lomnK W]M +DvS.f8zmTzVXSԟޢ@5Aw"~k%T#(Gmyلܭ*+.r!-Q뽗8P><ǀhWd{71R.l]=mٛpeUwqU|M D=*WB5K.e:{KXLC`oWTg
oP䴋9J*<hxO(8%F0ii~~uٍ {c6tu$CANBD?F'("!w8%=+{=kYAUѮwo^5=i]r-bZrbpi: +=+fa=V`YUi(ťCkGM2& +uԱPܫu=/+=p`mf"/-ǫܮ\#x %.-mhf&8b;rq;
O[~٠{*ZEz=jX>[h*Ob~XdaW Vԟa5oǚ ܣN.jj ˢU3Q Ok߸JYlg9}"]`hGMJQ]yO ި}DeAv` iU ^xX[CH Z4wdܮxF`/jR:F38- +MDy]~=nQeѫ*|hPUm'I +ʃE*4I)iZ;ݲPrkE1˂
뗄' +Y2o6nWhiNG?VQ +dU]ԤyD>xlV~q9Q/ߐ.eAV>7,;ySqw;MWR<.v0m,q )b^1Xu?fbh.+<}$.&ljW֦dy? Qda̶w]qkܑS9Eehjn
YÃ쌔C{C|=Y1 ̩.o~ DaqZ;!9#~V>n~ZͫYi]z-oC8߭]jĨR*(1]W۶XjV6~w]`}||z.jW[C<QyiO$)"0fTVl]JH\FACvφ='\/y}o?}Qm䃻6y/o=cdi1!dꣳ[/1QYe.KUz3/ͻ_?xiý7/9JrݪoG.7zRH\w럾xWᙶ/>>OZ?)+r.9aWDMtUnWg+"2~װK*juCFu#Ԥ>}}ծ%WPylw;u)Ɩn"v%¸׀_h
{|i J+;6[b7ka `FK {45J&_\`JU7/>'*{ V:S&J{).=Dƕw럽|'Ҥ`7"jʮ]>?&tr'[өjhѧ6"2,湮 +ܼ;ԅUQA5)/A+<]hFV,mL_R2 1r\aKѴKj(FjR,ZǓեYgr4B!71LD[$S[k7n;p"#711 +?0㏯hzp(lҾ!k[!<nȾHMV7h]N^/}{(1abV_7=zPy#\r0?z$,:(xV05muP#d"kaPw[]x*qWO!G+uPk!&id'd˾+qB7F<0Vڥԣ{V͛e G+u0eVVIxos7*]t_(ԭZluJ:%4f*kX/X#䅫wꞠ.~߭a_lfmehAdMљn.|W21fQ$n˧uwJ^8#o|+cm%uP2F0qۅ+os$rYjt)D³eYgw{,6YK*zsWD=v5o +C6qSo^[8Wr7K1O/>~,62NcQcn^H>_ZS/w`Ƿ^=DJSwH̫́5
[35XYY#^<b2ˈOp݆?|,G{;%yHGwH3F-}zf(3ewszwK\B2du@ʸev`SF+4:Q +ޒ1rΌ9Vo=@h~q`V,Tv5PQZu^1%鶋Vma1p'۶P:d鈍\dm(-OܜzB
/*=yeP̫OF:6X`e 5~9e"ҊZ&WF;)vt_ނX<X:r3Vcl#ɒF
AS#F/ilw|>4dՁ-
%:a4{ؤshohz=ep?wY'2 g۱Bꆖ#+2>Ft<sȮ0%̧ᵃIN$8m +?/|ܒ;>q%-qgo2hb}j4N37lב97럷
|P`êgmr3"hV3\DZItO+72gp`t.tƚ%`ŝSZ&VlK8}zçS*~ɃkSGx.=M}4X
JtA&ӊ-'n"֙w//+<y2Yr#dh@ n`|}Ծ|ǐ1hmP:Gwog۳i;3=܈Fh tmޤsW=zAGøMSCMI;C]qKFtBCQ{\Wml~ۿ_?}&aۆUFN`
ckUBԝ7
{]Al+NovrGGаK+l;X t+4XfnoNFy~1 +
e=s{wq'G-`X;xt%akV#8UCS|R +(4ք>c8E:fNZ2:&rSc'kٞSRC$8`ϹdU_:Ec
jSѷp?u_82PICڊ'b#ڙ*I1Cpcf/"FSf}d#Dtbk9YB*NU>[Mc[]l *踒b +<%7h3:^<[mJٸWqb«ɪ>s
wɳB ۧ7MŗNZUz|^#O K)-%7ihUh"wԍyo܅9?:?|\W@E8Iq0V
婳=&}l?965*0>[A3WZ.^}V_?ދzOr +q$*p# +U9 [=g),#OgSO \<W09.mnݲ6.>a{g^]TPC3Yd"+2nTwBF{_<^EcSPV`n+#xIF8Z5~a.Ll6rq<q"xw"| +TP;WhI=%`<3sU[xԪ2::z≽a>ɉw8>jnBw'eN}XA~,졘@s]n4$tl9SJF8Awh#J
_bc6Q;AZԦY/ {b]Ֆ'n"C,
E]sCgʨ-J;ŗR(
195 M*F-Bm88[Ѻ4%>{DO@9>#ն;Ѫ4\ׄǥ\*_⨸qx{7miNKyϱ^O*.J*5^ '!.FrAw2~;q_4kMP݆*7TqyBN<3WPBA~y`5P +*|Z汝!+#(;r9}=opx}o[+>?
"|<:kUNdłU_~{(A!Tw L&OB{8~g6A=>BlhZhߕ'p^ <w=P=C{0zqbx )QDmal
EPԲ!6Q䷥뷠x-<S!+(XȈt3
Eݙ~23C.1ˈ}z\ǍJF7b?Cs4R9^ub-ǭlTbxXs8vd8'`2G+GGjTzвD֍G>4zuXPuAẒQ| +~1Ш8r<H*?7|;8j78t uW:~ 9r +OUМY1ߎR֟ԕRg`<{RUf*H(8ODVhb脳W+ꚠQqU36q2"-<Aͷ4My8YT&*X +bӐ3. +QTpq
X1a;88F/(28~q6"q27v `" 6_{ƉMҜaw`<,q6tf.XJy>:3Þ8BJSg;D?
T܁qTurލ!t;8eTٸ{cm8ymY>Ζ61qrv5|o+!˻W&DٴadSG[? ~8dG0 8O[TKgs Ĺ[' ]9\{2[
Ulڨi܁79oBbo3TQOʨYo.8bm}]UwH,pLĹqb,H?ʹ*cu<'-saUOT⓵ +ݛF781NU^>o68S* =ޛι#hzrq-ꤊ5^fԼrO
x~ivJUIUNК:o܁z8N
+'pa<mFEۜwP7w<݁/Nx%
c<3Qc3Cps[/c*3O溡e"dK0Rq̣xS\A˳֍gnprhaY8
<_wʭ%.Z˟"^MqY*q.\8JSoN>uwcM,܅u~T\j)Lo|XpmzXmR&5Unp
7<@>6~`!OXkOc,Y66dT#`0WZ?cc/A=/16F0lP{x>=puF6EYLimݲ.17aT5TfOAΘ
Ⰺslq[=<4c*n<-HO>Rg<+e=cpL5bzbd#ψCCƹ-ڷ8¥\S䁺.~$
F6ur8P&
۵AP_.@?u.5JS-
6ЩU:n__+6d
85-:?5ޣ+6nSoхMp'N,<Ћt<ӫt<֫t6xTtх+zοb +0+{.|2|~oѥއlKt;[w}5:/zN#|^K-^/ѕew[t;w'-݉;w]]{.|W{FC?Ҍ[t0R'q. ObUpn
S؆*m*8] s̡Jeb-*ᤊt/C~yR?Кt'R'UV?nr.^ָÊq.՟hM%F?(btŸb!\b~i0/'b.ę1Np?`2bxzO3-ɶ6!ΌqML=AA~;mnn8qߎcs#iҴ1br3|%Qįp%ۋ
ơ:"+q!88_lG(6V8iԡ:ǸDpXc<hL!*NA=JEmTl?Npٶ888~ sAllg}osPTI;7x`q;}!w!ZvcqN0= Č|֖9y_7]}G8XK{BW-@<.Ү
"8 +J(Zn}S?=h<FA=82q9x
Zg hM;f3MUvmqЌ;/*>5jU0Uq +l|x8ĞNz4*FTԝ9ﻎ_~%=Z6nd$,DE +:8Ƿ9Wx-w,nG1QG'|Wh smmWJmc'l\':ެhO,"x7Jgm
p:0#&i].`*1Bf"M>*rWַ\qω_Mn?-hwdh Z3!7l݄ݼ|2.|oD;ًc"ﰽYw_di|Fqܑx۵Fmf<0&1=Pݛ٧ou5"TqC4kM)n<zIQJ
hSӥժ +:A_e?GXx1*
.TBC.ڸ &́*g^C(]q@]Y&؎o3SGp%Ux! k85@E.8#~ +Zo^fϕF@vwπdqd;G`^<ms@D_hq_9)
Vqj"'.U7a"O66^i-SsJ1o!K5Ԯ,sZ9{N,]Cw *?<}PwPLn7KU +ڦvKξYܮBFMUEObmkx@ӪG2 +7U]Sh}^iNjef:[A^ .^uMdC2w03iw!q_[(Sg9zJ˽E9`nof:T[Aۥ8V@=3ANnǰExy~Ƒ!+,UdE]DqXa)Eyq7 6rX::ߠ[hO-vu;xʝ +rj#^Tg8·zOYz<ϰئZd'g]G9ǖgH} +UUm:zSOmX4mb{`lJh zUQU +;?b +}TBd3TSŽ*(&6A;84>~^܉>%-MqPK&Uޡfߺ%D0ƚWRb<TWV:cR +10C
3bS=Z5t0{G@tKDt I+"7
|XX[QxDlR;3b}+_lsh%vW"4/1B_CK?JXJA{\5a٢t0 =dq":p;8[ oDh[6w0qO6^+ >=8yze3f2Փ'b7VOY8Xt^5(:6l;zƝ7h $vJ/Z=,:y *>GJysQtlu$=.x:HDW>mBc9p>cg>oI箖#}S2}|PSsPo9ӵp?9٧:a|}Ծd"a"d"hzt%=.3#Cn9k=:ԍVm?Utrbz| Dufq#4(P2]:d[h'Gsr}ƩkSGx.=M}neq"J:s|Bw$]Q]sb,_=]t+5'K +s%4t+ F$߰]GܼSOh<}XU|̡!^.8Eq'4:Pt#\_cy5
h-F,-n\IK.Npa6 䆖{\-vmPd[_]͜Gv.g>UENLS F KN0ZD&v:d<s37p?wYh 88Jh@I+c+\(,xPNƱ-
Չk Φ(GH+jOu^5k|7tQqlugiL$vp.KǿN.*3c
1O]*A322b7X`e 5SLp"Gs\W߷'&u`юNZhG2L`\9=sh<t2ҁw|
%#.2`BkhS
: +M@kyKlܙЁ:gSH6&J2#<Nց\c,aHǥ'>BAK2H^4Ham[qk^2t0d|Fx;2xzQ;Ne%tأus >C{6u(1n<Z Ppn\_;}߽zZWeĶmkx r~S2?kyx˧N`EP75owK={]<ko_<6`L;7-˽Y)^ˉIw~WE2<mt*(*.)=Febfnztrڑ=<MhѪYILRj:gʀȽ^~Z<X4>o[vҙ]hOxD+os$rQZ<P1pʃڿݫg5 +.:3o|+cet 7EgǺ]QӀV1V>"Vg8XKvLD\VYzr '/\-ɋ7Q(KG*odؿ5g(2(ęlGwmroTxw0[1 +Oe<oWQ}X\tۼY +}B"<cd5,lٗ}^sn՟}P˧u5 +9`oꓥDyG/RVE=z֝/lÜ*bzf~Ʉy:1*/97Om;1l$/Z.Z>|סy7QBuyARK8-tuU&J7::-_qہWlGVy?K]J;{lcme B|m>!o̻h-8Eqφ#g +Jk7FY]ERDa4=zԅS7r5MMcG%{ tYs]+!|n1 +sFyvŖ0]8=ydbާdhҕVP70/4fR<pC. R!k[OS$)dKZ<h%"cR=2so@շ3U3Qq=\J®Un,pd&>%x,7^BNYeź\
Wh?~|F0Ҏ pkn(#
hCLFA]}͆(<*֣g]} T㺚r\vGy/q*/%*VxJɫXڻ\i +J4>tw}ALRDjwլTTWY.~>,/<ANI|2<NϹ^^1jWx>ZjRjo]HNL_CR}22X0|h|uaKʿy^>iL80$E4Ggi; ܝ]XZu4Y.>{hRiwDz/Eaf>ޥP +*5\+7n;r*3>V.~AUiavFʡ=!k=Y]٭0!vRR/ߩ} TrYcmuYQnR1aV-v65PCZfu8Z=QznoH'f`o>B0Qj]LM:#"}YF(1Ũ]٭偆+SkG[JNtFECV}i/fB37' +Y2oꊲPa]AW19iW@XLlbJkh/@Ew(/W/'ڻ%5)3C-IRlOd<p5AVQ]+e>d\>Pz%]!l*PpHHI ZꄛdqaDa.ŀUh?j"5AT(? *3-ȲmKFq}9Y&%'!"0?!,ڕǚ>;
O[~
]YI|EEVI Q?%;CXXԮTu8zG~~)~d;B(TYWuz^Ӆ6p5c'+5]#sGȏ뷪@onyn +"-jzP[sZŴX|&w~Y$+QI9Ec-#619bε;?'BZ v,ߴ4@-FAvf1Gs#]5&(t}LTT| 7W˪>l];* + +ԡ?ixPs(RƩcx\(I +MF6MBb>a[v={fyO !e0T'?ķ/ +܍V\|xΨ]P]ϛ!c8!L@y{}hԎ}'R_/*_`!_2B(a9&4ѝ _Ql{r2N%ܻ-"5BLaIj +rT]P.Db9Ums]G +AR^u/w>RJH'H +BjaaA Hij
ߩ,y-rٓnfŒlfLT,%o}<9æ-ynUi~قJ,YPf IH !&Tr/OK9#:,o2ǹt5e$.0}yWBF^Y]p%(!)i/\/)s1RU *-a? ߾r#M.'HŅy2$9#zSL}-UʼnLť&*hZqp^郄D=x$Lƅ%ek5<z9y5| 1_O/cuݩ&\<z2)QaޞZ0UWF-JLXʹ탼|¸@ԴfBâ9qrk%eUwb'&l5żRnǍ
uީuzA^si'ݹushbg9Fz*,&wKF% +DV^YM)/(t֝ǒO=rN~e5>DV=~yAb^x 9x#da{5Օe7PI\j]1aZ~T-5%,5h\`X>p %7YYbm57Enݹwi2so*Ss䥮H߬Cܿ[sV⢂̌ScM!k<,gB_[MY^VJ\DpQD4.0a;iKy\z]
)9x8r~¥으kE7JJoWܮbQYyViɍky9ٗ.OO;rhb=;nۀLpwurk5k4TʓpBi1r8[,.0C +QӚ:mLN.K=Vlu9v"ԙs.g_-((dQPp5?/7'rsigN8vPo{:_/OwׅY[j*cC
β`*BT5u
MgY͙7suA!#ؽ7C;|ԴsMK=}dcG:x n1["7^|aPOK}$YIq!R +qII +SԴ3-m~d*o_M[ص'6n J$Ao%??.vϮ۶FGFl +
+^lBysfa*rbA}bp^ +brS4uδc0=WyMQQ[XDGEEFo~>^</]pt#]-҄ X^ +,X|/D$Q(#%FMgZXER]ܖ/\{ڵ~u]kJK\6V3MM5TY&Ơnb h+d(\"ȈIIkL3nj>f\;{N-&Ao,r^4n+YӍhNQ<QVJB\; +0P>C%2w8!B4v;Ux3YmXX[[Ͷen6cT]m-
5eɓdBdM$L,BxQ RҲ'MVPTM700da`?U9TWS0yDYi) bqcnjfaTtK*d,vDTL|DyEIIYy +;JJȁD9Yi) b"B#1*:`R Q~le("!))%%%JRR;-c
)Lt 3j4 /BamAA +BÀz(ԓlB?` +yfZ5K@CX<D0 ! +
g%t2!CZY!PfZ + + + + + +6l#06l(*A(Е:lHQG"P6z(Ç
WK_ +|%F7_@P!< +SԵt
(z:ZSĄC_CGQP71b +2|Ge{a_<ce4[9:-rqus[< +٬7JJKo
8JKKn\/:{|UUeExZ]aƉMT7uٸ3żUw U.IܹX}ظQZ_~I&s
slvaIEU{0@н5U%g _x$1~?P#;0k}?kΘa7cfp:ˁ3>/{ {Ep½eyi(̗<jk]AAG +]]ܵ'^3U5UʚڊzqMeyiUmO3ݛ\k涹Ğ
T?й.<1 t2#Xwy]MUuUMuylȘ`?bppT{`kŶ
;042u^x/M#78`<l3Z8u_6\?@gw3V]Ymf?YUZ[m>5ƓcN<
f ob}67ؽŵ#|s[()pחVVrUV*WUk'U#coVp\skpUTկ߾)DG䉈t/M!H
+C}!R)`kf!R)`kf!R[C4[l
l2D +bkf!-hbkf!-hbkf!-hbkf!-hB7h)toB[C4[S[Ix[C$1]kX"$aq2l
/aq|Ct <h+Eh}ab`GS.ÅithIhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖KԶhɱtOm鎖L춅PtOp[NPtOp[O3qEA43qBLAE|EGHEM5݁t`tGEt݅xf"t@DDD*t hYr!i*a,3,(:@4/_bE)>bg`PL#!(88$$$Afuy(G30"bexXHpuyGD'$BjdbDrY"yD'&2
;JOMND&+yN@"2sJJK0?'3-9>:" Y@bӲ*kW^hW76Te% +3RSE3}~E&dT|;<_zD~<G;/_]QM3F6-S/.%~c'ǯ=i"'z7֗dF?69MlQǨ=W_W3?Hȿ~g}uaFB1I3~V[?ϊ:FĦU~起gH~췿v^[Me8&%k>'~~eâsV5m7/"/|||֦U9abQ4l8q +JF"{g^$hw~,1D:2Dn9ߢ4HD֔M&{GX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDb"VLĊX1+&bDՔD~S"?xyH;'.D|N/rJψĚ ~9ϼ~?Hwt"F"{|o=o32/>|lH L*[ݱ{x/w7W^|eYNLtg?Ǿ×o{~W?{եL$Ḩѫ|w7}~ꏞ{_F#&YҰyȍw_ͷ7WOt#;77d&8.e!p>G/_篾O=ཷp"W|DH2%^Xgp==SO?^|G?|{)LvZ"+WƦW6o=}<ط~'<=;NlmO]Q,Y"(<&9qsy='2~?>x;?:~oscYNrLxЊeKH`XTbfQͺ{>C=Vك|+_;>zbk]MQfbTXִƶ~?=myi8KV$b6YEM{p㙳nŻϝ=s
{67Ue4z֠kFFq+cGGGjk)I
rԁ<$:1=q=w=wnٺy]cuI^zb1Et1$$IJ)*il^߶}KGt:on[XS^!1jIWTVYSиz^PWSYVfLGfb"̜¢Ҳ镖d%G#@Mw$1ɩ̬2]ɉ1ᡎ
" B&Q1q G q1Q#( +2GݲZ
NQK,mr7ȳ8Y'[DDD)***cg7%=$M)TS)ݔar2MY'?}pg]<l6vR^ߏ6>|OD
A!>&s=wA!@D8"(3R\t'H<Y6y<r@t}ggTTR̺w>Q:wDDDDT/(((,,,***...)))---+++//khhh4Yeݺu6lhmmm3m=Ww1camfvӳ||]a"=L_# +ۆ-tdw{W.!+$1I"O< +^#g!3E7q4qp_Ttt\%|1w"1[}zbZwtttvvvuuuwwl۶oǎvk<p:4<<<?>?:Fx <.mÖc +s^juw.ӞLϊY-8{Cq\NsXY^?Ŏx6nƫa\
`gUxDFkVDbǽ<uW¼,撘EGo79f2&vGxcX9AQ"754qF0ymԕ"2,[v;LTKA2k%j}i/ﶈ(^dV,YRO/^eݹ.>xAě)Xmbq(.X$\q%٢8UB?dagEw;E O+Q3O81N* + +*MXX{"<'Nκ_"kgDgɬlq1AGKX~vq'$EEEΖ?͊-*&&jHt8//c'YY>V|Y,kC<z~7ysEg㳲rrrp̵1lyvVK--sVV
Y|=AhpeҥǫX;+i!oxo/ ZDYܟ;«j+^aX>ii'Isݯ⣉ZhD>~[V\Isݯb@/ +Dd +ϏZ:뵭_=X>Q?C틇|JwDκ?/:ӵU%\h,>oyha-S;ybbq*l7| +ԏhYRY/3Swg"thwEvKs{N}\a-uGtv߭o/dgNY%4n߄3ok YY"5^;+~Ev[5Y WkP,Ig(7LJ~˜yvנL-/5mwb(qJYϋba<opb-,l;5-7[ ߫,.COD +문hinp4nߎ>o yᔖ%R浳q +EZ`Aܝ
0oΊ7z297J\ + agxqttX=A3~oP2x%R lLL=03~xA0c ZvV9+.T|3~WwʳDʼvV#.@%N~3ʛAܝ
`gfkΊ7zⰦE$m_-nHA0.>V͖Ί7zPxiW6OV↤Ke([,lI:r%$$`MI +↤CP_͖8Ś7M˛A08٘%R0@wqB~~!i.PxvHto$%%aMI +↤CP,o\XӊPXX(nHA0%ޢ]b;K4[\;8I[Hܐ4`(q***%R㵳QTi8I[Xܐ4`(q*::)YF +↤CP]D +v6""B233E$m +qC\ex<;K`jgQ%Fʕ5-'i+TVVb%.C%$$!Y"uBE$m*qC\eDvH(%.`MI +↤CP͖Ί7gQ\i8I[Vܐ4`(q*)) )٘ +Ú
Is1,:Z\XӢqBCC!i.P2TJJJll,;K`jgQ% +B + + +E$mFqC\eT<DPP;K4[uB +E$m$i.P2TZZ;KfV(WQQִhk֬CPqqqtALY +*..ƚ
Is1222Y"5S;*PURR5-'i+477b%.C\`vh,jrbMI +---↤CP,KgQ"tBP2iݯ^ +֭7$ J\JHH agfkV(Wyy9ִ
ys1Y,YQ( +媨cI[Uܐ4`(q*'''11|͖0:Z\XӢqB[[!i.P2Tnn.;KfjgQ% +BċyfqC\e<<Dhh(;K4[uBQ44NVOb%.C糳Dj,EDgQ+kZ4NA0UPP)UWW5-'i+tttb%.CDjvUBP+kZ4NV7$ J\***JNN|͖0:Z\8Ś
Is1Y"5^;BV1ִWXmnqC\epvh$E֬Y5#nțe(vHKP( +jjjcI[a۶m↤CP,IgQfi8I[Wܐ4`(q%R6 +Z\---EVV7$ J\*++KKK[r,uAH:+.@hhcqC\e(vHPʵai8I[a]↤CP颳MwD(Wkk+ִh/nHA0)tV|, +Z\mmmXӢq!i.P2TEEEFFԯ" ,ʅ3Pi8I[a↤CP,2¢>(j%.cMII\eJ5,uAH:rcM:888cs1Y"eDXPс5-'i+8p@ܐ4`(q*33%R ,ى5-'i+<xPܐ4`(q%R6 +Z\]]]XӢq¡C
Is1~ջ0yguzm+b%.CDʼvBPimqC\1lmmmvvvtt4:kuA;m60FGG
Is1,\X:aɊBV(Woo/ִhpqC\0`{{{]]]NNNLL,uA;'^d%m1qC\0 ;K4E}Y,YQ( +ڱcI +↤CaXvhݵkִhpqqC\0 ;K4DXPߏ5-'i+8qBܐ4`(ؘ{Zra-`MI +'O7$ +Ds1 +Z\8ŚN:%nHA0qqq"0cgE$mӧOb͑ggQ,VY +BE$mnIܐ4`(ݽf͚xvhf,ִhpqC\Y9t%BP`MI +gϞ7$ +455&$$._tAYi8I[ܹs↤CDs4XPuAi8I[[n7$ +n۶(11%;5-'i+z↤CDsY%BPCaMI +v!i.P_nbMI +'
Is1bg]XGtUBP+kxxkZ4NV뮻
Is1}}}֭+))IJJ +a-t>Obvh,bEP+kddhO}Jܐ4`(cǎ
6&''DKg8I[3!i.agγJ%+ +Z\7t'i+|7$ +ڵ,%%eʕ;Eq
Is1;K4wS;%+ +Z\G9s'i+{↤CaTvh|쬤↤,_xvAdEP+kllٳxՓCa͛7WTTfg|cgmfl.;K8XP5>>~9l%m/↤Ca{b@?{"{gk+河D~d,XPu[ngxqksEm1*##Y +a-Զ>(nXggQKV +BN8q뭷~3,}Mܰ4׳CajNm~Xp7%(,(jr<yn??#<"n;CatvvfffFGG;E`.m7DmY"?P>XbɊBV(שS>O?~Ӆ$ +<x.+++&&&88%_:㏳DY,VdEP+wu+/췾kA0<tPwwwCCCvvvlllHH;K;-.<%Z\xݘhqD?ED]?KX:SAY>(YEE--|>vC@L,h!YówϗΞ;wVbvh,
Eg[ZZz{{O:u[LJ* +A0ġ +A0;K4GS;WPPf͚aM{$iCa@ +p@apvhf#G=i4WVQXY9t6888666??kpppttk&Is%m +PX,/fִMJ* +A0;K4G]tiPP:vvvݻwxxkqbq(8,`=tִc&Is%m %mhh8x ִGLJ* +A0ġ +p@a!Y"_;cbM;l4WVQX0 ;K4]d :][[y澾>
kC&Is%m +PXD%loo +A0dgـȬjtv۶m$͕U`(aq@ap"YYwg]۠I\I[EaCa@vh.v633{ǎXӊI+o+`YpX"uvvuu屛J* +A0dgbjg#""\.Weeekkkgggoo/ִ&Is%m +P88YYtvÆ
۶mÚ$iCa@ +Y"eήXbʕ֭koovI\I[Ea18;K@ٖ{vwwcM$otm ,K.$:^^^܌uuuaMg4WVQX".@PYYtk^bq +vH˗/OKK+++[fMkkkGGִLJ* +A( +p@a/`g|!lcc
۱1I+i(,p(`gyljjjii):n:bMm4WVQX".@aXpX|ta-644KXv$͕UP,\LlXXXJJJIII]]]sss[[ִ&Is%m 8 ఀ%IgkkkZ[[K-H+i(,p(`gY:lٲ5klذuk7I+i(,P8;K@٪*q +kZQ:Ism".@P)٤JTE,ݼ6WVQX".@PYY
XӶ$͕UP8ِĂ&i[MJ* +A( +p@a_Hb痕֮Yk
&Is%m 8 Dj,]tl^^^iiiuu5@]g7wbq +,:[RR".cMb4WVQX".@P;Kfjgrrr+++m6I+i(,p(|%;K:]TTTQQQWW'^jA\I[Ea1 + +*,,,//E֘$͕U( +p@a,/lfffAAAYYYuu5ִt +D\¡ +A( +p@`gLl@@@LLLrrrfff~~~II ִ&Is%m 8 /dg|1]g\.W^^^qq1ִ&Is%m 8 !Y"^;[TT5mI\I[Ea1 +A( +٩_dbǧfeecM[b4WVQX".@P;KfjgWX5mI\I[Ea1 +vV|x6---++k|bq + |%;K
JHHHMMĚ6$iD\Bgq@CD +v644Tݓr5I+i(,p()t6...999##kbq +ِq8==klbq +ta-vuV#.CaMe4WVQX".@aXY"5S;lٲ`tVݓ5mI\I[Ea1 +v6((Hݓ5$iD\¡ +$ONNƚ6$iD\¡ +xG\6$iDbX/bg|ᵳxEÚ6$iY`gLҥKQ(vOLLLBBִ)&Is%m ,2ygK-H+i(,0,CXD%ήX"88X\:CݒLJ* +,%R0K,A=2ִt +DbXY"5^;|rtV݃屛J* +AܝoZ~;K:+6$iD\¡%R&טּ%^jA\I[Ea1 +A,,;K:yu2I+i(,;K^;~G\VN\I[ag|!ұxu\I[Ea1;K4wu}8,,u0I+i(,/@DKYq2;ݛ,/$u_:vwV\I[EaY"K&7WV dz@;KIgݗKJ* +AܝF;Kyge(-$i +YY"_;+.CaM+J'oAY"etVԺym%;IgݗP`º,\LK<.+ixu_bgͪJ* +QwDSZQ+i(8egbV4WVQXwg[Dby,H+o +MD@1a!b2;d{/G_t? t= +]DtODSAݓ}alBL_pt?!tQ="Ʉ1_zgp[Bv5hRVZy
hËf7e/Fڟ-@~k/Rڟ#-@k.Riҵ
/fҸz'"Ҹz'b/@sw/Fһ
4wzb@/Asq/Rһ4G'"@/Asq/Rһ4G'"@/1?f@21>ab<_6c~8xOq:ytm<X8ݼPZ oиv?]3a2k7chSo"'EͰl}#kyx9N9=Ymck#?>/sy77]#ٟef=ncn`ϔAycsgv?4ر?'' """"""""""""""""""""""""""""""""" """""5K/&ݦfjbRep{.1JYi$6RCх{懱'FF+"hR`` + + dDl;8c 18'ƎcQ+BU5B7W\=
OHH4$%%%''$''%DEz#<
8 +>3!!)k6nwpѱ'NpԩN~cGG|m]injQ˒\c+VXNJdJǓս$q& 8
1 ++״u[j=k'NVUTVVRb"&gR3r + ?˃ڗShdBz^imƎ+w:|MPwyǭgo:yоunTnq5g)怱عb Rc#=ZjJT4~/9M5N.[f͚ƆD#(\Мu#dMĹl빵x#̬lX۶W=8r솏Wv빏9g畝mk+.Jshc*jp>XW[U]KO2fcuaCǶW@.I,âR2K*״lҦ6֖_h,3cqfWqnl5O=Sst'X?IrV_ּasgO#Μsۻ[*s҉[cK*pNزn=
ի.Hpo326%n]{O߮]}WXsgbɾz][˱"5 QfK/jhZ8Z77Vl<K*(_ށ?|cȩnݴnM]eI+8]a|Ml4ҭBڷtlm}vH}f]\vs;zzW:穬wUܺqfg&<:1=q]ۖ+ze389l|&gϫ[:tӇ?2s˪WMn1K'Źse-mWnƵ
E٩oeꦶumKN\i68p`po-xsx҉3ŬK+/[ybW]s{Ԯ*Sj=ewNf2sWU74u:kغWī8-,ohٴ8s +H40D\7A83k6v^KNtiSqڴC><2<4{SSMq1qębniff0
sZrןxT6\~Б'NLi%5jc ɗEQXgvSvrDNKr:i,sG;6vջϕx3(ÃC
0q*%i-'.-)%=+g];GƎ܉eeQ˸\2qqzr9]CCۍӒK\9m^[[ +2&%eK8ĉU榪K͵{y~sU><<`"'r8|%>)53e};vо[ZJ\I;SFNc߱*Ñ##mY7q]!`z/o+IA.5VħWn*u#wniOUT#0k%y>K$]Tt=^gMUEYFܧ18VAe +$C>go-庾&}xGi,G}Ufs+ҹMSN?<m3 +Q ϾO?of`F_<z̭ۖ\zկ̦)'/Mg>Φt4M.O| +34%*&ԧADw?MI dr!dw?g~K9]z
ӊϮ'*z ?+=4_?| |9ͬO螘=*Q9I:>3}=^ʀ]"vPN3:2"^agf1y"{}vSǒskwy?w=iܟʩV{x%/ӈ=6]?`>vB)5^\N~H{O}믟??3llXWNHp۲`eO,Ԇ&Vw2ws9v)+'[ASwk]֠PNν?_3ٴIeqλ}zW|h4v+.yJspE>oLN.o߹kjokuAt5%o2_tVL~sO˽;>4S&m2_,mn/brҥKb}>tauc{woow{cu!7{k^]O}fW6w_t=4?<|wom.yYՍZ̧Dt}{wo\[MF7QWc|09ZSgWVQ2mZ췼~KGptzfaiټPb~J}ؕ-vzݳ+*WWz&G`e}skksc}ueyia.NLُM+n]t&ˤW_֯NSٙٹlʾL`Wx*IOOPixAyQa.
wh|j:;;7?77˚ex*Tb^vd"ZUkqڻn%J2E{k:W~/F'e}),jȻMط;)oxo,rvGx|*:91>6:<4lk7n6tt#vy֎E#cv@_W,]/^fWeS1ɭo2oo`(2<22~|1Tڏ#?pļ|_L"H.^=jۍ0auoߓ{L[U흲Э%w8U-:7L=u*ힵV{C_$+/[ZeGy)YXS_vеDS4ծM
UWٟ.bmiY4ygDџn3/ [#,.]_\?œ֚xvQt]k{[d㥢;j;h_]@_goܹhzay>v+/p{/哙_ڐmmc^P]Sᶝ_;#-w9ʕ_p^;vҗf3~ LE;~eX?~:Y%oi+q֫[._zů.v)P~}~ԯN + +pʼD~u|Q(Եp6%fBwnj_ +\L5.:@vUFix@#?@iW_\d ..30UIs
vMEʝvEEeJjh])Qq?@yѮDڟz\hET.>P+"*g(M(ӮE}Z~@v(7R<PVEF}]. [aO]1<B0v%]I;ho@NIѮmK;2E\ODѮjH;&˾@$ѮjE;eX + +[..Ӻިml
u7ׇnJ`]YS[oTN^KJ?1iTזށ`_WkcJIu6y +hE9$
߬mlNv4K+zÆD[P$jk +K\UGr!WY%7teLk싌Gxl|82^MX:$CãcjR?Wx^?${ZFT:D|SVΞ>ձhl*>i +K".B5uOݰ4QTy%7"CԪPc{x<eS^9fN%STrjUkaKhZ5QWY%sGWym&spi`CաƖ d,12ӱAZyS +1"1ųV&0zQ 5t*=30?;M\754wGLL͘;[k6tKYzoFN㻀644655qhWW5xfnqia6=5P,g:-,-//-̤\{5<=G^\S7MXήR2=4̙-eL\ZjM_g`x<HegWVVsMܽ?ڑu{MPML]<M +21d *J7n5D&gWm\[M5oj +^rvvaie}csscm1%τy'wW2+jLut)NLFc1;Xg:OU7CmhjviTL|LZ_/D흝᮶f>ZU
jQs(RЩDR+ɩ!NMWo8[^5qZF&&+k[;{{;[[!&~-NꞼԘ6Q馈F'&&'2 Mgٙl:3&*5uMᑩڊkw[=ۻ{o + +tunn7fL'괊HS2UT!'EtF*KKK34VS5q]1q]qmoinn-Jepppkw˴Dtlx0mm2fWDJOiUMlԮ~7ALٜ",;`1ѴzƳ.VS륺f%{
dlbl$2809OtތE"6{^d2Wra*t}}ccskkksDD@(C}]-5;-;wn\[^ɦS56:23ٛCdBUn]]b4VVȈq.iƜ++qֶlo-LGm"mh*5g6^Kk[LT+qX]^3
tj:&tDt\h/_&'%+WZ}~*QY?îo(nʹake4y)Ѹ78fU/.ۛ+K3l6N%㱉QYM
vY[QT婨]:(Ӯn%RUGSY6{ζ03lXwl\ӱсB\Ҧ"JQ)N,/a'qX$,ѝ".}zKd kxQ^SjƧ$S6s`'&[6&K8zkgxtӱ@|MLgַ|G8?k"7X?v\+d|[%#cccBcIR\_GiBzK~c\Lff`dtr%wWowT{kɩhdOƵ&rTV-Wj]0:n'X7.!M3f:;oI4luIVU3Y^˥#2TW[[386҉{Ch^/jd}#ӑZ7D~1QMڨkR䘴I 2d!73#Cՙbo{xږj;=?Oڐy֮D*ͦ&<x455_f/ ]0W75.s&AIhLd;Z3K^*UMU5%n'd
7'u&O 8?7#1ly b7wQͤxtbԌJMz2>usÒx%2p2l[ܑ&w_:+Kg%Ilm]TcUVL&dttdZP~e`#;ok6۶K@$ۑXYQ]]#oh̀t|T!T$e0WV7ib\Tw9hީ?g+ -ISUMa{SΪbkL/,zpJ4\̥Sv.ah@K.Pka atvfn,+Y[.ˀxomQ
oS2`FN +Y&9K1iR
S4@s[]=dkȀxt\S %l>?fГ^kYfDu/423I5yNQi*/#b͚_ڮt2<lF#v]R#kfYI32'ylr=4VwSm;fZtO35xtl(dU)4tۻC_#ϸD!cpݲ};G`DIO"@(:91>&
2{\wZedG{{Lutʄ&Uv<eӒW쓡e&S/OʥѱH7=^q4ҴڰFUL2i*
h|*F'''L(ݴȰ{bc&e{g:
(e)T۰[E#cv݅fiSX$L\_Q<MMJTIofV\eu"-bR?v{r~-]>B*UԺ婲ظU_+e]_ZI2?m\Ѫd̴8uŨaOalSe4i[o`ߋS'=/4AVYt?F"=-j4
vMDe465.t]uY-
MDԸu-7MgeRUV[[mjzn\j+1֨{[Cx>#-!5S+isen1nL3eS8UVym캖hB}&Ӳ2+snVp֍Kpo7d\ًcSk_Y7y"\Ye.ͮd{h7b-K{jۘʺB3n5L՟wI|/Y®foICa3l%ށ0v~z7֡pO#4Lʬផst*]7W +G&K?_u~%4nNӱ͔1*d[Z{ݠhgzDYM)LyffP'Y:BX+^e;o,YUUۖM~a7Cdŷ'~eWJGV͙U;Úldg5JySCbGG[}z&I0oo-,踺!]r&Ǵq'P$Gw滻ݻ, +F?!Oކi/]];d{{UIBiI<Uu@ZUB-so,s)T揚揶Mv k>QYUYa]][t.LFx- m('}9efcaGcVrXrzg%I].>Z2s|y{ߗ +um +_ozk6M]u]UIN}N.vhKYֶ8UVD˗f٦]Yb---y+Dox;ʏ*8DY!|IZv̜-0a-v) +'/Vpum1m ,edݳ8q\Gas<Ɵbae (meUvolxSvRb:cFHYwɵߒKGr^XIm?6G)PXVzUuXǥ鏗|mŦs JMvG]/"2XpEUŌe=iauti:-M
[e{KC&HaleIuppG[_[kbU@_3tDU#dnqm{쿳-a= 9P-/iITѰ3[{mIJTe:{ITN~?<O.o{ٗtBlf\f][w?xt]LzM\{$ϱspwy{I5'd7q-c +*щi9j;Xy!vG۸r57ka;wT|ҞtNꓵr] +[=#JMgDU^,s{<_Z-r>.T}PohrwIN~xckW82KQٌGU;ǞAX㒫lwd<*&$QNTX +nMyuru!F
'94:>11>j3pNKۏ]O}rue"Qݝiq+ުvq2Emv=x^c{]1,){{ީmVka
:yFy[#.I!Ɇw:1GaS+MƦƆz
uo8)5M&6OzXMG ջ;6]]~d5bN,twU֨F,¡/)a7᮶Kx +TW1{\mt^O{}M/|w{wĘ_Y^g0YC;mhvǣSgր8:k('zN[N 2 5P($gD*kcrCibC饚:3B)6yy"ͺ]m
+P\n-1LME81JM]5in̳Pn-.F9iq/P|kwo`ppphxt"sh66tl,!
X{<h<|쑖C{wȟoƦ≤2-o[kKsk#D^u:N>% +Aua]MƼsT@?4cؔuǝm
O)@u꺳Krm:=N\ Yf͞oq)5 Iw /4-Bt[ .{nnI\-`f80$mܒy;`zã=mMi]8v9=U7ؕ_q"Zl`R3J\nnzZ9}x>۶^on_ +WHJNx7\ݽd #9m$~`$q
/rYm}s/k![˳^\_eGPA&wӠ гH\6amli봻bDo} 3gr[#mPn0E/epTqN`VmuauwHNL`FuzS]
DܩU5
Mj67*Wyǘ堭r9h_`(226KE[$Y<(nǫ'.`pp(2<*ffgsdT.`z\7u
E"ɩt:ͤYݦ5|G1{t*JNM䟾@5aiWǍ1蔉t2.v[
D +\h,b6)X$]HSy`x<:H&D<H$NS!ACgE&LdF&J\&7+<<1F63Uu% +GhTp8jZSu*i88#;㜱1TSE:;֖붺׀ofnǯ}ݝ&MFcccC}]m
a
9]ckGw_Tζ&fmmȨYu$oȯi5==օL4oVY7n\vkUM]Csk͍u7_1ArY0a
EH
M--TsSeԷkx:TW $o0Q]"AϹY5
k
fě{fV|-Bp!&~*k_wCf .K$ZoHCX/ [B˘aLsQU +ert211΄d,H$d.Kf҉p&;K[X25
? eӱ\<12Xm:g;Tl&JҙX2J6ϥ͟ųt<'ۘO/K$bd&ƒ霼ݙy
;sX&1-7zfZQ
T81NSk_?g?W|g5ct. zP#)gO^>?<cL&1&x!0w_c1GsB6OCE%<xG^{SM#roSG:6{hG=☐By#clc/+;^~vO <G+;3n>;oݧ
?7 Ͽ~NOg;I2TX6mkxBx<k~{?~sfٗ?{ei4߿Z?ٓ{[013Jc'cű_ o.<m&_23_D;<8fM;7 + + +v + +K!QWeR3x+E%XUui"^{,(Bjyx]!ދ +!ڇ*B=K
!NڙB+vi%B
9^BH}o#nx]:!uNTrAy+ՑCx]!o^<R@yץ6#tx[N! +}ۅM!)!^g)!e]ufBIB}g=uBHx>ksBNNV:!+1.ByxVk +יC!g!ׇ3NxBy#>NiBy79^'0!xBjKuRBH}ww!]>}>!Y?B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!BHMhsX:$(_6T?
+xYtEBֶ6YbE3ͭN^V~Aro=]*zG9 +?8< +Tz%)8 0F{;}pU%KŦщ陙)c|txP;V +R._Nv~ +1fD:eӺ8!F:8KP +5 +ñ +(/5 +@nie
}"qz:"wpLm
+BD4u5Ϛ
Զ@|z4D- ƓL&../`,L#P +16&Ru8Z6[vV4X{ۀhdR*OM:B
hñd6$cjA8&WW{ë6Bm +SBU@M 䊰X}'j2ڊfvrl@`ޫ7IdV7677֖sX{;: +HRp@}RT=2+뛗/_ސ~(P}R=4>Ng,fS¶ ͬ +.}s; +CH<_Y/ZBijZœ .,,Db̢ސl?Pi&RͭM9x8 Yp4A8Jer7vd4T\ +Y +Z7 +@VP_Q{fv +@5C?rd5>vU.eсb] +K wv]z`2/3Ila +/? J]=}}ݝm-h
Bc3 +ֱhOѭ2ydl|bbBmԯ]ؠGf +^X۾ oQ#'&d +݃ +$/35[+`z!z:}Ni&E*i#eggGU[A0o`d\;X"-.k[Mg"{(|u啵uWvͿ/8_49 +T +?9)_[ȼ8'I#G?-=tގ.cIfW}ݫepגo)VEDickT8潰ɃZ٧&
ퟁ* +VϤL5k_Zxk88X8BR
wCqW.OTk}ãzpN?\ڔ +O1B ]^O-)r0Pޡ4>*q!Ɣ:ԁ[/[z/:3EW%V)3r+Y;RI֫e:g|W\}WE +"5kY7Яi*j-@:diG~kZ
(#o9Q!=)~ 2ghOZj@:pcǫ5ظ +jrO$t6~թ=z/c?mYM-ziZ5335iEgݙq}"mG&'
?#DR-Qk>d +YͤQ@q@s7Q|Uwx?CX$F6+"~en-WN^#j=g^ᗌQOM[ZZq* + +@<:朞P4tu^{G5g + +=(~/gxP ++`r" +_X@_%}$uClSLHOp +lpEj7WLdl-{AZwWm[PǾ +>=P + +T U +&@S[WTpAMͪ~=ύ 5-<eyhg +pH32<rݭזu9#;z&f17s k_9_ ]O#`}U㫕jqi[[iM7Ⲁ,.P?oC !}\pVUlLUznOg>[.SX38jL32[kT9A5C/L@}/3u[{r1Xܶ_gZw:[MTK<'IC@kodHE݅*7@œ;{j~8xH-C|ɏZA. +U~1Zptڼ]m3w-QuE]7)Pϳ
zC;[YC{:-ʪ|~P"MRMVLo[m
|qm +ZU>3(nVzqͥZ7;^`,U~R϶1҆Q`;ėU~cj֝%*(\۹r{UQ̩7_TxaB!B!B!B!B!B!B!B!B!f\}7ߺՋ뾯}]ӼFg<dOwſGן>ZsϦ;_ƌGbLXHLƍ2x$ϘB"ɔ_sTLTrnH6}t6ʦsƂ:(ID2ɚ>lHĒ0t$x:I&30Oa.1D$=L3/ͤd"xL6ƿT2'$r2tFO*c,rd,5*GaߢǏ<|ދGHU~}E/gCGw}ڮ{*y/?RLT^<W[_>M"y.U&KyߋmK帼~߫˸]}>i^y߫:{*9*я9ީbrb.{_5ֿ~!}K_|ۻWϿw{4d1a#35nqqcU\0c{Ə&f<nᏇ>3G2r/C§ +f# ӴŽ\ɛ>tO}ߡib,Z,}wz:̡O&c#Jm[z˒ү9~_2#D*.i.F6@ ̤rFC+=xxтwOJoqȋʋl|r}|YÃ8wi3TY( ?7ٍ
PM,bfi)HӴ&n#L*)Y;|/I't(U ƐG83{T\2wx8ge7hgj]%KK?课_1i{ҠS%92sppT*wz'(:X>߷,
^ۏrcB$+o߿ueG& + +U/hm-!B!B!B!B!uB!r'E4HoAB!B ySČHΌ'3I#d3x +i3Mdp$ә{A`TF$siv@X1 }>n'"鄙uE̜,ޏGbd!3l$JR;>uxwQyfH2t<Wsg3I]Lc-_,ͥ*^T.eE.䲸2dG*.`Ʋ[Ì? +d:R +[!3CJ>4Kg&JOC?a[d%T(hYDT+l,ͤfKj{Ɋ7tJ!BBes*9ɭ/^S=gJyX;P^lIuUf\*L%sKX?v3*$7RIC\^ٜOL%bD,ݺKH*I?!+Q4VSNu)44lT:'Qپx^\X֎DC1T(R}B*H+)q3`+CaH&tfV=PzM㟿WIt/bƋxgE(lwQ#ݳJa%oڟ}ybzѪUaJ{HMerZYk??T2kd$in.(ݹ$ef.~f}2>lx)uo{NJ7KxR*1*Kjy,%l:Q}2%dw6%j'AJ_m\)lJ7K_\JUOtj+%+T9{uT~\.Yx
D +DgxTZ7eH*nt=a)D>ʔ_jY+*~zԺR&ZR="V۷oY+Z̴P% |<KIbfJD<2bݤ f3YU۪*xό&2s+"lnͺe}-nU4g5\yk{x~O~Gb_|߷UD6b<?xa͐ύx2;lo*߅< doAG6Wwh6d.)3Iգ 2$#TPR"TfH<L$%y-JTƩ#Ocif +nDEI$ 3fd3G}YosO~'B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!zBtVxVYu444x +J:+N:AsNs^sQӨijj)˃^~= +ӵ8V}EӦitiz5r!7ayPPŧk%^Qd(Ui&5~MHr!7ayPZ~El^+:]E"FP'KKKKkkkjnjh>|y\M|X59E"^-J:+NWxU1gϞ}w7OO_5>'y<%f)*ZIg1PNW.\jRxt}plu-P\T$k%^+-G +ӵ9k%^kR\*ZIgJȫk%B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!Bycz녩V
Lj`USjTZ50ժV
Lj`USjRB!B!B!B!B!߿u|뻩=ֽ^?]}Eן_{ǭ/>7c~Ȉ~デFp}_qkxvӈ}O/mCGwf<$DXHH6e)ӌ$Siv_^w_|QKws{7>j7nO>{nfT223Ft|`A5ʖ3_?||ӯ5:_wpO=-[?ݿزULB'V*st@6rc/W(/|/zyo]0>O$#D.m,H<Kc$kdJec?@Y#>я&?|7S?.o$X:hB!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!R
Rxt%Z[gJYᵶ +ӕkm^+!g:+NWB +uVx^k:]듆WCYuzܥq5օ[L9Ay
\ɿ{x,*v.h<%M65-6Zmi|ǁx]xK)wYOO߂koo.,\^8'DgO<:+"Z&25Y +ҋNMWWWGkOӯkF41fBch&5SE_O>gax=6~HxOm-%bH%$~;^?zJX?A~ "# %& hORIdIpI|{vHIf$va9
QCC8NVH*<Ԡ>%7QK%5@ CP8_XXD"h4x<H$d*d2l6/iVVVVWW666677w4W?3$0. +)HC$_ůF:#v?yAsx,-KfCH)0H@K}oz7nܼy֭[o߾sݻw?O>O?>?|ѣGO<y
?3<5{x%)$tS.I[<Ri$gxg/&~)Z~>i>0C )iM$mZ[gK=[VM
k(]R+O|߈od8>Iڏv"[CVy+?G~Qj8U^kplkkϊ_sVI_p(Y^Xƿwloر=?ׄd/+뤆+BRCxs斬%=g_'+U$-p2 ƁxQ$pZUr>[dq6pv4Ť9#guVduPP!RnU{;"OYj!XžZ[gZ{Ɓd0&C)k+W!R_G(ؗkm^kMYI;{U3 +SfepV:2d=:2CR5+=e(rb\X&PTfe*CXEr\8)`MZ:!Zj*Y聾2rjZzpRX&mww75Kip,!EEVI t˦,!UPQДtCeZ4\X醒j8Y:DCEVI +\8)`MZ:nmmf *CX+'9B!pR.,ً/RTf/9ZA8'I;00FR5MvPZT.jr\X4i@jꨨY((] +I +\8)`Mڱjq,}AekQQ$IpR.,uERe&hJ:2(] +R\8)`Mډ t5KHTԬB_PZT.jLF.&aCCC===,!QQ}AekQQdYpR.,2CRN/a-*J|>/NʅAvjjjdd%jjj,*CXE`iiI.&h___SS5KH8iʂ2(]jI +eA_P.jr\ Ѥ͖uJR8iʂ2(] +vvvI +eA_PZT.j7nܐ'ljD"uJR8iʂ2(] +n)Nʅd29??OR55ʒNcV
[Q֭[r\X4iSf B_PZw۷o˅raML&DYBLr(bWԆT&Z;wIIf5KHuh֢tQ+{\8)`M|>OrjjBʤuQ+IRբIieYz.(h
Z'|"NʅiRE֢tQ+OI +>spR.,mllP` ,bWDiZ8)`Md2999yZSETZݕ&5Ki(,t+"X*CXEÇr\X4iS5KHuh֢tQ+x\8)`MڝjpTPbWDTZ'OI +={&NʕMZjSRYQ+bWD,TZ_~)NʅA|guJRk*JEŋr\jѤf 9%vBA,VĮ`Be*JoF.Ҥqloo/5KHk*CXEo'IKrJ4!jEq,TZw}'NʅA͛l fے{֢tQ+~&Nʅ YBNQBM],ıPZT.j?I +~_ʅravФ}j(f c5Z??ȅravYBNOf#D]"֢tQ+I +ErYB^9vBAVĮ`YZ-S+w(S%?o~=|ƍL0&j: +?C.,Rf,VĮ`BewTZʅ2OoѣG7ofЬ 68fEBdKrvX*Y(*VĮ`Be$>l?w/~ɓ[nfjhYB^eE+"XıPŹe/˧O}{iiizz%`=KHm,! -8>KHmyPoLHmu=\?KHmQYSA5y4} NYoL]<G7jpT<7h`,y'$~qQZAXf y%iظ{ӧOҸ(I ,@ +-Mr+U +aE.jK1STGE^phMcuR + PN=,!']
ŵi\VyP.=,!'IpZjUZ,5K)qѬuliENj=يYjYBXۢqWZ.,!'$֢r*t@QZPP\ENj%0f 9=N}u,UuRTV5Kix)ͺ(IG5u^!f&X:U;,!4\'5Ҙf 9!jVcE.j`%kVR\'Z15KBVf]:YBNf߱mToEZH%ErB,&poN-D.,5K q]e\pE,!$ٖqTvq`YBNȱ+ KN+N BjjXEވ)AHmpz*N BjZ 6Z%N BjZ 6Z%N B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!g;B!B!B!B!B!r +LaUӮzvô]'r
Z I!Y0}ag{l!Fb,C+zaaVHa]?7nNvzӣhG;=Nvzڽ +ǧY+^˞̟}&o4~ښseЋLVkVrZwd:yc2.;r žg +s$]ǫʘsel'IAݖU
][xa4c{QP2p3{6z2˓=<{ھ`Aֿ7|koͻN%Us&ġs,Y^&GiYj1Ⱥyx7x\tb<({gqG^o7-/oyU= =
ܣ2\ޑ./f9ƎM`AW"DɨCXhlLMxQ.Gj=Ǝp]$n}kcǮd':U}N?]N=z_p=Ma%V]p`&7<&%ow?rkq%l6[SN,kΑioMU]6-0^knn8w'Mط6o
+K7.3Y`Y%vj5ޕ+{ѷƺ\Xr<;IExIr h8n˪ޣ((}GYG{<yј;p\;e$gX=vmx=<{ھ`Aֿ7|koͻNUs&qЎ 4y{Y\ϳLƏrӺbuw~p{o yPd k,oZQ_L~?{,9zGB.w/Y,c˳j]vimTlOO9^ٹ>Vw9eq<($N';ϋC3XQ}{AZcmks乜}[ںiVðImv/h]/?9<FOyo=}z+ЫNw/]"ISM9}6-o6.t7<> +0'0'8ҜM Ѝ_3z+v5<lxӣ\Os兮i +HԗKI+ri/nvK6#n K1#w}=6AVEeDFF8ykskg=637Ə)X߂orSSɄm||==>LxӃ3ȇߛ7e<~->L6S{.6sIJu֥fbK69or6l붗KTbi(fs}_w=OM}.TANasdliu[^FČx+l{GoNGaͫzr~z\ݼsy̱9Ale1g7G<0].'NCFXC%X%Xm%'eZfz(-ia ;tFIwCUsŗ]79[|,UѸ +ID.~i/WҺ߳=}f$'bɴ.]~zrk^;|4Oo6:<Yi8|&E)Ch*.+9,ĕoCL(f8lK+i6=EfH +f +z0M`FiK[ͦʙ*aÇZ7184Ӡo3``꾣/gOmm<%Ms(A=6H{'$;o]QQ*9;+,C-.Bx؎}VxG;XZdkM6
עΛ%ErlBFQxw|]-fG0Eyҍئԣ(ĺ] :.kK꜡j.FlId`kj`Ƅ +> p<ZjgY24=0 +7C\B@!tǛt?T~3^MD5Us(ҷPgUV)!p8 +"XL}zfPAj'^;tR#V
E >,9s NTF vԅiǑ6r1(d<<%*tjQ1"GIPu(2LTAЛ=X-ĜHz<@6L.ڛxLY|sl]! M>9fŨs\p +(3|]&dmdˬPa26<U8 +JniΆLḦY]Y8Bٴ֡**TsfBo ,/~]L
, R25R*u9n8t,
%~ +=Jɔ>Ta.`phv|jPU~A +PpSYF4@4dA҆uQlHGǮFcVos~tݗs;kLQ6Fq2pȊOǖ< jJdZgIUaQ#'#63-*4>0C<OH:ehG(ĔUJ-${<WI EJFkoW,N&Q&
ڹD}#aƖ,?Hփn/!;PE(4H3q" +gbqQQJ9y=jg,&d. @L4cWגxdH7EԢRB E+vo;Y֪yV,/~[W4{FAj.@A'3%w: +($ +ҵTi$bƆmwo +rGt406܈FA,[kADRo$DT;$OYgOV (&Enwvd $},:3Inf@a7!3sC`7w1H.+ +oGh~w7|*.eU!Fd:r'K~ICq$PU#)P +q +/GS0 gs +486 0 0 480 273.6162109 546.6401367 cm +/Im0 Do +Q +
endstream
endobj
757 0 obj
<</BBox[317.616 983.64 715.616 585.64]/Group 915 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 916 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 793 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +398 0 0 398 317.6162109 585.6401367 cm +/Im0 Do +Q +
endstream
endobj
758 0 obj
<</BBox[385.616 858.64 653.616 814.64]/Group 917 0 R/Length 58/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 918 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 796 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +268 0 0 44 385.6162109 814.6401367 cm +/Im0 Do +Q +
endstream
endobj
759 0 obj
<</BBox[382.616 888.64 656.616 585.64]/Group 919 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 920 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 799 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +274 0 0 303 382.6162109 585.6401367 cm +/Im0 Do +Q +
endstream
endobj
760 0 obj
<</BBox[408.0 947.0 704.0 856.0]/Group 921 0 R/Length 42/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 922 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 802 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +296 0 0 91 408 856 cm +/Im0 Do +Q +
endstream
endobj
761 0 obj
<</BBox[246.0 1106.0 718.0 580.0]/Group 923 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 924 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 805 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +472 0 0 526 246 580 cm +/Im0 Do +Q +
endstream
endobj
762 0 obj
<</BBox[283.0 1022.0 796.0 520.0]/Group 925 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ColorSpace<</CS0 788 0 R>>/ExtGState<</GS0 926 0 R>>/ProcSet[/PDF/ImageC/ImageI]/XObject<</Im0 808 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +513 0 0 502 283 520 cm +/Im0 Do +Q +
endstream
endobj
925 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
926 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 927 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
927 0 obj
<</BC 928 0 R/G 929 0 R/S/Luminosity/Type/Mask>>
endobj
928 0 obj
[0.0 0.0 0.0]
endobj
929 0 obj
<</BBox[283.0 1022.0 796.0 520.0]/Group 930 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 833 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +513 0 0 502 283 520 cm +/Im0 Do +Q +
endstream
endobj
930 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
923 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
924 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 931 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
931 0 obj
<</BC 932 0 R/G 933 0 R/S/Luminosity/Type/Mask>>
endobj
932 0 obj
[0.0 0.0 0.0]
endobj
933 0 obj
<</BBox[246.0 1106.0 718.0 580.0]/Group 934 0 R/Length 43/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 839 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +472 0 0 526 246 580 cm +/Im0 Do +Q +
endstream
endobj
934 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
921 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
922 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 935 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
935 0 obj
<</BC 936 0 R/G 937 0 R/S/Luminosity/Type/Mask>>
endobj
936 0 obj
[0.0 0.0 0.0]
endobj
937 0 obj
<</BBox[408.0 947.0 704.0 856.0]/Group 938 0 R/Length 42/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 845 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +296 0 0 91 408 856 cm +/Im0 Do +Q +
endstream
endobj
938 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
919 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
920 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 939 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
939 0 obj
<</BC 940 0 R/G 941 0 R/S/Luminosity/Type/Mask>>
endobj
940 0 obj
[0.0 0.0 0.0]
endobj
941 0 obj
<</BBox[382.616 888.64 656.616 585.64]/Group 942 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 851 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +274 0 0 303 382.6162109 585.6401367 cm +/Im0 Do +Q +
endstream
endobj
942 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
917 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
918 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 943 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
943 0 obj
<</BC 944 0 R/G 945 0 R/S/Luminosity/Type/Mask>>
endobj
944 0 obj
[0.0 0.0 0.0]
endobj
945 0 obj
<</BBox[385.616 858.64 653.616 814.64]/Group 946 0 R/Length 58/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 857 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +268 0 0 44 385.6162109 814.6401367 cm +/Im0 Do +Q +
endstream
endobj
946 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
915 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
916 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 947 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
947 0 obj
<</BC 948 0 R/G 949 0 R/S/Luminosity/Type/Mask>>
endobj
948 0 obj
[0.0 0.0 0.0]
endobj
949 0 obj
<</BBox[317.616 983.64 715.616 585.64]/Group 950 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 863 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +398 0 0 398 317.6162109 585.6401367 cm +/Im0 Do +Q +
endstream
endobj
950 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
913 0 obj
<</I false/K false/S/Transparency/Type/Group>>
endobj
914 0 obj
<</AIS true/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 951 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
951 0 obj
<</BC 952 0 R/G 953 0 R/S/Luminosity/Type/Mask>>
endobj
952 0 obj
[0.0 0.0 0.0]
endobj
953 0 obj
<</BBox[273.616 1026.64 759.616 546.64]/Group 954 0 R/Length 59/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 747 0 R>>/ProcSet[/PDF/ImageB]/XObject<</Im0 869 0 R>>>>/Subtype/Form>>stream
+q +/GS0 gs +486 0 0 480 273.6162109 546.6401367 cm +/Im0 Do +Q +
endstream
endobj
954 0 obj
<</CS 735 0 R/I false/K false/S/Transparency/Type/Group>>
endobj
746 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 955 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
748 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 956 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
749 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 957 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
750 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 958 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
751 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask 959 0 R/Type/ExtGState/ca 1.0/op false>>
endobj
959 0 obj
<</G 960 0 R/S/Luminosity/Type/Mask>>
endobj
960 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 961 0 R/Length 84/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +15.8993082 0 0 -16.1025677 1392.8847656 296.171875 cm +BX /Sh0 sh EX Q +
endstream
endobj
961 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
958 0 obj
<</G 962 0 R/S/Luminosity/Type/Mask>>
endobj
962 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 963 0 R/Length 85/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +11.9217148 0 0 -12.0797682 1392.7294922 335.4882812 cm +BX /Sh0 sh EX Q +
endstream
endobj
963 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
957 0 obj
<</G 964 0 R/S/Luminosity/Type/Mask>>
endobj
964 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 965 0 R/Length 87/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +225.9361572 0 0 -225.9361572 1389.9453125 728.7958984 cm +BX /Sh0 sh EX Q +
endstream
endobj
965 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
956 0 obj
<</G 966 0 R/S/Luminosity/Type/Mask>>
endobj
966 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 967 0 R/Length 83/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +7.9551892 0 0 -8.0455885 1392.7294922 366.4384766 cm +BX /Sh0 sh EX Q +
endstream
endobj
967 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
955 0 obj
<</G 968 0 R/S/Luminosity/Type/Mask>>
endobj
968 0 obj
<</BBox[-32768.0 32767.0 32767.0 -32768.0]/Group 969 0 R/Length 84/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 744 0 R>>/Shading<</Sh0 892 0 R>>>>/Subtype/Form>>stream
+q +0 g +/GS0 gs +225.9359131 0 0 -225.9359131 509.96875 794.0385742 cm +BX /Sh0 sh EX Q +
endstream
endobj
969 0 obj
<</CS/DeviceGray/I false/K false/S/Transparency/Type/Group>>
endobj
912 0 obj
[/ICCBased 819 0 R]
endobj
5 0 obj
<</Intent 154 0 R/Name(Calque 1)/Type/OCG/Usage 155 0 R>>
endobj
248 0 obj
<</Intent 395 0 R/Name(Calque 1)/Type/OCG/Usage 396 0 R>>
endobj
489 0 obj
<</Intent 636 0 R/Name(Calque 1)/Type/OCG/Usage 637 0 R>>
endobj
636 0 obj
[/View/Design]
endobj
637 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
395 0 obj
[/View/Design]
endobj
396 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
154 0 obj
[/View/Design]
endobj
155 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
731 0 obj
[730 0 R]
endobj
970 0 obj
<</CreationDate(D:20140916123631+02'00')/Creator(Adobe Illustrator CS6 \(Macintosh\))/ModDate(D:20140918115258+02'00')/Producer(Adobe PDF library 10.01)/Title(logotalerv2)>>
endobj
xref
0 971
0000000004 65535 f
+0000000016 00000 n
+0000000194 00000 n
+0000050669 00000 n
+0000000006 00000 f
+0000539738 00000 n
+0000000009 00000 f
+0000050726 00000 n
+0000051599 00000 n
+0000000010 00000 f
+0000000011 00000 f
+0000000012 00000 f
+0000000013 00000 f
+0000000014 00000 f
+0000000015 00000 f
+0000000016 00000 f
+0000000017 00000 f
+0000000018 00000 f
+0000000019 00000 f
+0000000020 00000 f
+0000000021 00000 f
+0000000022 00000 f
+0000000023 00000 f
+0000000024 00000 f
+0000000025 00000 f
+0000000026 00000 f
+0000000027 00000 f
+0000000028 00000 f
+0000000029 00000 f
+0000000030 00000 f
+0000000031 00000 f
+0000000032 00000 f
+0000000033 00000 f
+0000000034 00000 f
+0000000035 00000 f
+0000000036 00000 f
+0000000037 00000 f
+0000000038 00000 f
+0000000039 00000 f
+0000000040 00000 f
+0000000041 00000 f
+0000000042 00000 f
+0000000043 00000 f
+0000000044 00000 f
+0000000045 00000 f
+0000000046 00000 f
+0000000047 00000 f
+0000000048 00000 f
+0000000049 00000 f
+0000000050 00000 f
+0000000051 00000 f
+0000000052 00000 f
+0000000053 00000 f
+0000000054 00000 f
+0000000055 00000 f
+0000000056 00000 f
+0000000057 00000 f
+0000000058 00000 f
+0000000059 00000 f
+0000000060 00000 f
+0000000061 00000 f
+0000000062 00000 f
+0000000063 00000 f
+0000000064 00000 f
+0000000065 00000 f
+0000000066 00000 f
+0000000067 00000 f
+0000000068 00000 f
+0000000069 00000 f
+0000000070 00000 f
+0000000071 00000 f
+0000000072 00000 f
+0000000073 00000 f
+0000000074 00000 f
+0000000075 00000 f
+0000000076 00000 f
+0000000077 00000 f
+0000000078 00000 f
+0000000079 00000 f
+0000000080 00000 f
+0000000081 00000 f
+0000000082 00000 f
+0000000083 00000 f
+0000000084 00000 f
+0000000085 00000 f
+0000000086 00000 f
+0000000087 00000 f
+0000000088 00000 f
+0000000089 00000 f
+0000000090 00000 f
+0000000091 00000 f
+0000000092 00000 f
+0000000093 00000 f
+0000000094 00000 f
+0000000095 00000 f
+0000000096 00000 f
+0000000097 00000 f
+0000000098 00000 f
+0000000099 00000 f
+0000000100 00000 f
+0000000101 00000 f
+0000000102 00000 f
+0000000103 00000 f
+0000000104 00000 f
+0000000105 00000 f
+0000000106 00000 f
+0000000107 00000 f
+0000000108 00000 f
+0000000109 00000 f
+0000000110 00000 f
+0000000111 00000 f
+0000000112 00000 f
+0000000113 00000 f
+0000000114 00000 f
+0000000115 00000 f
+0000000116 00000 f
+0000000117 00000 f
+0000000118 00000 f
+0000000119 00000 f
+0000000120 00000 f
+0000000121 00000 f
+0000000122 00000 f
+0000000123 00000 f
+0000000124 00000 f
+0000000125 00000 f
+0000000126 00000 f
+0000000127 00000 f
+0000000128 00000 f
+0000000129 00000 f
+0000000130 00000 f
+0000000131 00000 f
+0000000132 00000 f
+0000000133 00000 f
+0000000134 00000 f
+0000000135 00000 f
+0000000136 00000 f
+0000000137 00000 f
+0000000138 00000 f
+0000000139 00000 f
+0000000140 00000 f
+0000000141 00000 f
+0000000142 00000 f
+0000000143 00000 f
+0000000144 00000 f
+0000000145 00000 f
+0000000146 00000 f
+0000000147 00000 f
+0000000148 00000 f
+0000000149 00000 f
+0000000150 00000 f
+0000000151 00000 f
+0000000152 00000 f
+0000000153 00000 f
+0000000156 00000 f
+0000540197 00000 n
+0000540229 00000 n
+0000000157 00000 f
+0000000158 00000 f
+0000000159 00000 f
+0000000160 00000 f
+0000000161 00000 f
+0000000162 00000 f
+0000000163 00000 f
+0000000164 00000 f
+0000000165 00000 f
+0000000166 00000 f
+0000000167 00000 f
+0000000168 00000 f
+0000000169 00000 f
+0000000170 00000 f
+0000000171 00000 f
+0000000172 00000 f
+0000000173 00000 f
+0000000174 00000 f
+0000000175 00000 f
+0000000176 00000 f
+0000000177 00000 f
+0000000178 00000 f
+0000000179 00000 f
+0000000180 00000 f
+0000000181 00000 f
+0000000182 00000 f
+0000000183 00000 f
+0000000184 00000 f
+0000000185 00000 f
+0000000186 00000 f
+0000000187 00000 f
+0000000188 00000 f
+0000000189 00000 f
+0000000190 00000 f
+0000000191 00000 f
+0000000192 00000 f
+0000000193 00000 f
+0000000194 00000 f
+0000000195 00000 f
+0000000196 00000 f
+0000000197 00000 f
+0000000198 00000 f
+0000000199 00000 f
+0000000200 00000 f
+0000000201 00000 f
+0000000202 00000 f
+0000000203 00000 f
+0000000204 00000 f
+0000000205 00000 f
+0000000206 00000 f
+0000000207 00000 f
+0000000208 00000 f
+0000000209 00000 f
+0000000210 00000 f
+0000000211 00000 f
+0000000212 00000 f
+0000000213 00000 f
+0000000214 00000 f
+0000000215 00000 f
+0000000216 00000 f
+0000000217 00000 f
+0000000218 00000 f
+0000000219 00000 f
+0000000220 00000 f
+0000000221 00000 f
+0000000222 00000 f
+0000000223 00000 f
+0000000224 00000 f
+0000000225 00000 f
+0000000226 00000 f
+0000000227 00000 f
+0000000228 00000 f
+0000000229 00000 f
+0000000230 00000 f
+0000000231 00000 f
+0000000232 00000 f
+0000000233 00000 f
+0000000234 00000 f
+0000000235 00000 f
+0000000236 00000 f
+0000000237 00000 f
+0000000238 00000 f
+0000000239 00000 f
+0000000240 00000 f
+0000000241 00000 f
+0000000242 00000 f
+0000000243 00000 f
+0000000244 00000 f
+0000000245 00000 f
+0000000246 00000 f
+0000000247 00000 f
+0000000249 00000 f
+0000539811 00000 n
+0000000250 00000 f
+0000000251 00000 f
+0000000252 00000 f
+0000000253 00000 f
+0000000254 00000 f
+0000000255 00000 f
+0000000256 00000 f
+0000000257 00000 f
+0000000258 00000 f
+0000000259 00000 f
+0000000260 00000 f
+0000000261 00000 f
+0000000262 00000 f
+0000000263 00000 f
+0000000264 00000 f
+0000000265 00000 f
+0000000266 00000 f
+0000000267 00000 f
+0000000268 00000 f
+0000000269 00000 f
+0000000270 00000 f
+0000000271 00000 f
+0000000272 00000 f
+0000000273 00000 f
+0000000274 00000 f
+0000000275 00000 f
+0000000276 00000 f
+0000000277 00000 f
+0000000278 00000 f
+0000000279 00000 f
+0000000280 00000 f
+0000000281 00000 f
+0000000282 00000 f
+0000000283 00000 f
+0000000284 00000 f
+0000000285 00000 f
+0000000286 00000 f
+0000000287 00000 f
+0000000288 00000 f
+0000000289 00000 f
+0000000290 00000 f
+0000000291 00000 f
+0000000292 00000 f
+0000000293 00000 f
+0000000294 00000 f
+0000000295 00000 f
+0000000296 00000 f
+0000000297 00000 f
+0000000298 00000 f
+0000000299 00000 f
+0000000300 00000 f
+0000000301 00000 f
+0000000302 00000 f
+0000000303 00000 f
+0000000304 00000 f
+0000000305 00000 f
+0000000306 00000 f
+0000000307 00000 f
+0000000308 00000 f
+0000000309 00000 f
+0000000310 00000 f
+0000000311 00000 f
+0000000312 00000 f
+0000000313 00000 f
+0000000314 00000 f
+0000000315 00000 f
+0000000316 00000 f
+0000000317 00000 f
+0000000318 00000 f
+0000000319 00000 f
+0000000320 00000 f
+0000000321 00000 f
+0000000322 00000 f
+0000000323 00000 f
+0000000324 00000 f
+0000000325 00000 f
+0000000326 00000 f
+0000000327 00000 f
+0000000328 00000 f
+0000000329 00000 f
+0000000330 00000 f
+0000000331 00000 f
+0000000332 00000 f
+0000000333 00000 f
+0000000334 00000 f
+0000000335 00000 f
+0000000336 00000 f
+0000000337 00000 f
+0000000338 00000 f
+0000000339 00000 f
+0000000340 00000 f
+0000000341 00000 f
+0000000342 00000 f
+0000000343 00000 f
+0000000344 00000 f
+0000000345 00000 f
+0000000346 00000 f
+0000000347 00000 f
+0000000348 00000 f
+0000000349 00000 f
+0000000350 00000 f
+0000000351 00000 f
+0000000352 00000 f
+0000000353 00000 f
+0000000354 00000 f
+0000000355 00000 f
+0000000356 00000 f
+0000000357 00000 f
+0000000358 00000 f
+0000000359 00000 f
+0000000360 00000 f
+0000000361 00000 f
+0000000362 00000 f
+0000000363 00000 f
+0000000364 00000 f
+0000000365 00000 f
+0000000366 00000 f
+0000000367 00000 f
+0000000368 00000 f
+0000000369 00000 f
+0000000370 00000 f
+0000000371 00000 f
+0000000372 00000 f
+0000000373 00000 f
+0000000374 00000 f
+0000000375 00000 f
+0000000376 00000 f
+0000000377 00000 f
+0000000378 00000 f
+0000000379 00000 f
+0000000380 00000 f
+0000000381 00000 f
+0000000382 00000 f
+0000000383 00000 f
+0000000384 00000 f
+0000000385 00000 f
+0000000386 00000 f
+0000000387 00000 f
+0000000388 00000 f
+0000000389 00000 f
+0000000390 00000 f
+0000000391 00000 f
+0000000392 00000 f
+0000000393 00000 f
+0000000394 00000 f
+0000000397 00000 f
+0000540079 00000 n
+0000540111 00000 n
+0000000398 00000 f
+0000000399 00000 f
+0000000400 00000 f
+0000000401 00000 f
+0000000402 00000 f
+0000000403 00000 f
+0000000404 00000 f
+0000000405 00000 f
+0000000406 00000 f
+0000000407 00000 f
+0000000408 00000 f
+0000000409 00000 f
+0000000410 00000 f
+0000000411 00000 f
+0000000412 00000 f
+0000000413 00000 f
+0000000414 00000 f
+0000000415 00000 f
+0000000416 00000 f
+0000000417 00000 f
+0000000418 00000 f
+0000000419 00000 f
+0000000420 00000 f
+0000000421 00000 f
+0000000422 00000 f
+0000000423 00000 f
+0000000424 00000 f
+0000000425 00000 f
+0000000426 00000 f
+0000000427 00000 f
+0000000428 00000 f
+0000000429 00000 f
+0000000430 00000 f
+0000000431 00000 f
+0000000432 00000 f
+0000000433 00000 f
+0000000434 00000 f
+0000000435 00000 f
+0000000436 00000 f
+0000000437 00000 f
+0000000438 00000 f
+0000000439 00000 f
+0000000440 00000 f
+0000000441 00000 f
+0000000442 00000 f
+0000000443 00000 f
+0000000444 00000 f
+0000000445 00000 f
+0000000446 00000 f
+0000000447 00000 f
+0000000448 00000 f
+0000000449 00000 f
+0000000450 00000 f
+0000000451 00000 f
+0000000452 00000 f
+0000000453 00000 f
+0000000454 00000 f
+0000000455 00000 f
+0000000456 00000 f
+0000000457 00000 f
+0000000458 00000 f
+0000000459 00000 f
+0000000460 00000 f
+0000000461 00000 f
+0000000462 00000 f
+0000000463 00000 f
+0000000464 00000 f
+0000000465 00000 f
+0000000466 00000 f
+0000000467 00000 f
+0000000468 00000 f
+0000000469 00000 f
+0000000470 00000 f
+0000000471 00000 f
+0000000472 00000 f
+0000000473 00000 f
+0000000474 00000 f
+0000000475 00000 f
+0000000476 00000 f
+0000000477 00000 f
+0000000478 00000 f
+0000000479 00000 f
+0000000480 00000 f
+0000000481 00000 f
+0000000482 00000 f
+0000000483 00000 f
+0000000484 00000 f
+0000000485 00000 f
+0000000486 00000 f
+0000000487 00000 f
+0000000488 00000 f
+0000000000 00000 f
+0000539886 00000 n
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000539961 00000 n
+0000539993 00000 n
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000255776 00000 n
+0000540315 00000 n
+0000526925 00000 n
+0000530281 00000 n
+0000272389 00000 n
+0000072965 00000 n
+0000080180 00000 n
+0000077135 00000 n
+0000078917 00000 n
+0000078454 00000 n
+0000076711 00000 n
+0000076244 00000 n
+0000075807 00000 n
+0000072621 00000 n
+0000268801 00000 n
+0000268915 00000 n
+0000536963 00000 n
+0000100986 00000 n
+0000537080 00000 n
+0000537197 00000 n
+0000537314 00000 n
+0000537431 00000 n
+0000255969 00000 n
+0000257133 00000 n
+0000257476 00000 n
+0000254768 00000 n
+0000530345 00000 n
+0000530669 00000 n
+0000530992 00000 n
+0000531314 00000 n
+0000531637 00000 n
+0000531937 00000 n
+0000532239 00000 n
+0000058286 00000 n
+0000058829 00000 n
+0000064795 00000 n
+0000065059 00000 n
+0000065376 00000 n
+0000071367 00000 n
+0000071636 00000 n
+0000071964 00000 n
+0000072239 00000 n
+0000052478 00000 n
+0000055983 00000 n
+0000269032 00000 n
+0000269149 00000 n
+0000269266 00000 n
+0000269383 00000 n
+0000269500 00000 n
+0000056047 00000 n
+0000056374 00000 n
+0000056701 00000 n
+0000057027 00000 n
+0000057354 00000 n
+0000057664 00000 n
+0000057975 00000 n
+0000526888 00000 n
+0000199803 00000 n
+0000081996 00000 n
+0000228123 00000 n
+0000199867 00000 n
+0000155074 00000 n
+0000178049 00000 n
+0000155138 00000 n
+0000151139 00000 n
+0000153025 00000 n
+0000151203 00000 n
+0000140569 00000 n
+0000146002 00000 n
+0000140633 00000 n
+0000126432 00000 n
+0000133614 00000 n
+0000126496 00000 n
+0000101099 00000 n
+0000114198 00000 n
+0000101163 00000 n
+0000080732 00000 n
+0000091259 00000 n
+0000080796 00000 n
+0000080227 00000 n
+0000079327 00000 n
+0000078963 00000 n
+0000078501 00000 n
+0000077183 00000 n
+0000076758 00000 n
+0000076291 00000 n
+0000075854 00000 n
+0000072668 00000 n
+0000073002 00000 n
+0000073158 00000 n
+0000076140 00000 n
+0000076586 00000 n
+0000077043 00000 n
+0000078037 00000 n
+0000078795 00000 n
+0000079247 00000 n
+0000080558 00000 n
+0000082042 00000 n
+0000091204 00000 n
+0000091375 00000 n
+0000091441 00000 n
+0000091472 00000 n
+0000091749 00000 n
+0000091824 00000 n
+0000102529 00000 n
+0000114314 00000 n
+0000114380 00000 n
+0000114411 00000 n
+0000114688 00000 n
+0000114763 00000 n
+0000127223 00000 n
+0000133730 00000 n
+0000133796 00000 n
+0000133827 00000 n
+0000134103 00000 n
+0000134178 00000 n
+0000141446 00000 n
+0000146118 00000 n
+0000146184 00000 n
+0000146215 00000 n
+0000146508 00000 n
+0000146583 00000 n
+0000151556 00000 n
+0000153141 00000 n
+0000153207 00000 n
+0000153238 00000 n
+0000153530 00000 n
+0000153605 00000 n
+0000156876 00000 n
+0000178165 00000 n
+0000178231 00000 n
+0000178262 00000 n
+0000178555 00000 n
+0000178630 00000 n
+0000202059 00000 n
+0000228239 00000 n
+0000228305 00000 n
+0000228336 00000 n
+0000228629 00000 n
+0000228704 00000 n
+0000254922 00000 n
+0000255143 00000 n
+0000255262 00000 n
+0000255367 00000 n
+0000255458 00000 n
+0000255549 00000 n
+0000255655 00000 n
+0000255851 00000 n
+0000255883 00000 n
+0000268483 00000 n
+0000268571 00000 n
+0000265290 00000 n
+0000257856 00000 n
+0000258107 00000 n
+0000265561 00000 n
+0000271957 00000 n
+0000271527 00000 n
+0000271093 00000 n
+0000270661 00000 n
+0000269617 00000 n
+0000269672 00000 n
+0000269970 00000 n
+0000270048 00000 n
+0000270205 00000 n
+0000270426 00000 n
+0000270501 00000 n
+0000270581 00000 n
+0000270716 00000 n
+0000271015 00000 n
+0000271148 00000 n
+0000271449 00000 n
+0000271582 00000 n
+0000271879 00000 n
+0000272012 00000 n
+0000272311 00000 n
+0000272465 00000 n
+0000272712 00000 n
+0000273723 00000 n
+0000284180 00000 n
+0000349769 00000 n
+0000415358 00000 n
+0000480947 00000 n
+0000539701 00000 n
+0000536321 00000 n
+0000536385 00000 n
+0000535680 00000 n
+0000535744 00000 n
+0000535040 00000 n
+0000535104 00000 n
+0000534399 00000 n
+0000534463 00000 n
+0000533781 00000 n
+0000533845 00000 n
+0000533161 00000 n
+0000533225 00000 n
+0000532541 00000 n
+0000532605 00000 n
+0000532721 00000 n
+0000532787 00000 n
+0000532818 00000 n
+0000533086 00000 n
+0000533341 00000 n
+0000533407 00000 n
+0000533438 00000 n
+0000533706 00000 n
+0000533961 00000 n
+0000534027 00000 n
+0000534058 00000 n
+0000534324 00000 n
+0000534579 00000 n
+0000534645 00000 n
+0000534676 00000 n
+0000534965 00000 n
+0000535220 00000 n
+0000535286 00000 n
+0000535317 00000 n
+0000535605 00000 n
+0000535860 00000 n
+0000535926 00000 n
+0000535957 00000 n
+0000536246 00000 n
+0000536501 00000 n
+0000536567 00000 n
+0000536598 00000 n
+0000536888 00000 n
+0000539271 00000 n
+0000538842 00000 n
+0000538409 00000 n
+0000537978 00000 n
+0000537548 00000 n
+0000537603 00000 n
+0000537900 00000 n
+0000538033 00000 n
+0000538331 00000 n
+0000538464 00000 n
+0000538764 00000 n
+0000538897 00000 n
+0000539193 00000 n
+0000539326 00000 n
+0000539623 00000 n
+0000540342 00000 n
+trailer
<</Size 971/Root 1 0 R/Info 970 0 R/ID[<07511D2C75694DC89622C9F4B35B7624><8DE5252BE861447E9DAABAECF80D68A1>]>>
startxref
540533
%%EOF
\ No newline at end of file diff --git a/doc/logos/eps/icon_taler.eps b/doc/logos/eps/icon_taler.eps Binary files differnew file mode 100644 index 000000000..79ab648a6 --- /dev/null +++ b/doc/logos/eps/icon_taler.eps diff --git a/doc/logos/eps/logo_taler.eps b/doc/logos/eps/logo_taler.eps Binary files differnew file mode 100644 index 000000000..343128499 --- /dev/null +++ b/doc/logos/eps/logo_taler.eps diff --git a/doc/logos/fonts/OldNewspaperTypes.ttf b/doc/logos/fonts/OldNewspaperTypes.ttf Binary files differnew file mode 100755 index 000000000..7b9cf31b9 --- /dev/null +++ b/doc/logos/fonts/OldNewspaperTypes.ttf diff --git a/doc/logos/fonts/perpetue/Perpetua Bold Italic.ttf b/doc/logos/fonts/perpetue/Perpetua Bold Italic.ttf Binary files differnew file mode 100644 index 000000000..3882fe928 --- /dev/null +++ b/doc/logos/fonts/perpetue/Perpetua Bold Italic.ttf diff --git a/doc/logos/fonts/perpetue/Perpetua Bold.ttf b/doc/logos/fonts/perpetue/Perpetua Bold.ttf Binary files differnew file mode 100644 index 000000000..c73833dbb --- /dev/null +++ b/doc/logos/fonts/perpetue/Perpetua Bold.ttf diff --git a/doc/logos/fonts/perpetue/Perpetua Italic.ttf b/doc/logos/fonts/perpetue/Perpetua Italic.ttf Binary files differnew file mode 100644 index 000000000..e4f295ed1 --- /dev/null +++ b/doc/logos/fonts/perpetue/Perpetua Italic.ttf diff --git a/doc/logos/fonts/perpetue/Perpetua.ttf b/doc/logos/fonts/perpetue/Perpetua.ttf Binary files differnew file mode 100644 index 000000000..846b3dca6 --- /dev/null +++ b/doc/logos/fonts/perpetue/Perpetua.ttf diff --git a/doc/logos/fonts/smoth_bight/End User Licence Agreement.txt b/doc/logos/fonts/smoth_bight/End User Licence Agreement.txt new file mode 100755 index 000000000..04209197c --- /dev/null +++ b/doc/logos/fonts/smoth_bight/End User Licence Agreement.txt @@ -0,0 +1,133 @@ +End User License Agreement and Software Inclusion Agreement
+
+
+"Purchaser" and "User" may be used interchangeably in this agreement.
+
+
+The official release page is at kustren.deviantart.com/
+
+or
+
+
+
+ Copyright 2013 Kustren
+ Trademark 2013 Kustren licence
+ Commercial distribution, rendering and printing of the font and derived work is prohibited.
+
+
+
+
+
+Usage
+
+Smoth-Bight is freeware Font is free to use for personal and commercial purposes. No payment is necessary, and there is no limit to the amount of prints, pages, or other medium to be produced using them. However, you cannot offer the font for commercial sale, or offer for direct download. The inclusion othe font name and/or site URL in the credits or documentation when it is used is appreciated, but this is not mandatory.
+My font is for personal and commercial use, can be used for any type of work, while nature, neither will be able to be modified in any kind of way.
+This license is multipurpose, my font can be used on multiple computers at once.
+
+
+
+
+
+Payment
+
+Payment is not required for the use of kustren's freeware Font.
+
+
+
+Support
+
+If you experience problems with any Kustren's Freeware font (such as spacing issues or missing characters), please verify that you have the correct and current version of the fonts. In the case of Freeware font, downloading the font directly from the http://kustren.deviantart.com/ site will ensure that the font files have not been altered.
+
+
+
+
+Copyright (c) 2014 by Kustren. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/logos/fonts/smoth_bight/Licencia de la fuente - Version en Espa§ol.txt b/doc/logos/fonts/smoth_bight/Licencia de la fuente - Version en Espa§ol.txt new file mode 100755 index 000000000..39833a36a --- /dev/null +++ b/doc/logos/fonts/smoth_bight/Licencia de la fuente - Version en Espa§ol.txt @@ -0,0 +1,77 @@ +Contrato de licencia de usuario final e Inclusin del acuerdo de Software
+
+"Comprador " y "Usuario " se pueden utilizar indistintamente en el presente acuerdo .
+
+
+La pgina oficial de lanzamiento de la fuente es:
+http://kustren.deviantart.com/
+
+
+
+ Derechos de autor 2013 Kustren
+ Marcas 2013 licencia Kustren
+ Se prohbe la distribucin comercial, la representacin y la impresin de la fuente y el trabajo derivado .
+
+
+
+
+
+Uso
+
+La fuente de Kustren llamada Smoth-Bight es freware en su mayoria, de uso libre, pero solo para fines personales y/o comerciales, no hay limite en la cantidad de copias realizadas de la mis ma fuente en los pcs que usted utilice.
+Sin embargo no pueden ofrecer esta tipografia ni como suyas y/o comercializarla, tampoco estara permitida la descarga directa en otras paginas web, a menos que sea con consentimiento de su creador Kustren.
+Si usted usa mi fuente deme creditos por ello y/o coloque la pagina web mia, esto es de agradecer.
+
+licencia del derecho de uso
+
+Esta fuente es para uso personal y/o comercial, puede ser usadas para cualquier tipo de trabajo, pero no podra ser modificadas de ningun tipo de manera.
+Recuerden que si ustedes compran alguna tipografia no se convierten en propietariuo de la fuente, solo obtienen la licencia de uso, es decir la misma que estan leyendo.
+Esta licencia es de multiuso, mi fuente pueden ser usadas en multiples computadores a la vez.
+
+
+
+
+
+pago
+
+El pago no es necesaria para el uso de esta fuente de software gratuito creada por kustren.
+
+
+
+apoyo
+
+Si tienen problema con la fuente (como problemas de espaciado o caracteres que faltan ) , por favor, compruebe que tiene la versin correcta y actualizada de la fuente. En el caso de fuentes de dominio pblico , la descarga de la fuente directamente desde el sitio http://kustren.deviantart.com/ se asegurar de que los archivos de fuentes no han sido alterados.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/logos/fonts/smoth_bight/Smoth-Bight - Por Kustren.otf b/doc/logos/fonts/smoth_bight/Smoth-Bight - Por Kustren.otf Binary files differnew file mode 100755 index 000000000..33fd97934 --- /dev/null +++ b/doc/logos/fonts/smoth_bight/Smoth-Bight - Por Kustren.otf diff --git a/doc/logos/fonts/smoth_bight/Smoth-Bight Italic - Por Kustren.otf b/doc/logos/fonts/smoth_bight/Smoth-Bight Italic - Por Kustren.otf Binary files differnew file mode 100755 index 000000000..5fb25e646 --- /dev/null +++ b/doc/logos/fonts/smoth_bight/Smoth-Bight Italic - Por Kustren.otf diff --git a/doc/logos/ico/favicon1616.ico b/doc/logos/ico/favicon1616.ico Binary files differnew file mode 100644 index 000000000..141b93d3f --- /dev/null +++ b/doc/logos/ico/favicon1616.ico diff --git a/doc/logos/ico/favicon4848.ico b/doc/logos/ico/favicon4848.ico Binary files differnew file mode 100644 index 000000000..3c04b1266 --- /dev/null +++ b/doc/logos/ico/favicon4848.ico diff --git a/doc/logos/png/icon_taler.png b/doc/logos/png/icon_taler.png Binary files differnew file mode 100644 index 000000000..b1dd9a587 --- /dev/null +++ b/doc/logos/png/icon_taler.png diff --git a/doc/logos/png/logo_taler.png b/doc/logos/png/logo_taler.png Binary files differnew file mode 100644 index 000000000..bdffc3c9c --- /dev/null +++ b/doc/logos/png/logo_taler.png diff --git a/doc/paper/.latexmkrc b/doc/paper/.latexmkrc new file mode 100644 index 000000000..16bc358f0 --- /dev/null +++ b/doc/paper/.latexmkrc @@ -0,0 +1,15 @@ +add_cus_dep('glo', 'gls', 0, 'run_makeglossaries'); +add_cus_dep('acn', 'acr', 0, 'run_makeglossaries'); + +sub run_makeglossaries { + if ( $silent ) { + system "makeglossaries -q '$_[0]'"; + } + else { + system "makeglossaries '$_[0]'"; + }; +} + +push @generated_exts, 'glo', 'gls', 'glg'; +push @generated_exts, 'acn', 'acr', 'alg'; +$clean_ext .= ' %R.ist %R.xdy'; diff --git a/doc/paper/taler.bib b/doc/paper/taler.bib new file mode 100644 index 000000000..ce5b1cb11 --- /dev/null +++ b/doc/paper/taler.bib @@ -0,0 +1,94 @@ +@article{nakamoto2008bitcoin, + title={Bitcoin: A peer-to-peer electronic cash system}, + author={Nakamoto, Satoshi}, + year={2008} +} + +@Article{blum1981, + author = {Manuel Blum}, + title = {Coin Flipping by Telephone}, + journal = {CRYPTO}, + year = {1981}, + pages = {11-15}, +} + +@inproceedings{chaum1990untraceable, + title={Untraceable electronic cash}, + author={Chaum, David and Fiat, Amos and Naor, Moni}, + booktitle={Proceedings on Advances in cryptology}, + pages={319--327}, + year={1990}, + organization={Springer-Verlag New York, Inc.} +} + +@inproceedings{chaum1983blind, + title={Blind signatures for untraceable payments}, + author={Chaum, David}, + booktitle={Advances in cryptology}, + pages={199--203}, + year={1983}, + organization={Springer} +} + +@inproceedings{rivest2004peppercoin, + title={Peppercoin micropayments}, + author={Rivest, Ronald L}, + booktitle={Financial Cryptography}, + pages={2--8}, + year={2004}, + organization={Springer} +} + +@inproceedings{miers2013zerocoin, + title={Zerocoin: Anonymous distributed e-cash from bitcoin}, + author={Miers, Ian and Garman, Christina and Green, Matthew and Rubin, Aviel D}, + booktitle={Security and Privacy (SP), 2013 IEEE Symposium on}, + pages={397--411}, + year={2013}, + organization={IEEE} +} + +@inproceedings{selby2004analyzing, + title={Analyzing the Success and Failure of Recent e-Payment Schemes}, + author={Selby, Jack R}, + booktitle={Financial Cryptography}, + pages={1--1}, + year={2004}, + organization={Springer} +} + +@misc{brands1993efficient, + title={An efficient off-line electronic cash system based on the representation problem}, + author={Brands, Stefan A}, + year={1993}, + publisher={Centrum voor Wiskunde en Informatica} +} + +@article{dent2008extensions, + title={Extensions to Chaum's Blind Signature Scheme and OpenCoin Requirements}, + author={Dent, AW and Paterson, KG and Wild, PR}, + year={2008} +} + +@article{dent2008preliminary, + title={Preliminary Report on Chaum's Online E-Cash Architecture}, + author={Dent, AW and Paterson, KG and Wild, PR}, + journal={Royal Holloway, University of London}, + year={2008} +} + + + +@inproceedings{tor-design, + title = {Tor: The Second-Generation Onion Router}, + author = {Roger Dingledine and Nick Mathewson and Paul Syverson}, + booktitle = {Proceedings of the 13th USENIX Security Symposium}, + year = {2004}, + month = {August}, + www_important = {1}, + www_tags = {selected}, + www_html_url = {https://www.torproject.org/svn/trunk/doc/design-paper/tor-design.html}, + www_pdf_url = {https://www.torproject.org/svn/trunk/doc/design-paper/tor-design.pdf}, + www_section = {Anonymous communication}, +} + diff --git a/doc/paper/taler.tex b/doc/paper/taler.tex new file mode 100644 index 000000000..7a71d7636 --- /dev/null +++ b/doc/paper/taler.tex @@ -0,0 +1,995 @@ +\documentclass{llncs} +%\usepackage[margin=1in,a4paper]{geometry} +\usepackage[T1]{fontenc} +\usepackage{palatino} +\usepackage{xspace} +\usepackage{microtype} +\usepackage{tikz} +\usepackage{amsmath,amssymb} +\usepackage{enumitem} +\usetikzlibrary{shapes,arrows} +\usetikzlibrary{positioning} +\usetikzlibrary{calc} + + + +% Terminology: +% - SEPA-transfer -- avoid 'SEPA transaction' as we use +% 'transaction' already when we talk about taxable +% transfers of Taler coins and database 'transactions'. +% - wallet = coins at customer +% - reserve = currency entrusted to mint waiting for withdrawl +% - deposit = SEPA to mint +% - withdrawl = mint to customer +% - spending = customer to merchant +% - redeeming = merchant to mint (and then mint SEPA to merchant) +% - refreshing = customer-mint-customer +% - dirty coin = coin with exposed public key +% - fresh coin = coin that was refreshed or is new +% - coin signing key = mint's online key used to (blindly) sign coin +% - message signing key = mint's online key to sign mint messages +% - mint master key = mint's key used to sign other mint keys +% - owner = entity that knows coin private key +% - transaction = coin ownership transfer that should be taxed +% - sharing = coin copying that should not be taxed + + +\title{Taler: Taxable Anonymous Libre Electronic Reserves} + +\begin{document} +\mainmatter + +%\author{Florian Dold \and Sree Harsha Totakura \and Benedikt M\"uller \and Christian Grothoff} +%\institute{The GNUnet Project} + + +\maketitle + +\begin{abstract} +This paper introduces Taler, a Chaum-style digital currency using +blind signatures that enables anonymous payments while ensuring that +entities that receive payments are auditable and thus taxable. Taler +differs from Chaum's original proposal in that customers can never defraud anyone, +merchants can only fail to deliver the merchandise to the customer, +and mints can be fully audited. Consequently, enforcement of honest +behavior is better and more timely than with Chaum, and is at least as +strict as with legacy credit card payment systems that do not provide +for privacy. Furthermore, Taler allows fractional and incremental +payments, and even in this case is still able to guarantee +unlinkability of transactions via a new coin refreshing protocol. +Finally, Taler also supports microdonations using probabilistic +transactions. We argue that Taler provides a secure digital currency +for modern liberal societies as it is a flexible, libre and efficient +protocol and adequately balances the state's need for monetary control +with the citizen's needs for private economic activity. +\end{abstract} + +\section{Introduction} + +The design of payment systems shapes economies and societies. Strong, +developed nation states are evolving towards fully transparent payment +systems, such as the MasterCard and VisaCard credit card schemes and +computerized bank transactions such as SWIFT. Such systems enable +mass surveillance and thus extensive government control over the +economy, from taxation to intrusion into private lives. Bribery and +corruption are limited to elites that can afford to escape the +dragnet. The other extreme are economies of developing, weak nation +states where economic activity is based largely on coins, paper money +or even barter. Here, the state is often unable to effectively +monitor or tax economic activity, and this limits the ability of the +state to shape the society. As bribery is virtually impossible to +detect, it is widespread and not limited to social elites. +ZeroCoin~\cite{miers2013zerocoin} is an example for translating such +an economy into the digital realm. + +Taler is supposed to offer a middleground between an authoritarian +state in total control of the population and weak states with almost +anarchistic economies. Specifically, we believe that a liberal +democracy needs a payment system with the following properties: + +\begin{description} + \item[Customer Anonymity] It must be impossible for mints, merchants + and even a global active adversary, to trace the spending behavior + of a customer. + \item[Unlinkability] Merchants must not be able to tell if two + transactions were performed by the same customer. It must be + infeasible to link a set of transactions to the same (anonymous) + customer. %, even when taking aborted transactions into account. + \item[Taxability] In many current legal systems, it is the + responsibility of the merchant to deduct (sales) taxes from + purchases made by customers, or to pay (income) taxes for payments + received for work. + %Taxation is neccessary for the state to + %provide legitimate social functions, such as education. Thus, a payment + %system must facilitate sales, income and transaction taxes. + This specifically means that it must be able to audit merchants (or + generally anybody receiving money), and thus the receiver of + electronic cash must be easily identifiable. + %non-anonymous, as this would enable tax fraud. + \item[Verifiability] The payment system should try to minimize the + trust necessary between the participants. In particular, digital + signatures should be used extensively in order to be able to + resolve disputes between the involved parties. Nevertheless, + customers must never be able to defraud anyone, and merchants must + at best be able to defraud their customers by not delivering the + on the agreed contract. Neither merchants nor customers must ever + be able to commit fraud against the mint. Both customers and + merchants must receive cryptographic proofs of bad behavior in + case of protocol violations by the mint. Thus, only the mint will + have to be tightly audited and regulated. The design must make it + easy to audit the finances of the mint. + \item[Ease of Deployment] %The system should be easy to deploy for + % real-world applications. In order to lower the entry barrier and + % acceptance of the system, a gateway to the existing financial + % system should be provided, i.e. by integrating internet-banking + % protocols such as HBCI/FinTAN. + The digital currency should be + tied 1:1 to existing currencies (such as EUR or USD) to avoid + exposing users to unnecessary risks from currency fluctuations. + Moreover, the system must have a free software reference + implementation and an open protocol standard. +% The protocol should +% be able to run easily over HTTP(S). + \item[Low resource consumption] In order to minimize the operating + costs and environmental impact of the payment system, it must + avoid the reliance on expensive and ``wasteful'' computations + such as proof-of-work. + \item[Large Payments and Microdonations] The payment system needs to + handle large payments in a reliable manner. Furthermore, for + microdonations the system should allow sacrificing reliability to + achieve economic viability. +\end{description} + +Taler builds on ideas from Chaum~\cite{chaum1983blind}, who proposed a +digital currency system that would provide (some) customer anonymity +while disclosing the identity of the merchants. Chaum's digital cash +system had some limitations and ultimately failed to be widely +adopted. In our assessment, key reasons include: + +\begin{itemize} + \item The use of patents to protect the technology; a payment system + must be libre --- free software --- to have a chance for widespread + adoption. + \item The use of off-line payments and thus deferred detection of + double-spending, which could require the mint to attempt to recover + funds from customers via the legal system. This creates a + significant business risk for the mint, as the system is not + self-enforcing from the perspective of the mint. In 1983 off-line + payments might have been a necessary feature. However, today + requiring network connectivity is feasible and avoids the business + risks associated with deferred fraud detection. + \item % In addition to the risk of legal disputes with fradulent + % merchants and customers, + Chaum's published design does not clearly + limit the financial damage a mint might suffer from the + disclosure of its private online signing key. +% \item Chaum did not support fractional payments, and Brand's +% extensions for fractional payments broke unlinkability and thus +% limited anonymity. Chaum also did not support microdonations, +% leaving an opportunity for expanding payments into additional areas +% unexplored. +% \item Chaum's system was implemented at a time where the US market +% was still dominated by paper checks and the European market was +% fragmented into dozens of currencies. Today, SEPA provides a +% unified currency and currency transfer method for most of Europe, +% significantly lowering the barrier to entry into this domain for +% a larger market. +\end{itemize} + +This paper describes Taler, a simple and practical payment with the +above goals in mind. The basic idea is to use Chaum's model of +customer, merchant and mint (Figure~\ref{fig:cmm}) where the customer +withdraws digital currency from the mint with unlinkability provided +via blind signatures. In contrast to Chaum, Taler uses online +detection of double-spending, thus ensuring the merchant instantly +that a transaction is valid. Instead of using cryptographic methods +to enable fractional payments, the customer can simply include +the fraction of a coin's value that is to be paid to the merchant in +his message to the merchant. + + +\begin{figure}[h] +\centering +\begin{tikzpicture} + + +\tikzstyle{def} = [node distance= 7em and 10em, inner sep=1em, outer sep=.3em]; +\node (origin) at (0,0) {}; +\node (mint) [def,above=of origin,draw]{Mint}; +\node (customer) [def, draw, below left=of origin] {Customer}; +\node (merchant) [def, draw, below right=of origin] {Merchant}; + +\tikzstyle{C} = [color=black, line width=1pt] +\draw [<-, C] (customer) -- (mint) node [midway, above, sloped] (TextNode) {withdraw coins}; +\draw [<-, C] (mint) -- (merchant) node [midway, above, sloped] (TextNode) {deposit coins}; +\draw [<-, C] (merchant) -- (customer) node [midway, above, sloped] (TextNode) {spend coins}; +\end{tikzpicture} +\caption{Taler's system model for the payment system is based on Chaum~\cite{chaum1983blind}.} +\label{fig:cmm} +\end{figure} + +Online fraud detection can create problems if the network fails during +the initial steps of a transaction. For example, a law enforcement +agency might try to entrap a customer by offering illicit goods and +then aborting the transaction after learning the public key of the +coin. If the customer were to then later spend that coin on a +purchase with shipping, the law enforcement agency could link the two +transactions and might be able to use the shipping to deanonymize the +customer. Similarly, fractional payments also lead to the +possibility of customers wanting to legitimately use the same coin +twice. Taler addresses this problem by allowing customers to {\em + refresh} coins. Refreshing means that a customer is able to +exchange one coin for a fresh coin, with the old and the new coin +being unlinkable (except for the customer himself). Taler ensures +that the {\em entity} of the user owning the new coin is the same as the +entity of the user owning the old coin, thus making sure that the +refreshing protocol cannot be abused for money laundering or other +illicit transactions. + + +\section{Related Work} + +\subsection{Blockchain-based currencies} + +In recent years, a class of decentralized electronic payment systems, +based on collectively recorded and verified append-only public +ledgers, have gained immense popularity. The most well-known protocol +in this class is Bitcoin~\cite{nakamoto2008bitcoin}. An initial +concern with Bitcoin was the lack of anonymity, as all Bitcoin +transactions are recorded for eternity, which can enable +identification of users. In theory, this concern has been addressed +with the Zerocoin extension to the protocol~\cite{miers2013zerocoin}. + +While these protocols dispense with the need for a central, trusted +authority and provide anonymity, we argue there are some major, +irredeemable problems inherent in these systems: + +\begin{itemize} + \item Bitcoins are not (easily) taxable. The legality and legitimacy of + this aspect is questionable. The Zerocoin extension would only make + this worse. + \item Bitcoins can not be bound to any fiat currency, and are subject to + significant value fluctuations. While such fluctuations may be + acceptable for high-risk investments, they make Bitcoin unsuitable as + a medium of exchange. + \item The computational puzzles solved by Bitcoin nodes with the purpose + of securing the block chain + consume a considerable amount of computational resources and thus + energy. Thus, Bitcoin does not represent an environmentally responsible + design. + \item Anyone can easily start an alternative Bitcoin transaction chain + (a so-called AltCoin) and, if successful, reap the benefits of the low + cost to initially create coins via computation. As a result, dozens of + AltCoins have been created, often without any significant changes to the + technology. A large number of AltCoins creates additional overheads for + currency exchange and exascerbates the problems with currency fluctuations. +\end{itemize} + +\subsection{Chaum-style electronic cash} + +Chaum's original digital cash system~\cite{chaum1983blind} was +extended by Brands~\cite{brands1993efficient} with the ability to +perform fractional payments; however, the transactions performed with +the same coin then become linkable. +% +%Some argue that the focus on technically perfect but overwhelmingly +%complex protocols, as well as the the lack of usable, practical +%solutions lead to an abandonment of these ideas by +%practitioners~\cite{selby2004analyzing}. +% +To our knowledge, the only publicly available effort to implement +Chaum's idea is +Opencoin~\cite{dent2008extensions}. However, +Opencoin seems to be neither actively developed nor used, and it is +not clear to what degree the implementation is even complete. Only a +partial description of the Opencoin protocol is available to date. + +\subsection{Peppercoin} + +Peppercoin~\cite{rivest2004peppercoin} is a microdonation protocol. +The main idea of the protocol is to reduce transaction costs by +minimizing the number of transactions that are processed directly by +the mint. Instead of always paying, the customer ``gambles'' with the +merchant for each microdonation. Only if the merchant wins, the +microdonation is upgraded to a macropayment to be deposited at the +mint. Peppercoin does not provide customer-anonymity. The proposed +statistical method for mints detecting fraudulent cooperation between +customers and merchants at the expense of the mint not only creates +legal risks for the mint (who has to make a statistical argument), but +also would require the mint to learn about microdonations where the +merchant did not get upgraded to a macropayment. Thus, it is unclear +how much Peppercoin would actually do to reduce the computational +burden on the mint. + + +\section{Design} + +The payment system we propose is built on the blind signature +primitive proposed by Chaum, but extended with additional +constructions to provide unlinkability, online fraud detection and +taxability. + +As with Chaum, the Taler system comprises three principal types of +actors: The \emph{customer} is interested in receiving goods or +services from the \emph{merchant} in exchange for payment. When +making a transaction, both the customer and the merchant must agree on +the same \emph{mint}, which serves as an intermediary for the +financial transaction between the two. The mint is responsible for +allowing the customer to obtain the anonymous digital currency and for +enabling the merchant to convert the anonymous digital currency back +to some traditional currency. + +\subsection{Security model} + +Taler's security model assumes that cryptographic primitives are +secure and that each participant is under full control of his system. +The contact information of the mint is known to both customer and +merchant from the start. Furthermore, the merchant is known to the +customer and we assume that an anonymous, reliable bi-directional +communication channel can be established by the customer to both the +mint and the merchant. + +The mint is trusted to hold funds of its customers and to forward them +when receiving the respective deposit instructions from the merchants. +Customer and merchant can have some assurances about the mint's +liquidity and operation, as the mint has proven reserves, is subject +to the law, and can have its business is regularly audited (for +example, by the government or a trusted third party auditor). +Audits of the mint's accounts must reveal any possible fraud. +% +The merchant is trusted to deliver the service or goods to the +customer upon receiving payment. The customer can seek legal relief +to achieve this, as he must get cryptographic proofs of the contract +and that he paid his obligations. +% +Neither the merchant nor the customer may have any ability to {\em + effectively} defraud the mint or the state collecting taxes. Here, +``effectively'' means that the expected return for fraud is negative. +% +Note that customers do not need to be trusted in any way, and that in +particular it is never necessary for anyone to try to recover funds +from customers using legal means. + + +\subsection{Taxability and Entities} + +Electronic coins are trivially copied between machines. Thus, we must +clarify what kinds of operations can even be expected to be taxed. +After all, without instrusive measures to take away control of the +computing platform from its users, copying an electronic wallet from +one computer to another can hardly be prevented by a payment system. +Furthermore, it would also hardly be appropriate to tax the moving of +funds between two computers owned by the same individual. We thus +need to clarify which kinds of transfers we expect to tax. + +Taler is supposed to ensure that the state can tax {\em transactions}. +We define a transaction as the transfer of funds between {\em mutually + distrustful} entities. Two entities are assumed to be mutually +distrustful if they are unwilling to share control over assets. If a +private key is shared between two entities, then both entities have +equal access to the credentials represented by the private key. In a +payment system this means that either entity could spent the +associated funds. Assuming the payment system has effective +double-spending detection, this means that either entity has to +constantly fear that the funds might no longer be available to it. +Thus, ``transfering'' funds by sharing a private key implies that +receiving party must trust the sender. In Taler, making funds +available by sharing a private key and thus sharing control is {\bf + not} considered a {\em transaction} and thus {\bf not} recorded for +taxation. + +A {\em transaction} is a transfer where it is assured that one entity +gains control over funds while at the same time another entity looses +control over those funds. Taler ensures taxability only when some +entity acquires exclusive control over digital coins. For +transactions, the state can obtain information from the mint (or the +bank) that identifies the entity that received the digital coins as +well as the exact value of those coins. Taler also allows the mint +(and thus the state) to learn the value of digital coins withdrawn by +a customer --- but not how, where or when they were spent. Finally, +to enable audits, the current balance and profits of the mint are also +easily determined. + +\subsection{Anonymity} + +An anonymous communication channel (e.g. via Tor~\cite{tor-design}) is +used for all communication between the customer and the merchant. +Thus, the customer can remain anonymous; however, the system does reveal +that the customer is one of the patrons of the mint. Naturally, the +customer-merchant operation might leak other information about the +customer, such as a shipping address. Such purchase-specific +information leakage is outside of the scope of this work. + +The customer may use an anonymous communication channel for the +communication with the mint to avoid leaking IP address information; +however, the mint will anyway be able to determine the customer's +identity from the (SEPA) transfer that the customer initiates to +obtain anonymous digital cash. The scheme is anonymous +because the mint will be unable to link the known identity of the +customer that withdrew anonymous digital currency to the {\em + purchase} performed later at the merchant. +% All the mint will be +%able to confirm is that the customer is {\em one} of its patrons who +%previously obtained the anonymous digital currency --- and of course +%that the coin was not spent before. + +While the customer thus has anonymity for his purchase, the mint will +always learn the merchant's identity (which is necessary for +taxation), and thus the merchant has no reason to anonymize his +communication with the mint. +% Technically, the merchant could still +%use an anonymous communication channel to communicate with the mint. +%However, in order to receive the traditional currency the mint will +%require (SEPA) account details for the deposit. + +%As both the initial transaction between the customer and the mint as +%well as the transactions between the merchant and the mint do not have +%to be done anonymously, there might be a formal business contract +%between the customer and the mint and the merchant and the mint. Such +%a contract may provide customers and merchants some assurance that +%they will actually receive the traditional currency from the mint +%given cryptographic proof about the validity of the transaction(s). +%However, given the business overheads for establishing such contracts +%and the natural goal for the mint to establish a reputation and to +%minimize cost, it is more likely that the mint will advertise its +%external auditors and proven reserves and thereby try to convince +%customers and merchants to trust it without a formal contract. + + +\subsection{Coins} + +A \emph{coin} is a digital token which derives its financial value +from a signature on the coin's identifier by a mint. The mint is +expected to have multiple {\em coin signing key} pairs available for +signing, each representing a different coin denomination. + +The coin signing keys have an expiration date (typically measured in +years), and coins signed with a coin signing key must be spent (or +exchanged for new coins) before that expiration date. This allows the +mint to limit the amount of state it needs to keep to detect +double spending attempts. Furthermore, the mint is expected to use each coin +signing key only for a limited number of coins, for example by +limiting its use to sign coins to a week or a month. That way, if the +private coin signing key were to be compromised, the mint can detect +this once more coins are redeemed than the total that was signed into +existence using the respective coin signing key. In this case, the +mint can allow the original set of customers to exchange the coins +that were signed with the compromised private key, while refusing +further transactions from merchants if they involve those coins. As a +result, the financial damage of loosing a private signing key can be +limited to at most twice the amount originally signed with that key. +To ensure that the mint does not enable deanonymization of users by +signing each coin with a fresh coin signing key, the mint must +publicly announce the coin signing keys in advance. Those +announcements are expected to be signed with an off-line long-term +private {\em master signing key} of the mint and possibly the auditor. + +Before a customer can withdraw a coin from the mint, he has to pay the +mint the value of the coin, as well as processing fees. This is done +using other means of payments, such as SEPA transfers~\cite{sepa}. +The subject line of the transfer must contains {\em withdrawal + authorization key}, a public key for digital signatures generated by +the customer. When the mint receives a transfer with a public key in +the subject, it adds the funds to a {\em reserve} identified by the +withdrawl authorization key. By signing the withdrawl messages using +the withdrawl authorization key, the customer can prove to the mint +that he is authorized to withdraw anonymous digital coins from the +reserve. The mint will record the withdrawl messages with the reserve +record as proof that the anonymous digital coin was created for the +correct customer. + +After a coin is minted, the customer is the only entity that knows the +private key of the coin, making him the \emph{owner} of the coin. The +coin can be identified by the mint by its public key; however, due to +the use of blind signatures, the mint does not learn the public key +during the minting process. Knowledge of the private key of the coin +enables the owner to spent the coin. If the private key is shared +with others, they also become owners of the coin. + +\subsection{Coin spending} + +To spent a coin, the coin's owner needs to sign a {\em deposit + request} specifying the amount, the merchant's account information +and a {\em business transaction-specific hash} using the coin's +private key. A merchant can then transfer this permission of the +coin's owner to the mint to obtain the amount in traditional currency. +If the customer is cheating and the coin was already spent, the mint +provides cryptographic proof of the fraud to the merchant, who will +then refuse the transaction. +% The mint is typically expected +%to transfer the funds to the merchant using a SEPA transfer or similar +%methods appropriate to the domain of the traditional currency. + +%The mint needs to ensure that a coin can only be spent once. This is +%done by storing the public keys of all deposited coins (together with +%the deposit request and the owner's signature confirming the +%transaction). The mint's state can be limited as coins signed with +%expired coin sigining keys do not have to be retained. + +\paragraph{Partial spending.} + +To allow exact payments without requiring the customer to keep a large +amount of ``change'' in stock, the payment systems allows partial +spending of coins. Consequently, the mint the must not only store the +identifiers of spent coins, but also the fraction of the coin that has +been spent. + +%\paragraph{Online checks.} +% +%For secure transactions (non-microdonations), the merchant is expected +%to perform an online check to detect double-spending. In the simplest +%case, the merchant simply directly confirms the validity of the +%deposit permission signed by the coin's owner with the mint, and then +%proceeds with the contract. + +\paragraph{Incremental payments.} + +For services that include pay-as-you-go billing, customers can over +time sign deposit permissions for an increasing fraction of the value +of a coin to be paid to a particular merchant. As checking with the +mint for each increment might be expensive, the coin's owner can +instead sign a {\em lock permission}, which allows the merchant to get +an exclusive right to redeem deposit permissions for the coin for a +limited duration. The merchant uses the lock permission to determine +if the coin has already been spent and to ensure that it cannot be +spent by another merchant for the {\em duration} of the lock as +specified in the lock permission. If the coin has been spent or is +already locked, the mint provides the owner's deposit or locking +request and signature to prove the attempted fraud by the customer. +Otherwise, the mint locks the coin for the expected duration of the +transaction (and remembers the lock permission). The merchant and the +customer can then finalize the business transaction, possibly +exchanging a series of incremental payment permissions for services. +Finally, the merchant then redeems the coin at the mint before the +lock permission expires to ensure that no other merchant spends the +coin first. + + +\paragraph{Probabilistic spending.} + +Similar to Peppercoin, Taler supports probabilistic spending of coins to +support cost-effective transactions for small amounts. Here, an +ordinary transaction is performed based on the result of a biased coin +flip with a probability related to the desired transaction amount in +relation to the value of the coin. Unlike Peppercoin, in Taler either +the merchant wins and the customer looses the coin, or the merchant +looses and the customer keeps the coin. Thus, there is no opportunity +for the merchant and the customer to conspire against the mint. To +determine if the coin is to be transferred, merchant and customer +execute a secure coin flipping protocol~\cite{blum1981}. The commit +values are included in the business contract and are revealed after +the contract has been signed using the private key of the coin. If +the coin flip is decided in favor of the merchant, the merchant can +redeem the coin at the mint. + +One issue in this protocol is that the customer may use a worthless +coin by offering a coin that has already been spent. This kind of +fraud would only be detected if the customer actually lost the coin +flip, and at this point the merchant might not be able to recover from +the loss. A fradulent anonymous customer may run the protocol using +already spent coins until the coin flip is in his favor. As with +incremental spending, lock permissions could be used to ensure that +the customer cannot defraud the merchant by offering a coin that has +already been spent. However, as this means involving the mint even if +the merchant looses the coin flip, such a scheme is unsuitable for +microdonations as the transaction costs from involving the mint might +be disproportionate to the value of the transaction, and thus with +locking the probabilistic scheme has no advantage over simply using +fractional payments. + +Hence, Taler uses probabilistic transactions {\em without} the online +double-spending detection. This enables the customer to defraud the +merchant by paying with a coin that was already spent. However, as, +by definition, such microdonations are for tiny amounts, the incentive +for customers to pursue this kind of fraud is limited. + + +\subsection{Refreshing Coins} + +In the payment scenarios there are several cases where a customer will +reveal the public key of a coin to a merchant, but not ultimately sign +over the full value of the coin. If the customer then continues to +use the remainder of the value of the coin in other transactions, +merchants and the mint could link the various transactions as they all +share the same public key for the coin. + +Thus, the owner might want to exchange such a {\em dirty} coin for a +{\em fresh} coin to ensure unlinkability of future transactions with +the previous operation. Even if a coin is not dirty, the owner of a +coin may want to exchange a coin if the respective coin signing key is +about to expire. All of these operations are supported with the {\em + coin refreshing protocol}, which allows the owner of a coin to +exchange existing coins (or their remaining value) for fresh coins +with a new public-private key pairs. Refreshing does not use the +ordinary spending operation as the owner of a coin should not have to +pay taxes on this operation. Because of this, the refreshing protocol +must assure that owner stays the same. After all, the coin refreshing +protocol must not be usable for transactions, as transactions in Taler +must be taxable. + +Thus, one main goal of the refreshing protocol is that the mint must +not be able to link the fresh coin's public key to the public key of +the dirty coin. The second main goal is to enable the mint to ensure +that the owner of the dirty coin can determine the private key of the +fresh coin. This way, refreshing cannot be used to construct a +transaction --- the owner of the dirty coin remains in control of the +fresh coin. + +As with other operations, the refreshing protocol must also protect +the mint from double-spending; similarly, the customer has to have +cryptographic evidence if there is any misbehaviour by the mint. +Finally, the mint may choose to charge a transaction fee for +refreshing by reducing the value of the generated fresh coins +in relation to the value of the melted coins. +%Naturally, all such transaction fees should be clearly stated as part +%of the business contract offered by the mint to customers and +%merchants. + + +\section{Taler's Cryptographic Protocols} + +% In this section, we describe the protocols for Taler in detail. + +For the sake of brevity, we do not specifically state that the +recipient of a signed message always first checks that the signature +is valid. Also, whenever a signed message is transmitted, it is +assumed that the receiver is told the public key (or knows it from the +context) and that the signature contains additional identification as +to the purpose of the signature (such that it is not possible to +use a signature from one protocol step in a different context). + +When the mint signs messages (not coins), an {\em online message + signing key} of the mint is used. The mint's long-term offline key +is used to certify both the coin signing keys as well as the online +message signing key of the mint. The mint's long-term offline key is +assumed to be well-known to both customers and merchants, for example +because it is certified by the auditors. + +As we are dealing with financial transactions, we explicitly state +whenever entities need to safely commit data to persistent storage. +As long as those commitments persist, the protocol can be safely +resumed at any step. Commitments to disk are cummulative, that is an +additional commitment does not erase the previously committed +information. Keys and thus coins always have a well-known expiration +date; information committed to disk can be discarded after the +expiration date of the respective public key. Customers can also +discard information once the respective coins have been fully spent, +and merchants may discard information once payments from the mint have +been received (assuming records are also no longer needed for tax +authorities). The mint's bank transfers dealing in traditional +currency are expected to be recorded for tax authorities to ensure +taxability. + +\subsection{Withdrawal} + +To withdraw anonymous digital coins, the customer performs the +following interaction with the mint: + +\begin{enumerate} + \item The customer identifies a mint with an auditor-approved + coin signing public-private key pair $K := (K_s, K_p)$ + and randomly generates: + \begin{itemize} + \item withdrawal key $W := (W_s,W_p)$ with private key $W_s$ and public key $W_p$, + \item coin key $C := (C_s,C_p)$ with private key $C_s$ and public key $C_p$, + \item blinding factor $b$, + \end{itemize} + and commits $\langle W, C, b \rangle$ to disk. + \item The customer transfers an amount of money corresponding to (at least) $K_p$ to the mint, with $W_p$ in the subject line of the transaction. + \item The mint receives the transaction and credits the $W_p$ reserve with the respective amount in its database. + \item The customer sends $S_W(E_b(C_p))$ to the mint to request withdrawl of $C$; here, $E_b$ denotes Chaum-style blinding with blinding factor $b$. + \item The mint checks if the same withdrawl request was issued before; in this case, it sends $S_{K}(E_b(C_p))$ to the customer.\footnote{Here $S_K$ + denotes a Chaum-style blind signature with private key $K_s$.} + If this is a fresh withdrawl request, the mint performs the following transaction: + \begin{enumerate} + \item checks if the reserve $W_p$ has sufficient funds for a coin of value corresponding to $K_p$ + \item stores the withdrawl request $\langle S_W(E_b(C_p)), S_K(E_b(C_p)) \rangle$ in its database for future reference, + \item deducts the amount corresponding to $K_p$ from the reserve, + \item and sends $S_{K}(E_b(C_p))$ to the customer. + \end{enumerate} + If the guards for the transaction fail, the mint sends an descriptive error back to the customer, + with proof that it operated correctly (i.e. by showing the transaction history for the reserve). + \item The customer computes (and verifies) the unblind signature $S_K(C_p) = D_b(S_K(E_b(C_p)))$. + The customer writes $\langle S_K(C_p), C_s \rangle$ to disk (effectively adding the coin to the + local wallet) for future use. +\end{enumerate} + +\subsection{Exact, partial and incremental spending} + +A customer can spend coins at a merchant, under the condition that the +merchant trusts the mint that minted the coin. Merchants are +identified by their public key $M := (M_s, M_p)$, which must be known +to the customer apriori. + +The following steps describe the protocol between customer, merchant and mint +for a transaction involving a coin $C := (C_s, C_p)$ which is previously signed +by a mint's denomination key $K$, i.e. the customer posses +$\widetilde{C} := S_K(C_p)$: + +\begin{enumerate} +\item\label{offer} The merchant sends an \emph{offer:} $\langle S_M(m, f), + \vec{D} \rangle$ containing the price of the offer $f$, a transaction + ID $m$ and the list of mints $D_1, \ldots, D_n$ accepted by the merchant + where each $D_i$ is a mint's public key. +\item\label{lock} The customer must possess or acquire a coin minted by a mint that is + accepted by the merchant, i.e. $K$ should be publicly signed by some $D_i + \in \{D_1, D_2, \ldots, D_n\}$, and has a value $\geq f$. + + Customer then generates a \emph{lock-permission} $\mathcal{L} := + S_c(\widetilde{C}, t, m, f, M_p)$ where $t$ specifies the time until which the + lock is valid and sends $\langle \mathcal{L}, D_i\rangle$ to the merchant, + where $D_i$ is the mint which signed $K$. +\item The merchant asks the mint to apply the lock by sending $\langle + \mathcal{L} \rangle$ to the mint. +\item The mint validates $\widetilde{C}$ and detects double spending if there is + a lock-permission record $S_c(\widetilde{C}, t', m', f', M_p')$ where $(t', + m', f', M_p') \neq (t, m, f, M_p)$ or a \emph{deposit-permission} record for + $C$ and sends it to the merchant, who can then use it prove to the customer + and subsequently ask the customer to issue a new lock-permission. + + If double spending is not found, the mint commits $\langle \mathcal{L} \rangle$ to disk + and notifies the merchant that locking was successful. +\item\label{contract} The merchant creates a digitally signed contract + $\mathcal{A} := S_M(m, f, a, H(p, r))$ where $a$ is data relevant to the contract + indicating which services or goods the merchant will deliver to the customer, and $p$ is the + merchant's payment information (e.g. his IBAN number) and $r$ is an random nounce. + The merchant commits $\langle \mathcal{A} \rangle$ to disk and sends it to the customer. +\item The customer creates a + \emph{deposit-permission} $\mathcal{D} := S_c(\widetilde{C}, f, m, M_p, H(a), H(p, r))$, commits + $\langle \mathcal{A}, \mathcal{D} \rangle$ to disk and sends $\mathcal{D}$ to the merchant. +\item\label{invoice_paid} The merchant commits the received $\langle \mathcal{D} \rangle$ to disk. +\item The merchant gives $(\mathcal{D}, p, r)$ to the mint, revealing his + payment information. +\item The mint verifies $(\mathcal{D}, p, r)$ for its validity. A + \emph{deposit-permission} for a coin $C$ is valid if: + \begin{itemize} + \item $C$ is not refreshed already + \item there exists no other \emph{deposit-permission} on disk for \\ + $\mathcal{D'} := S_c(\widetilde{C}, f', m', M_p', H(a'), H(p', r'))$ for $C$ + such that \\ $(f', m',M_p', H(a')) \neq (f, m, M_p, H(a))$ + \item $H(p, r) := H(p', r')$ + \end{itemize} + If $C$ is valid and no other \emph{deposit-permission} for $C$ exists on disk, the + mint does the following: + \begin{enumerate} + \item if a \emph{lock-permission} exists for $C$, it is deleted from disk + \item\label{transfer} transfers an amount of $f$ to the merchant's bank account + given in $p$. The subject line of the transaction to $p$ must contain + $H(\mathcal{D})$. + \item $\langle \mathcal{D}, p, r \rangle$ is commited to disk. + \end{enumerate} + If the deposit record $\langle \mathcal{D}, p, r \rangle$ already exists, + the mint sends it to the merchant, but does not transfer money to $p$ again. +\end{enumerate} + +To facilitate incremental spending of a coin $C$ in a single transaction, the +merchant makes an offer in Step~\ref{offer} with a maximum amount $f_{max}$ he +is willing to charge in this transaction from the coin $C$. After obtaining the +lock on $C$ for $f_{max}$, the merchant makes a contract in Step~\ref{contract} +with an amount $f \leq f_{max}$. The protocol follows with the following steps +repeated after Step~\ref{invoice_paid} whenever the merchant wants to charge an +incremental amount up to $f_{max}$: + +\begin{enumerate} + \setcounter{enumi}{4} +\item The merchant generates a new contract $ \mathcal{A}' := S_M(m, f', a', H(p, + r)) $ after obtaining the deposit-permission for a previous contract. Here + $f'$ is the accumulated sum the merchant is charging the customer, of which + the merchant has received a deposit-permission for $f$ from the previous + contract \textit{i.e.}~$f <f' \leq f_{max}$. Similarly $a'$ is the new + contract data appended to older contract data $a$. + The merchant commits $\langle \mathcal{A}' \rangle$ to disk and sends it to the customer. +\item Customer commits $\langle \mathcal{A}' \rangle$ to disk, creates + $\mathcal{D}' := S_c(\widetilde{C}, f', m, M_p, H(a'), H(p, r))$, commits + $\langle \mathcal{D'} \rangle$ and sends it to the merchant. +\item The merchant commits the received $\langle \mathcal{D'} \rangle$ and + deletes the older $\mathcal{D}$ +\end{enumerate} + +%Figure~\ref{fig:spending_protocol_interactions} summarizes the interactions of the +%coin spending protocol. + +For transactions with multiple coins, the steps of the protocol are executed in +parallel for each coin. + +During the time a coin is locked, it may not be spent at a +different merchant. To make the storage costs of the mint more predictable, +only one lock per coin can be active at any time, even if the lock only covers a +fraction of the coin's denomination. The mint will delete the locks when they +expire. Thus the coins can be reused once their locks expire. However, doing +so may link the new transaction to older transaction. + +Similarly, if a transaction is aborted after Step 2, subsequent transactions +with the same coin can be linked to the coin, but not directly to the coin's +owner. The same applies to partially spent coins. To unlink subsequent +transactions from a coin, the customer has to execute the coin refreshing +protocol with the mint. + +%\begin{figure}[h] +%\centering +%\begin{tikzpicture} +% +%\tikzstyle{def} = [node distance= 1em, inner sep=.5em, outer sep=.3em]; +%\node (origin) at (0,0) {}; +%\node (offer) [def,below=of origin]{make offer (merchant $\rightarrow$ customer)}; +%\node (A) [def,below=of offer]{permit lock (customer $\rightarrow$ merchant)}; +%\node (B) [def,below=of A]{apply lock (merchant $\rightarrow$ mint)}; +%\node (C) [def,below=of B]{confirm (or refuse) lock (mint $\rightarrow$ merchant)}; +%\node (D) [def,below=of C]{sign contract (merchant $\rightarrow$ customer)}; +%\node (E) [def,below=of D]{permit deposit (customer $\rightarrow$ merchant)}; +%\node (F) [def,below=of E]{make deposit (merchant $\rightarrow$ mint)}; +%\node (G) [def,below=of F]{transfer confirmation (mint $\rightarrow$ merchant)}; +% +%\tikzstyle{C} = [color=black, line width=1pt] +%\draw [->,C](offer) -- (A); +%\draw [->,C](A) -- (B); +%\draw [->,C](B) -- (C); +%\draw [->,C](C) -- (D); +%\draw [->,C](D) -- (E); +%\draw [->,C](E) -- (F); +%\draw [->,C](F) -- (G); +% +%\draw [->,C, bend right, shorten <=2mm] (E.east) +% to[out=-135,in=-45,distance=3.8cm] node[left] {aggregate} (D.east); +%\end{tikzpicture} +%\caption{Interactions between a customer, merchant and mint in the coin spending +% protocol} +%\label{fig:spending_protocol_interactions} +%\end{figure} + + +\subsection{Probabilistic spending} + +The following steps are executed for microdonations with upgrade probability $p$: +\begin{enumerate} + \item The merchant sends an offer to the customer. + \item The customer sends a commitment $H(r_c)$ to a random + value $r_c \in [0,2^R)$, where $R$ is a system parameter. + \item The merchant sends random $r_m \in [0,2^R)$ to the customer. + \item The customer computes $p' := (|r_c - r_m|) / (2^R)$. + If $p' < p$, the customer sends a coin with deposit-permission to the merchant. + Otherwise, the customer sends $r_c$ to the merchant. + \item The merchant deposits the coin, or checks if $r_c$ is consistent + with $H(r_c)$. +\end{enumerate} + +\subsection{Refreshing} + +The following protocol is executed in order to refresh a coin $C'$ of denomination $K$ to +a fresh coin $\widetilde{C}$ with the same denomination. In the protocol, $\kappa \ge 3$ is a security parameter. + +\begin{enumerate} + \item For each $i = 1,\ldots,\kappa$, the customer + \begin{itemize} + \item randomly generates transfer key $T^{(i)} := \left(t^{(i)}_s,T^{(i)}_p\right)$ where $T^{(i)}_p := t^{(i)}_s \cdot G$, + \item randomly generates coin key pair $C^{(i)} := \left(c_s^{(i)}, C_p^{(i)}\right)$ where $C^{(i)}_p := c^{(i)}_s \cdot G$, + \item randomly generates blinding factors $b_i$, + \item computes $E_i := E_{K_i}\left(c_s^{(i)}, b_i\right)$ where $K_i := c'_s \cdot T_p^{(i)}$ (The encryption key $K_i$ is + computed by multiplying the private key $c'_s$ of the original coin with the point on the curve + that represents the public key of the transfer key $T^{(i)}$.), + \end{itemize} + and commits $\langle C', \vec{T}, \vec{C}, \vec{b} \rangle$ to disk. + \item The customer computes $B_i := E_{b_i}(C^{(i)}_p)$ and sends commitments + $S_{C'}(\vec{E}, \vec{B}, \vec{T}))$ for $i=1,\ldots,\kappa$ to the mint; + here $E_{b_i}$ denotes Chaum-style blinding with blinding factor $b_i$. + \item The mint generates a random $\gamma$ with $1 \le \gamma \le \kappa$ and + marks $C'_p$ as spent by committing + $\langle C', \gamma, S_{C'}(\vec{E}, \vec{B}, \vec{T}) \rangle$ to disk + \item The mint sends $S_K(C'_p, \gamma)$ to the customer.\footnote{Instead of $K$, it is also + possible to use any equivalent mint signing key known to the customer here, as $K$ merely + serves as proof to the customer that the mint selected this particular $\gamma$.} + \item The customer commits $\langle C', S_K(C'_p, \gamma) \rangle$ to disk. + \item The customer computes $\mathfrak{R} := \left(t_s^{(i)}, C_p^{(i)}, b_i\right)_{i \ne \gamma}$ + and sends $S_{C'}(\mathfrak{R})$ to the mint. + \item \label{step:refresh-ccheck} The mint checks whether $\mathfrak{R}$ is consistent with the commitments; + specifically, it computes for $i \not= \gamma$: + \begin{itemize} + \item $\overline{K}_i := t_s^{(i)} \cdot C_p'$, + \item $(\overline{c}_s^{(i)}, \overline{b}_i) := D_{\overline{K}_i}(E_i)$, + \item $\overline{C}^{(i)}_p := \overline{c}_s^{(i)} \cdot G$, + \item $\overline{B}_i := E_{b_i}(C_p^{(i)})$, + \item $\overline{T}_i := t_s^{(i)} G$, + \end{itemize} + and checks if $\overline{C}^{(i)}_p = C^{(i)}_p$ and $H(E_i, \overline{B}_i, \overline{T}^{(i)}_p) = H(E_i, B_i, T^{(i)}_p)$ + and $\overline{T}_i = T_i$. + + \item \label{step:refresh-done} If the commitments were consistent, the mint sends the blind signature + $\widetilde{C} := S_{K}(B_\gamma)$ to the customer. + Otherwise, the mint responds with an error and confiscates the value of $C'$, + committing $\langle C', \gamma, S_{C'}(\mathfrak{R}) \rangle$ to disk as proof for the attempted fraud. +\end{enumerate} + +%\subsection{N-to-M Refreshing} +% +%TODO: Explain, especially subtleties regarding session key / the spoofing attack that requires signature. + +\subsection{Linking} + +For a coin that was successfully refreshed, the mint responds to +a request $S_{C'}(\mathtt{link})$ with $(T^{(\gamma)}_p$, $E_{\gamma}, \widetilde{C})$. + +This allows the owner of the old coin to also obtain the private key +of the new coin, even if the refreshing protocol was illicitly +executed by another party who learned $C'_s$ from the old owner. + + +\section{Discussion} + +\subsection{Offline Payments} + +Chaum's original proposals for anonymous digital cash avoided the +locking and online spending steps detailed in this proposal by +providing a means to deanonymize customers involved in +double-spending. We believe that this is problematic as the mint or +the merchant will then still need out-of-band means to recover funds +from the customer, which may be impossible in practice. In contrast, +in our design only the mint may try to defraud the other participants +and disappear. While this is still a risk, this is likely manageable, +especially compared to recovering funds via the court system from +customers. + + +\subsection{Bona-fide microdonations} + +Evidently the customer can ``cheat'' by aborting the transaction in +Step 3 of the microdonation protocol if the outcome is unfavourable --- +and repeat until he wins. This is why Taler is suitable for +microdonations --- where the customer voluntarily contributes --- +and not for micropayments. + +Naturally, if the donations requested are small, the incentive to +cheat for minimal gain should be quite low. Payment software could +embrace this fact by providing an appeal to conscience in form of an +option labeled ``I am unethical and want to cheat'', which executes +the dishonest version of the payment protocol. + +If an organization detects that it cannot support itself with +microdonations, it can always choose to switch to the macropayment +system with slightly higher transaction costs to remain in business. + +\subsection{Merchant Tax Audits} + +For a tax audit on the merchant, the mint includes the business +transaction-specific hash in the transfer of the traditional +currency. A tax auditor can then request the merchant to reveal +(meaningful) details about the business transaction ($\mathcal{D}$, +$a$, $p$, $r$), including proof that applicable taxes were paid. + +If a merchant is not able to provide theses values, he can be punished +in relation to the amount transferred by the traditional currency +transfer. + + +\section{Future Work} + +%The legal status of the system needs to be investigated in the various +%legal systems of the world. However, given that the system enables +%taxation and is able to impose withdrawl limits and thus is not +%suitable for money laundering, we are optimistic that states will find +%the design desirable. + +We did not yet perform performance measurements for the various +operations. However, we are pretty sure that the computational and +bandwidth cost for transactions described in this paper is likely +small compared to other business costs for the mint. We expect costs +within the system to be dominated by the (replicated, transactional) +database. However, these expenses are again likely small in relation +to the business cost of currency transfers using traditional banking. +Here, mint operators should be able to reduce their expenses by +aggregating multiple transfers to the same merchant. + + +\section{Conclusion} + +We have presented an efficient electronic payment system that +simultaneously addresses the conflicting objectives created by the +citizen's need for privacy and the state's need for taxation. The +coin refreshing protocol makes the design flexible and enables a +variety of payment methods. The libre implementation and open +protocol may finally enable modern society to upgrade to proper +electronic wallets with efficient, secure and privacy-preserving +transactions. + +\bibliographystyle{alpha} +\bibliography{taler} +\end{document} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 000000000..90ea1a047 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,9 @@ +*.o +*.deps +*.libs +*.lo +*.la +*.log +*.trs +*/__pycache__ +test-*
\ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..485c4f9d7 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,2 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include +SUBDIRS = include util mint diff --git a/src/include/Makefile.am b/src/include/Makefile.am new file mode 100644 index 000000000..d10d6d70e --- /dev/null +++ b/src/include/Makefile.am @@ -0,0 +1,7 @@ +EXTRA_DIST = \ + platform.h \ + taler_blind.h \ + taler_signatures.h \ + taler_types.h \ + taler_util.h \ + taler_rsa.h diff --git a/src/include/platform.h b/src/include/platform.h new file mode 100644 index 000000000..4cba7abfd --- /dev/null +++ b/src/include/platform.h @@ -0,0 +1,56 @@ +/* + This file is part of TALER + (C) 2014 Chrisitan Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file include/platform.h + * @brief This file contains the includes and definitions which are used by the + * rest of the modules + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +/* Include our configuration header */ +#ifndef HAVE_USED_CONFIG_H +# define HAVE_USED_CONFIG_H +# ifdef HAVE_CONFIG_H +# include "taler_config.h" +# endif +#endif + + +#if (GNUNET_EXTRA_LOGGING >= 1) +#define VERBOSE(cmd) cmd +#else +#define VERBOSE(cmd) do { break; }while(0) +#endif + +/* Include the features available for GNU source */ +#define _GNU_SOURCE + +/* Include GNUnet's platform file */ +#include <gnunet/platform.h> + +/* Do not use shortcuts for gcrypt mpi */ +#define GCRYPT_NO_MPI_MACROS 1 + +/* Do not use deprecated functions from gcrypt */ +#define GCRYPT_NO_DEPRECATED 1 + +#endif /* PLATFORM_H_ */ + +/* end of platform.h */ diff --git a/src/include/taler_db_lib.h b/src/include/taler_db_lib.h new file mode 100644 index 000000000..41b46264e --- /dev/null +++ b/src/include/taler_db_lib.h @@ -0,0 +1,132 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + +/** + * @file include/taler_db_lib.h + * @brief helper functions for DB interactions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Florian Dold + */ + +#ifndef TALER_DB_LIB_H_ +#define TALER_DB_LIB_H_ + +#include <libpq-fe.h> +#include "taler_util.h" + +#define TALER_DB_QUERY_PARAM_END { NULL, 0, 0 } +#define TALER_DB_QUERY_PARAM_PTR(x) { (x), sizeof (*(x)), 1 } +#define TALER_DB_QUERY_PARAM_PTR_SIZED(x, s) { (x), (s), 1 } + + +#define TALER_DB_RESULT_SPEC_END { NULL, 0, NULL } +#define TALER_DB_RESULT_SPEC(name, dst) { (void *) (dst), sizeof (*(dst)), (name) } +#define TALER_DB_RESULT_SPEC_SIZED(name, dst, s) { (void *) (dst), (s), (name) } + + +/** + * Description of a DB query parameter. + */ +struct TALER_DB_QueryParam +{ + /** + * Data or NULL + */ + const void *data; + /** + * Size of 'data' + */ + size_t size; + /** + * Non-null if this is not the last parameter. + * This allows for null as sentinal value. + */ + int more; +}; + + +/** + * Description of a DB result cell. + */ +struct TALER_DB_ResultSpec +{ + /** + * Destination for the data. + */ + void *dst; + + /** + * Allowed size for the data. + */ + size_t dst_size; + + /** + * Field name of the desired result. + */ + char *fname; +}; + + +/** + * Execute a prepared statement. + */ +PGresult * +TALER_DB_exec_prepared (PGconn *db_conn, + const char *name, + const struct TALER_DB_QueryParam *params); + + +/** + * Extract results from a query result according to the given specification. + * If colums are NULL, the destination is not modified, and GNUNET_NO + * is returned. + * + * @return + * GNUNET_YES if all results could be extracted + * GNUNET_NO if at least one result was NULL + * GNUNET_SYSERR if a result was invalid (non-existing field) + */ +int +TALER_DB_extract_result (PGresult *result, struct TALER_DB_ResultSpec *rs, int row); + + +int +TALER_DB_field_isnull (PGresult *result, + int row, + const char *fname); + + +int +TALER_DB_extract_amount_nbo (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_AmountNBO *r_amount_nbo); + + +int +TALER_DB_extract_amount (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_Amount *r_amount); + +#endif /* TALER_DB_LIB_H_ */ + +/* end of include/taler_db_lib.h */ diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h new file mode 100644 index 000000000..b224c4b33 --- /dev/null +++ b/src/include/taler_json_lib.h @@ -0,0 +1,101 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file include/taler_json_lib.h + * @brief helper functions for JSON processing using libjansson + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#ifndef TALER_JSON_LIB_H_ +#define TALER_JSON_LIB_H_ + +#include <jansson.h> + + +/** + * Convert a TALER amount to a JSON + * object. + * + * @param amount the amount + * @return a json object describing the amount + */ +json_t * +TALER_JSON_from_amount (struct TALER_Amount amount); + + +/** + * Convert absolute timestamp to a json string. + * + * @param the time stamp + * @return a json string with the timestamp in @a stamp + */ +json_t * +TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp); + + + +/** + * Convert binary data to a JSON string + * with the base32crockford encoding. + * + * @param data binary data + * @param size size of @a data in bytes + * @return json string that encodes @a data + */ +json_t * +TALER_JSON_from_data (const void *data, size_t size); + + +/** + * Parse given JSON object to Amount + * + * @param json the json object representing Amount + * @param r_amount where the amount has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_amount (json_t *json, + struct TALER_Amount *r_amount); + +/** + * Parse given JSON object to absolute time. + * + * @param json the json object representing absolute time in seconds + * @param r_abs where the time has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_abs (json_t *json, + struct GNUNET_TIME_Absolute *r_abs); + +/** + * Parse given JSON object to data + * + * @param json the json object representing data + * @param out the pointer to hold the parsed data. + * @param out_size the size of r_data. + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_data (json_t *json, + void *out, + size_t out_size); + + +#endif /* TALER_JSON_LIB_H_ */ + +/* End of taler_json_lib.h */ diff --git a/src/include/taler_microhttpd_lib.h b/src/include/taler_microhttpd_lib.h new file mode 100644 index 000000000..da601401f --- /dev/null +++ b/src/include/taler_microhttpd_lib.h @@ -0,0 +1,119 @@ + + +#ifndef TALER_MICROHTTPD_LIB_H_ +#define TALER_MICROHTTPD_LIB_H_ + + +#include <microhttpd.h> +#include <jansson.h> + + +/** + * Constants for JSON navigation description. + */ +enum +{ + /** + * Access a field. + * Param: const char * + */ + JNAV_FIELD, + /** + * Access an array index. + * Param: int + */ + JNAV_INDEX, + /** + * Return base32crockford encoded data of + * constant size. + * Params: (void *, size_t) + */ + JNAV_RET_DATA, + /** + * Return base32crockford encoded data of + * variable size. + * Params: (void **, size_t *) + */ + JNAV_RET_DATA_VAR, + /** + * Return a json object, which must be + * of the given type (JSON_* type constants, + * or -1 for any type). + * Params: (int, json_t **) + */ + JNAV_RET_TYPED_JSON +}; + + + +/** + * Send JSON object as response. Decreases + * the reference count of the JSON object. + * + * @param connection the MHD connection + * @param json the json object + * @param status_code the http status code + * @return MHD result code (MHD_YES on success) + */ +int +send_response_json (struct MHD_Connection *connection, + json_t *json, + unsigned int status_code); + + +/** + * Send a JSON object via an MHD connection, + * specified with the JANSSON pack syntax (see json_pack). + * + * @param connection connection to send the JSON over + * @param http_code HTTP status for the response + * @param fmt format string for pack + * @param ... varargs + * @return MHD_YES on success or MHD_NO on error + */ +int +request_send_json_pack (struct MHD_Connection *connection, + unsigned int http_code, + const char *fmt, ...); + + +/** + * Process a POST request containing a JSON object. + * + * @param connection the MHD connection + * @param con_cs the closure (contains a 'struct Buffer *') + * @param upload_data the POST data + * @param upload_data_size the POST data size + * @param json the JSON object for a completed request + * + * @returns + * GNUNET_YES if json object was parsed + * GNUNET_NO is request incomplete or invalid + * GNUNET_SYSERR on internal error + */ +int +process_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json); + + +/** + * Navigate through a JSON tree. + * + * Sends an error response if navigation is impossible (i.e. + * the JSON object is invalid) + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param ... navigation specification (see JNAV_*) + * @return GNUNET_YES if navigation was successful + * GNUNET_NO if json is malformed, error response was generated + * GNUNET_SYSERR on internal error + */ +int +request_json_require_nav (struct MHD_Connection *connection, + const json_t *root, ...); + +#endif /* TALER_MICROHTTPD_LIB_H_ */ diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h new file mode 100644 index 000000000..ee3b30e39 --- /dev/null +++ b/src/include/taler_mint_service.h @@ -0,0 +1,303 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file include/taler_mint_service.h + * @brief C interface to the mint's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#ifndef _TALER_MINT_SERVICE_H +#define _TALER_MINT_SERVICE_H + +#include "taler_rsa.h" +#include "taler_util.h" +#include <jansson.h> + +/** + * Handle to this library context + */ +struct TALER_MINT_Context; + +/** + * Handle to the mint + */ +struct TALER_MINT_Handle; + +/** + * Mint's signature key + */ +struct TALER_MINT_SigningPublicKey +{ + /** + * The signing public key + */ + struct GNUNET_CRYPTO_EddsaPublicKey key; + + /** + * Validity start time + */ + struct GNUNET_TIME_Absolute valid_from; + + /** + * Validity expiration time + */ + struct GNUNET_TIME_Absolute valid_until; +}; + + +/** + * Mint's denomination key + */ +struct TALER_MINT_DenomPublicKey +{ + /** + * The public key + */ + struct TALER_RSA_PublicKeyBinaryEncoded key; + + /** + * Timestamp indicating when the denomination key becomes valid + */ + struct GNUNET_TIME_Absolute valid_from; + + /** + * Timestamp indicating when the denomination key can’t be used anymore to + * withdraw new coins. + */ + struct GNUNET_TIME_Absolute withdraw_valid_until; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_Absolute deposit_valid_until; + + /** + * The value of this denomination + */ + struct TALER_Amount value; + + /** + * The applicable fee for withdrawing a coin of this denomination + */ + struct TALER_Amount fee_withdraw; + + /** + * The applicable fee to spend a coin of this denomination + */ + struct TALER_Amount fee_deposit; + + /** + *The applicable fee to refresh a coin of this denomination + */ + struct TALER_Amount fee_refresh; +}; + + +/** + * Initialise a context. A context should be used for each thread and should + * not be shared among multiple threads. + * + * @return the context + */ +struct TALER_MINT_Context * +TALER_MINT_init (); + + +/** + * Cleanup library initialisation resources. This function should be called + * after using this library to cleanup the resources occupied during library's + * initialisation. + * + * @param ctx the library context + */ +void +TALER_MINT_cleanup (struct TALER_MINT_Context *ctx); + + +/** + * Initialise a connection to the mint. + * + * @param ctx the context + * @param hostname the hostname of the mint + * @param port the point where the mint's HTTP service is running. If port is + * given as 0, ports 80 or 443 are chosen depending on @a url. + * @param mint_key the public key of the mint. This is used to verify the + * responses of the mint. + * @return the mint handle; NULL upon error + */ +struct TALER_MINT_Handle * +TALER_MINT_connect (struct TALER_MINT_Context *ctx, + const char *hostname, + uint16_t port, + struct GNUNET_CRYPTO_EddsaPublicKey *mint_key); + +/** + * Disconnect from the mint + * + * @param mint the mint handle + */ +void +TALER_MINT_disconnect (struct TALER_MINT_Handle *mint); + + +/** + * A handle to get the keys of a mint + */ +struct TALER_MINT_KeysGetHandle; + +/** + * Functions of this type are called to signal completion of an asynchronous call. + * + * @param cls closure + * @param emsg if the asynchronous call could not be completed due to an error, + * this parameter contains a human readable error message + */ +typedef void (*TALER_MINT_ContinuationCallback) (void *cls, + const char *emsg); + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the mint. No TALER_MINT_*() functions should be called + * in this callback. + * + * @param cls closure passed to TALER_MINT_keys_get() + * @param sign_keys NULL-terminated array of pointers to the mint's signing + * keys. NULL if no signing keys are retrieved. + * @param denom_keys NULL-terminated array of pointers to the mint's + * denomination keys; will be NULL if no signing keys are retrieved. + */ +typedef void (*TALER_MINT_KeysGetCallback) (void *cls, + struct TALER_MINT_SigningPublicKey **sign_keys, + struct TALER_MINT_DenomPublicKey **denom_keys); + + +/** + * Get the signing and denomination key of the mint. + * + * @param mint handle to the mint + * @param cb the callback to call with the keys + * @param cls closure for the above callback + * @param cont_cb the callback to call after completing this asynchronous call + * @param cont_cls the closure for the continuation callback + * @return a handle to this asynchronous call; NULL upon eror + */ +struct TALER_MINT_KeysGetHandle * +TALER_MINT_keys_get (struct TALER_MINT_Handle *mint, + TALER_MINT_KeysGetCallback cb, void *cls, + TALER_MINT_ContinuationCallback cont_cb, void *cont_cls); + +/** + * Cancel the asynchronous call initiated by TALER_MINT_keys_get(). This should + * not be called if either of the @a TALER_MINT_KeysGetCallback or @a + * TALER_MINT_ContinuationCallback passed to TALER_MINT_keys_get() have been + * called. + * + * @param get the handle for retrieving the keys + */ +void +TALER_MINT_keys_get_cancel (struct TALER_MINT_KeysGetHandle *get); + + +/** + * A Deposit Handle + */ +struct TALER_MINT_DepositHandle; + + +/** + * Callbacks of this type are used to serve the result of submitting a deposit + * permission object to a mint + * + * @param cls closure + * @param status 1 for successful deposit, 2 for retry, 0 for failure + * @param obj the received JSON object; can be NULL if it cannot be constructed + * from the reply + * @param emsg in case of unsuccessful deposit, this contains a human readable + * explanation. + */ +typedef void (*TALER_MINT_DepositResultCallback) (void *cls, + int status, + json_t *obj, + char *emsg); + +/** + * Submit a deposit permission to the mint and get the mint's response + * + * @param mint the mint handle + * @param cb the callback to call when a reply for this request is available + * @param cls closure for the above callback + * @param deposit_obj the deposit permission received from the customer along + * with the wireformat JSON object + * @return a handle for this request; NULL if the JSON object could not be + * parsed or is of incorrect format or any other error. In this case, + * the callback is not called. + */ +struct TALER_MINT_DepositHandle * +TALER_MINT_deposit_submit_json (struct TALER_MINT_Handle *mint, + TALER_MINT_DepositResultCallback cb, + void *cls, + json_t *deposit_obj); + + +#if 0 +/** + * Submit a deposit permission to the mint and get the mint's response. + * + * @param mint the mint handle + * @param cb the callback to call when a reply for this request is available + * @param cls closure for the above callback + * @param coin the public key of the coin + * @param denom_key denomination key of the mint which is used to blind-sign the + * coin + * @param ubsig the mint's unblinded signature + * @param transaction_id transaction identifier + * @param amount the amount to deposit + * @param merchant_pub the public key of the merchant + * @param h_contract hash of the contract + * @param h_wire hash of the wire format used + * @param csig signature of the coin over the transaction_id, amount, + * merchant_pub, h_contract and, h_wire + * @param wire_obj the wireformat object corresponding to h_wire + * @return a handle for this request + */ +struct TALER_MINT_DepositHandle * +TALER_MINT_deposit_submit_json_ (struct TALER_MINT_Handle *mint, + TALER_MINT_DepositResultCallback *cb, + void *cls, + struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, + struct TALER_BLIND_SigningPublicKey *denom_pub, + struct TALER_BLIND_Signature *ubsig, + uint64_t transaction_id, + struct TALER_Amount *amount, + struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub, + struct GNUNET_HashCode *h_contract, + struct GNUNET_HashCode *h_wire, + struct GNUNET_CRYPTO_EddsaSignature *csig, + json_t *wire_obj); +#endif + + +/** + * Cancel a deposit permission request. This function cannot be used on a + * request handle if a response is already served for it. + * + * @param the deposit permission request handle + */ +void +TALER_MINT_deposit_submit_cancel (struct TALER_MINT_DepositHandle *deposit); + +#endif /* _TALER_MINT_SERVICE_H */ diff --git a/src/include/taler_rsa.h b/src/include/taler_rsa.h new file mode 100644 index 000000000..1ed530013 --- /dev/null +++ b/src/include/taler_rsa.h @@ -0,0 +1,357 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file include/taler_rsa.h + * @brief RSA key management utilities. Some code is taken from gnunet-0.9.5a + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * + * Authors of the gnunet code: + * Christian Grothoff + * Krista Bennett + * Gerd Knorr <kraxel@bytesex.org> + * Ioana Patrascu + * Tzvetan Horozov + */ + +#ifndef TALER_RSA_H +#define TALER_RSA_H + +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_crypto_lib.h> + +/** + * Length of an RSA KEY (n,e,len), 2048 bit (=256 octests) key n, 2 byte e + */ +#define TALER_RSA_KEY_LENGTH 258 + +/** + * @brief Length of RSA encrypted data (2048 bit) + * + * We currently do not handle encryption of data + * that can not be done in a single call to the + * RSA methods (read: large chunks of data). + * We should never need that, as we can use + * the GNUNET_CRYPTO_hash for larger pieces of data for signing, + * and for encryption, we only need to encode sessionkeys! + */ +#define TALER_RSA_DATA_ENCODING_LENGTH 256 + +/** + * The private information of an RSA key pair. + */ +struct TALER_RSA_PrivateKey; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * GNUnet mandates a certain format for the encoding + * of private RSA key information that is provided + * by the RSA implementations. This format is used + * to serialize a private RSA key (typically when + * writing it to disk). + */ +struct TALER_RSA_PrivateKeyBinaryEncoded +{ + /** + * Total size of the structure, in bytes, in big-endian! + */ + uint16_t len GNUNET_PACKED; + uint16_t sizen GNUNET_PACKED; /* in big-endian! */ + uint16_t sizee GNUNET_PACKED; /* in big-endian! */ + uint16_t sized GNUNET_PACKED; /* in big-endian! */ + uint16_t sizep GNUNET_PACKED; /* in big-endian! */ + uint16_t sizeq GNUNET_PACKED; /* in big-endian! */ + uint16_t sizedmp1 GNUNET_PACKED; /* in big-endian! */ + uint16_t sizedmq1 GNUNET_PACKED; /* in big-endian! */ + /* followed by the actual values */ +}; +GNUNET_NETWORK_STRUCT_END + + +/** + * @brief an RSA signature + */ +struct TALER_RSA_Signature +{ + unsigned char sig[TALER_RSA_DATA_ENCODING_LENGTH]; +}; + +GNUNET_NETWORK_STRUCT_BEGIN +/** + * @brief header of what an RSA signature signs + * this must be followed by "size - 8" bytes of + * the actual signed data + */ +struct TALER_RSA_SignaturePurpose +{ + /** + * How many bytes does this signature sign? + * (including this purpose header); in network + * byte order (!). + */ + uint32_t size GNUNET_PACKED; + + /** + * What does this signature vouch for? This + * must contain a GNUNET_SIGNATURE_PURPOSE_XXX + * constant (from gnunet_signatures.h). In + * network byte order! + */ + uint32_t purpose GNUNET_PACKED; + +}; + + +struct TALER_RSA_BlindedSignaturePurpose +{ + unsigned char data[TALER_RSA_DATA_ENCODING_LENGTH]; +}; + + +/** + * @brief A public key. + */ +struct TALER_RSA_PublicKeyBinaryEncoded +{ + /** + * In big-endian, must be GNUNET_CRYPTO_RSA_KEY_LENGTH+4 + */ + uint16_t len GNUNET_PACKED; + + /** + * Size of n in key; in big-endian! + */ + uint16_t sizen GNUNET_PACKED; + + /** + * The key itself, contains n followed by e. + */ + unsigned char key[TALER_RSA_KEY_LENGTH]; + + /** + * Padding (must be 0) + */ + uint16_t padding GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_END + +/** + * Create a new private key. Caller must free return value. + * + * @return fresh private key + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_key_create (); + + +/** + * Free memory occupied by the private key. + * + * @param key pointer to the memory to free + */ +void +TALER_RSA_key_free (struct TALER_RSA_PrivateKey *key); + + +/** + * Encode the private key in a format suitable for + * storing it into a file. + * @return encoding of the private key + */ +struct TALER_RSA_PrivateKeyBinaryEncoded * +TALER_RSA_encode_key (const struct TALER_RSA_PrivateKey *hostkey); + + +/** + * Extract the public key of the given private key. + * + * @param priv the private key + * @param pub where to write the public key + */ +void +TALER_RSA_key_get_public (const struct TALER_RSA_PrivateKey *priv, + struct TALER_RSA_PublicKeyBinaryEncoded *pub); + + +/** + * Decode the private key from the data-format back + * to the "normal", internal format. + * + * @param buf the buffer where the private key data is stored + * @param len the length of the data in 'buffer' + * @return NULL on error + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_decode_key (const char *buf, uint16_t len); + + +/** + * Convert a public key to a string. + * + * @param pub key to convert + * @return string representing 'pub' + */ +char * +TALER_RSA_public_key_to_string (const struct TALER_RSA_PublicKeyBinaryEncoded *pub); + + +/** + * Convert a string representing a public key to a public key. + * + * @param enc encoded public key + * @param enclen number of bytes in enc (without 0-terminator) + * @param pub where to store the public key + * @return GNUNET_OK on success + */ +int +TALER_RSA_public_key_from_string (const char *enc, + size_t enclen, + struct TALER_RSA_PublicKeyBinaryEncoded *pub); + + +/** + * Sign a given block.h + * + * @param key private key to use for the signing + * @param msg the message + * @param size the size of the message + * @param sig where to write the signature + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +int +TALER_RSA_sign (const struct TALER_RSA_PrivateKey *key, + const void *msg, + size_t size, + struct TALER_RSA_Signature *sig); + + +/** + * Verify signature with the given hash. + * + * @param hash the hash code to verify against the signature + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_hash_verify (const struct GNUNET_HashCode *hash, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey); + + +/** + * Verify signature on the given message + * + * @param msg the message + * @param size the size of the message + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_verify (const void *msg, size_t size, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey); + +/** + * Key used to blind a message + */ +struct TALER_RSA_BlindingKey; + +/** + * Create a blinding key + * + * @return the newly created blinding key + */ +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_create (); + + +/** + * Destroy a blinding key + * + * @param bkey the blinding key to destroy + */ +void +TALER_RSA_blinding_key_destroy (struct TALER_RSA_BlindingKey *bkey); + + +/** + * Binary encoding for TALER_RSA_BlindingKey + */ +struct TALER_RSA_BlindingKeyBinaryEncoded +{ + unsigned char data[TALER_RSA_DATA_ENCODING_LENGTH]; +}; + + +/** + * Encode a blinding key + * + * @param bkey the blinding key to encode + * @param bkey_enc where to store the encoded binary key + * @return #GNUNET_OK upon successful encoding; #GNUNET_SYSERR upon failure + */ +int +TALER_RSA_blinding_key_encode (struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc); + + +/** + * Decode a blinding key from its encoded form + * + * @param bkey_enc the encoded blinding key + * @return the decoded blinding key; NULL upon error + */ +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_decode (struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc); + + +/** + * Blinds the given message with the given blinding key + * + * @param msg the message + * @param size the size of the message + * @param bkey the blinding key + * @param pkey the public key of the signer + * @return the blinding signature purpose; NULL upon any error + */ +struct TALER_RSA_BlindedSignaturePurpose * +TALER_RSA_message_blind (const void *msg, size_t size, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey); + + +/** + * Unblind a signature made on blinding signature purpose. The signature + * purpose should have been generated with TALER_RSA_message_blind() function. + * + * @param sig the signature made on the blinded signature purpose + * @param bkey the blinding key used to blind the signature purpose + * @param pkey the public key of the signer + * @return GNUNET_SYSERR upon error; GNUNET_OK upon success. + */ +int +TALER_RSA_unblind (struct TALER_RSA_Signature *sig, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey); + +#endif /* TALER_RSA_H */ + +/* end of include/taler_rsa.h */ diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h new file mode 100644 index 000000000..8c142f61f --- /dev/null +++ b/src/include/taler_signatures.h @@ -0,0 +1,106 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-keyup.c + * @brief Update the mint's keys for coins and signatures, + * using the mint's offline master key. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#ifndef TALER_SIGNATURES_H +#define TALER_SIGNATURES_H + +/** + * Purpose for signing public keys signed + * by the mint master key. + */ +#define TALER_SIGNATURE_MASTER_SIGNKEY 1 + +/** + * Purpose for denomination keys signed + * by the mint master key. + */ +#define TALER_SIGNATURE_MASTER_DENOM 2 + +/** + * Purpose for the state of a reserve, + * signed by the mint's signing key. + */ +#define TALER_SIGNATURE_RESERVE_STATUS 3 + +/** + * Signature where the reserve key + * confirms a withdraw request. + */ +#define TALER_SIGNATURE_WITHDRAW 4 + +/** + * Signature where the refresh session confirms + * the list of melted coins and requested denominations. + */ +#define TALER_SIGNATURE_REFRESH_MELT 5 + +/** + * Signature where the refresh session confirms + * the commits. + */ +#define TALER_SIGNATURE_REFRESH_COMMIT 6 + +/** + * Signature where the mint (current signing key) + * confirms the list of blind session keys. + */ +#define TALER_SIGNATURE_REFRESH_MELT_RESPONSE 7 + +/** + * Signature where the mint (current signing key) + * confirms the no-reveal index for cut-and-choose. + */ +#define TALER_SIGNATURE_REFRESH_COMMIT_RESPONSE 8 + +/** + * Signature where coins confirm that they want + * to be melted into a certain session. + */ +#define TALER_SIGNATURE_REFRESH_MELT_CONFIRM 9 + +/***********************/ +/* Merchant signatures */ +/***********************/ + +/** + * Signature where the merchant confirms a contract + */ +#define TALER_SIGNATURE_MERCHANT_CONTRACT 101 + +/*********************/ +/* Wallet signatures */ +/*********************/ + +/** + * Signature made by the wallet of a user to confirm a deposit permission + */ +#define TALER_SIGNATURE_DEPOSIT 201 + +/** + * Signature made by the wallet of a user to confirm a incremental deposit permission + */ +#define TALER_SIGNATURE_INCREMENTAL_DEPOSIT 202 + +#endif + diff --git a/src/include/taler_types.h b/src/include/taler_types.h new file mode 100644 index 000000000..c6c2c0209 --- /dev/null +++ b/src/include/taler_types.h @@ -0,0 +1,120 @@ +/** + * @file include/types.h + * @brief This files defines the various data and message types in TALER. + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Florian Dold + */ + +#ifndef TYPES_H_ +#define TYPES_H_ + +#include "taler_rsa.h" + + +/** + * Public information about a coin. + */ +struct TALER_CoinPublicInfo +{ + /** + * The coin's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + + /* + * The public key signifying the coin's denomination. + */ + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + + /** + * Signature over coin_pub by denom_pub. + */ + struct TALER_RSA_Signature denom_sig; +}; + + +/** + * Request to withdraw coins from a reserve. + */ +struct TALER_WithdrawRequest +{ + /** + * Signature over the rest of the message + * by the withdraw public key. + */ + struct GNUNET_CRYPTO_EddsaSignature sig; + + /** + * Purpose must be TALER_SIGNATURE_WITHDRAW. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Reserve public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + + /** + * Denomination public key for the coin that is withdrawn. + */ + struct TALER_RSA_PublicKeyBinaryEncoded denomination_pub; + + /** + * Purpose containing coin's blinded public key. + */ + struct TALER_RSA_BlindedSignaturePurpose coin_envelope; +}; + + + +/** + * Data type for messages + */ +struct TALER_MessageHeader +{ + /** + * The type of the message in Network-byte order (NBO) + */ + uint16_t type; + + /** + * The size of the message in NBO + */ + uint16_t size; +}; + +/*****************/ +/* Message types */ +/*****************/ + +/** + * The message type of a blind signature + */ +#define TALER_MSG_TYPE_BLINDED_SIGNATURE 1 + +/** + * The message type of a blinded message + */ +#define TALER_MSG_TYPE_BLINDED_MESSAGE 2 + +/** + * The message type of an unblinded signature + * @FIXME: Not currently used + */ +#define TALER_MSG_TYPE_UNBLINDED_SIGNATURE 3 + +/** + * The type of a blinding residue message + * @FIXME: Not currently used + */ +#define TALER_MSG_TYPE_BLINDING_RESIDUE 4 + +/** + * The type of a message containing the blinding factor + */ +#define TALER_MSG_TYPE_BLINDING_FACTOR 5 + + +#endif /* TYPES_H_ */ + +/* end of include/types.h */ diff --git a/src/include/taler_util.h b/src/include/taler_util.h new file mode 100644 index 000000000..a8a7c2013 --- /dev/null +++ b/src/include/taler_util.h @@ -0,0 +1,255 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file include/taler_util.h + * @brief Interface for common utility functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#ifndef UTIL_H_ +#define UTIL_H_ + +#include <gnunet/gnunet_util_lib.h> +#include <gcrypt.h> + +/* Define logging functions */ +#define LOG_DEBUG(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) + +#define LOG_WARNING(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__) + +#define LOG_ERROR(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__) + + +/** + * Tests a given as assertion and if failed prints it as a warning with the + * given reason + * + * @param EXP the expression to test as assertion + * @param reason string to print as warning + */ +#define TALER_assert_as(EXP, reason) \ + do { \ + if (EXP) break; \ + LOG_ERROR("%s at %s:%d\n", reason, __FILE__, __LINE__); \ + abort(); \ + } while(0) + + + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' with the message given + * by gcry_strerror(rc). + */ +#define LOG_GCRY_ERROR(cmd, rc) do { LOG_ERROR("`%s' failed at %s:%d with error: %s\n", cmd, __FILE__, __LINE__, gcry_strerror(rc)); } while(0) + + +#define TALER_gcry_ok(cmd) \ + do {int rc; rc = cmd; if (!rc) break; LOG_ERROR("A Gcrypt call failed at %s:%d with error: %s\n", __FILE__, __LINE__, gcry_strerror(rc)); abort(); } while (0) + + +#define TALER_CURRENCY_LEN 4 + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct TALER_AmountNBO +{ + uint32_t value; + uint32_t fraction; + char currency[TALER_CURRENCY_LEN]; +}; + +GNUNET_NETWORK_STRUCT_END + +struct TALER_HashContext +{ + gcry_md_hd_t hd; +}; + + + +/** + * Representation of monetary value in a given currency. + */ +struct TALER_Amount +{ + /** + * Value (numerator of fraction) + */ + uint32_t value; + /** + * Fraction (denominator of fraction) + */ + uint32_t fraction; + /** + * Currency string, left adjusted and padded with zeros. + */ + char currency[4]; +}; + + +/** + * Initialize Gcrypt library. + */ +void +TALER_gcrypt_init(); + + +/** + * Generate a ECC private key. + * + * @return the s-expression representing the generated ECC private key; NULL + * upon error + */ +gcry_sexp_t +TALER_genkey (); + + +/** + * Parse denomination description, in the format "T : V : F". + * + * @param str denomination description + * @param denom denomination to write the result to + * @return GNUNET_OK if the string is a valid denomination specification, + * GNUNET_SYSERR if it is invalid. + */ +int +TALER_string_to_amount (const char *str, struct TALER_Amount *denom); + + +/** + * FIXME + */ +struct TALER_AmountNBO +TALER_amount_hton (struct TALER_Amount d); + + +/** + * FIXME + */ +struct TALER_Amount +TALER_amount_ntoh (struct TALER_AmountNBO dn); + +/** + * Compare the value/fraction of two amounts. Does not compare the currency, + * i.e. comparing amounts with the same value and fraction but different + * currency would return 0. + * + * @param a1 first amount + * @param a2 second amount + * @return result of the comparison + */ +int +TALER_amount_cmp (struct TALER_Amount a1, struct TALER_Amount a2); + + +/** + * Perform saturating subtraction of amounts. + * + * @param a1 amount to subtract from + * @param a2 amount to subtract + * @return (a1-a2) or 0 if a2>=a1 + */ +struct TALER_Amount +TALER_amount_subtract (struct TALER_Amount a1, struct TALER_Amount a2); + + +/** + * Perform saturating addition of amounts + * + * @param a1 first amount to add + * @param a2 second amount to add + * @return sum of a1 and a2 + */ +struct TALER_Amount +TALER_amount_add (struct TALER_Amount a1, struct TALER_Amount a2); + + +/** + * Normalize the given amount. + * + * @param amout amount to normalize + * @return normalized amount + */ +struct TALER_Amount +TALER_amount_normalize (struct TALER_Amount amount); + + +/** + * Convert amount to string. + * + * @param amount amount to convert to string + * @return freshly allocated string representation + */ +char * +TALER_amount_to_string (struct TALER_Amount amount); + + +/** + * Return the base32crockford encoding of the given buffer. + * + * The returned string will be freshly allocated, and must be free'd + * with GNUNET_free. + * + * @param buffer with data + * @param size size of the buffer + * @return freshly allocated, null-terminated string + */ +char * +TALER_data_to_string_alloc (const void *buf, size_t size); + + +/** + * Get encoded binary data from a configuration. + * + * @return GNUNET_OK on success + * GNUNET_NO is the value does not exist + * GNUNET_SYSERR on encoding error + */ +int +TALER_configuration_get_data (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, const char *option, + void *buf, size_t buf_size); + + + + +int +TALER_refresh_decrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result); + +int +TALER_refresh_encrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result); + + + +void +TALER_hash_context_start (struct TALER_HashContext *hc); + + +void +TALER_hash_context_read (struct TALER_HashContext *hc, void *buf, size_t size); + + +void +TALER_hash_context_finish (struct TALER_HashContext *hc, + struct GNUNET_HashCode *r_hash); + +#endif diff --git a/src/mint/.gitignore b/src/mint/.gitignore new file mode 100644 index 000000000..a2e71d5da --- /dev/null +++ b/src/mint/.gitignore @@ -0,0 +1,6 @@ +taler-mint-dbinit +taler-mint-keycheck +taler-mint-keyup +taler-mint-pursemod +taler-mint-reservemod +taler-mint-httpd
\ No newline at end of file diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am new file mode 100644 index 000000000..2ae153485 --- /dev/null +++ b/src/mint/Makefile.am @@ -0,0 +1,131 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS) + +lib_LTLIBRARIES = \ + libtalermint.la \ + libtalermintapi.la + +libtalermint_la_SOURCES = \ + mint_common.c \ + mint_db.c + +libtalermint_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -lpq + +libtalermint_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + +libtalermintapi_la_SOURCES = \ + mint_api.c + +libtalermintapi_la_LIBADD = \ + -lgnunetutil \ + -ljansson \ + -lcurl + +libtalermintapi_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + + +bin_PROGRAMS = \ + taler-mint-keyup \ + taler-mint-keycheck \ + taler-mint-reservemod \ + taler-mint-httpd \ + taler-mint-dbinit + +taler_mint_keyup_SOURCES = \ + taler-mint-keyup.c + +taler_mint_keyup_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lgnunetutil +taler_mint_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS) + + +taler_mint_keycheck_SOURCES = \ + taler-mint-keycheck.c + +taler_mint_keycheck_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lgnunetutil \ + -lpq +taler_mint_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +taler_mint_reservemod_SOURCES = \ + taler-mint-reservemod.c +taler_mint_reservemod_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lgnunetutil +taler_mint_reservemod_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) + +taler_mint_httpd_SOURCES = \ + taler-mint-httpd.c \ + taler-mint-httpd_mhd.c \ + taler-mint-httpd_keys.c \ + taler-mint-httpd_deposit.c \ + taler-mint-httpd_withdraw.c \ + taler-mint-httpd_refresh.c +taler_mint_httpd_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lmicrohttpd \ + -ljansson \ + -lgnunetutil \ + -lpthread +taler_mint_httpd_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) + + +taler_mint_dbinit_SOURCES = \ + taler-mint-dbinit.c +taler_mint_dbinit_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lgnunetutil +taler_mint_dbinit_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +check_PROGRAMS = \ + test-mint-api \ + test-mint-deposits \ + test-mint-common + +test_mint_api_SOURCES = test_mint_api.c +test_mint_api_LDADD = \ + libtalermintapi.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -ljansson + +test_mint_deposits_SOURCES = \ + test_mint_deposits.c +test_mint_deposits_LDADD = \ + libtalermint.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -lpq + +test_mint_common_SOURCES = \ + test_mint_common.c +test_mint_common_LDADD = \ + libtalermint.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + -lgnunetutil diff --git a/src/mint/mint.h b/src/mint/mint.h new file mode 100644 index 000000000..5adce03c6 --- /dev/null +++ b/src/mint/mint.h @@ -0,0 +1,198 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler_mint.h + * @brief Common functionality for the mint + * @author Florian Dold + * @author Benedikt Mueller + */ + +#ifndef _MINT_H +#define _MINT_H + +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_common.h> +#include <libpq-fe.h> +#include "taler_util.h" +#include "taler_rsa.h" + +#define DIR_SIGNKEYS "signkeys" +#define DIR_DENOMKEYS "denomkeys" + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * FIXME + */ +struct TALER_MINT_SignKeyIssue +{ + struct GNUNET_CRYPTO_EddsaPrivateKey signkey_priv; + struct GNUNET_CRYPTO_EddsaSignature signature; + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_CRYPTO_EddsaPublicKey master_pub; + struct GNUNET_TIME_AbsoluteNBO start; + struct GNUNET_TIME_AbsoluteNBO expire; + struct GNUNET_CRYPTO_EddsaPublicKey signkey_pub; +}; + +struct TALER_MINT_DenomKeyIssue +{ + /** + * The private key of the denomination. Will be NULL if the private key is + * not available. + */ + struct TALER_RSA_PrivateKey *denom_priv; + struct GNUNET_CRYPTO_EddsaSignature signature; + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_CRYPTO_EddsaPublicKey master; + struct GNUNET_TIME_AbsoluteNBO start; + struct GNUNET_TIME_AbsoluteNBO expire_withdraw; + struct GNUNET_TIME_AbsoluteNBO expire_spend; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_AmountNBO value; + struct TALER_AmountNBO fee_withdraw; + struct TALER_AmountNBO fee_deposit; + struct TALER_AmountNBO fee_refresh; +}; + +struct RefreshMeltSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_HashCode melt_hash; +}; + +struct RefreshCommitSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_HashCode commit_hash; +}; + +struct RefreshCommitResponseSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + uint16_t noreveal_index; +}; + +struct RefreshMeltResponseSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_HashCode melt_response_hash; +}; + + +struct RefreshMeltConfirmSignRequestBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; +}; + + +GNUNET_NETWORK_STRUCT_END + + + +/** + * Iterator for sign keys. + * + * @param cls closure + * @param ski the sign key issue + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +typedef int (*TALER_MINT_SignkeyIterator)(void *cls, + const struct TALER_MINT_SignKeyIssue *ski); + +/** + * Iterator for denomination keys. + * + * @param cls closure + * @param dki the denomination key issue + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +typedef int (*TALER_MINT_DenomkeyIterator)(void *cls, + const char *alias, + const struct TALER_MINT_DenomKeyIssue *dki); + + + +/** + * FIXME + */ +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, void *cls); + + +/** + * FIXME + */ +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, void *cls); + + +/** + * Exports a denomination key to the given file + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_MINT_DenomKeyIssue *dki); + + +/** + * Import a denomination key from the given file + * + * @param filename the file to import the key from + * @param dki pointer to return the imported denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_MINT_DenomKeyIssue *dki); + + +/** + * Load the configuration for the mint in the given + * directory. + * + * @param mint_base_dir the mint's base directory + * @return the mint configuratin, or NULL on error + */ +struct GNUNET_CONFIGURATION_Handle * +TALER_MINT_config_load (const char *mint_base_dir); + + +int +TALER_TALER_DB_extract_amount (PGresult *result, unsigned int row, + int indices[3], struct TALER_Amount *denom); + +int +TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row, + int indices[3], struct TALER_AmountNBO *denom_nbo); + +#endif /* _MINT_H */ + diff --git a/src/mint/mint_api.c b/src/mint/mint_api.c new file mode 100644 index 000000000..b8d42b274 --- /dev/null +++ b/src/mint/mint_api.c @@ -0,0 +1,1121 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/mint_api.c + * @brief Implementation of the client interface to mint's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_mint_service.h" +#include "taler_signatures.h" +#include "mint.h" + +#define CURL_STRERROR(TYPE, FUNCTION, CODE) \ + GNUNET_log (TYPE, "cURL function `%s' has failed at `%s:%d' with error: %s", \ + FUNCTION, __FILE__, __LINE__, curl_easy_strerror (CODE)); + + + +/** + * Print JSON parsing related error information + */ +#define JSON_WARN(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)", \ + __FILE__, __LINE__, error.text, error.source) + +/** + * Failsafe flag + */ +static int fail; + +/** + * Context + */ +struct TALER_MINT_Context +{ + /** + * CURL multi handle + */ + CURLM *multi; + + /** + * CURL share handle + */ + CURLSH *share; + + /** + * Perform task handle + */ + struct GNUNET_SCHEDULER_Task *perform_task; +}; + +/** + * Type of requests we currently have + */ +enum RequestType +{ + /** + * No request + */ + REQUEST_TYPE_NONE, + + /** + * Current request is to receive mint's keys + */ + REQUEST_TYPE_KEYSGET, + + /** + * Current request is to submit a deposit permission and get its status + */ + REQUEST_TYPE_DEPOSIT +}; + + +/** + * Handle to the mint + */ +struct TALER_MINT_Handle +{ + /** + * The context of this handle + */ + struct TALER_MINT_Context *ctx; + + /** + * The hostname of the mint + */ + char *hostname; + + /** + * The CURL handle + */ + CURL *curl; + + /** + * Error buffer for CURL + */ + char emsg[CURL_ERROR_SIZE]; + + /** + * Download buffer + */ + void *buf; + + /** + * The currently active request + */ + union { + /** + * Used to denote no request if set to NULL + */ + void *none; + + /** + * Denom keys get request if REQUEST_TYPE_KEYSGET + */ + struct TALER_MINT_KeysGetHandle *keys_get; + + /** + * Deposit request if REQUEST_TYPE_DEPOSIT + */ + struct TALER_MINT_DepositHandle *deposit; + } req; + + /** + * The size of the download buffer + */ + size_t buf_size; + + /** + * Active request type + */ + enum RequestType req_type; + + /** + * The service port of the mint + */ + uint16_t port; + + /** + * Are we connected to the mint? + */ + uint8_t connected; + +}; + + +/** + * A handle to get the keys of a mint + */ +struct TALER_MINT_KeysGetHandle +{ + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this handle + */ + char *url; + + TALER_MINT_KeysGetCallback cb; + void *cls; + + TALER_MINT_ContinuationCallback cont_cb; + void *cont_cls; +}; + + +/** + * A handle to submit a deposit permission and get its status + */ +struct TALER_MINT_DepositHandle +{ + /** + *The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this handle + */ + char *url; + + TALER_MINT_DepositResultCallback cb; + void *cls; + + char *json_enc; + + struct curl_slist *headers; + +}; + + +/** + * Parses the timestamp encoded as ASCII string as UNIX timstamp. + * + * @param abs successfully parsed timestamp will be returned thru this parameter + * @param tstamp_enc the ASCII encoding of the timestamp + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +parse_timestamp (struct GNUNET_TIME_Absolute *abs, const char *tstamp_enc) +{ + unsigned long tstamp; + + if (1 != sscanf (tstamp_enc, "%lu", &tstamp)) + return GNUNET_SYSERR; + *abs = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get_zero_ (), + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, tstamp)); + return GNUNET_OK; +} + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + + +static int +parse_json_signkey (struct TALER_MINT_SigningPublicKey **_sign_key, + json_t *sign_key_obj, + struct GNUNET_CRYPTO_EddsaPublicKey *master_key) +{ + json_t *valid_from_obj; + json_t *valid_until_obj; + json_t *key_obj; + json_t *sig_obj; + const char *valid_from_enc; + const char *valid_until_enc; + const char *key_enc; + const char *sig_enc; + struct TALER_MINT_SigningPublicKey *sign_key; + struct TALER_MINT_SignKeyIssue sign_key_issue; + struct GNUNET_CRYPTO_EddsaSignature sig; + struct GNUNET_TIME_Absolute valid_from; + struct GNUNET_TIME_Absolute valid_until; + + EXITIF (JSON_OBJECT != json_typeof (sign_key_obj)); + EXITIF (NULL == (valid_from_obj = json_object_get (sign_key_obj, + "stamp_start"))); + EXITIF (NULL == (valid_until_obj = json_object_get (sign_key_obj, + "stamp_expire"))); + EXITIF (NULL == (key_obj = json_object_get (sign_key_obj, "key"))); + EXITIF (NULL == (sig_obj = json_object_get (sign_key_obj, "master_sig"))); + EXITIF (NULL == (valid_from_enc = json_string_value (valid_from_obj))); + EXITIF (NULL == (valid_until_enc = json_string_value (valid_until_obj))); + EXITIF (NULL == (key_enc = json_string_value (key_obj))); + EXITIF (NULL == (sig_enc = json_string_value (sig_obj))); + EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, + valid_from_enc)); + EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_until, + valid_until_enc)); + EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */ + EXITIF (103 != strlen (sig_enc)); /* strlen(base32(char[64])) = 103 */ + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103, + &sig, sizeof (sig))); + (void) memset (&sign_key_issue, 0, sizeof (sign_key_issue)); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_public_key_from_string (key_enc, + 52, + &sign_key_issue.signkey_pub)); + sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY); + sign_key_issue.purpose.size = + htonl (sizeof (sign_key_issue) + - offsetof (struct TALER_MINT_SignKeyIssue, purpose)); + sign_key_issue.master_pub = *master_key; + sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); + sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until); + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY, + &sign_key_issue.purpose, + &sig, + master_key)); + sign_key = GNUNET_new (struct TALER_MINT_SigningPublicKey); + sign_key->valid_from = valid_from; + sign_key->valid_until = valid_until; + sign_key->key = sign_key_issue.signkey_pub; + *_sign_key = sign_key; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + + +static int +parse_json_amount (json_t *amount_obj, struct TALER_Amount *amt) +{ + json_t *obj; + const char *currency_str; + int value; + int fraction; + + EXITIF (NULL == (obj = json_object_get (amount_obj, "currency"))); + EXITIF (NULL == (currency_str = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (amount_obj, "value"))); + EXITIF (JSON_INTEGER != json_typeof (obj)); + EXITIF (0 > (value = json_integer_value (obj))); + EXITIF (NULL == (obj = json_object_get (amount_obj, "fraction"))); + EXITIF (JSON_INTEGER != json_typeof (obj)); + EXITIF (0 > (fraction = json_integer_value (obj))); + (void) memset (amt->currency, 0, sizeof (amt->currency)); + (void) strncpy (amt->currency, currency_str, sizeof (amt->currency) - 1); + amt->value = (uint32_t) value; + amt->fraction = (uint32_t) fraction; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + +static int +parse_json_denomkey (struct TALER_MINT_DenomPublicKey **_denom_key, + json_t *denom_key_obj, + struct GNUNET_CRYPTO_EddsaPublicKey *master_key) +{ + json_t *obj; + const char *sig_enc; + const char *deposit_valid_until_enc; + const char *withdraw_valid_until_enc; + const char *valid_from_enc; + const char *key_enc; + struct TALER_MINT_DenomPublicKey *denom_key; + struct GNUNET_TIME_Absolute valid_from; + struct GNUNET_TIME_Absolute withdraw_valid_until; + struct GNUNET_TIME_Absolute deposit_valid_until; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct TALER_MINT_DenomKeyIssue denom_key_issue; + struct GNUNET_CRYPTO_EddsaSignature sig; + + EXITIF (JSON_OBJECT != json_typeof (denom_key_obj)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "master_sig"))); + EXITIF (NULL == (sig_enc = json_string_value (obj))); + EXITIF (103 != strlen (sig_enc)); + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103, + &sig, sizeof (sig))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_deposit"))); + EXITIF (NULL == (deposit_valid_until_enc = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_withdraw"))); + EXITIF (NULL == (withdraw_valid_until_enc = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_start"))); + EXITIF (NULL == (valid_from_enc = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "denom_pub"))); + EXITIF (NULL == (key_enc = json_string_value (obj))); + EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */ + EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, valid_from_enc)); + EXITIF (GNUNET_SYSERR == parse_timestamp (&withdraw_valid_until, + withdraw_valid_until_enc)); + EXITIF (GNUNET_SYSERR == parse_timestamp (&deposit_valid_until, + deposit_valid_until_enc)); + + (void) memset (&denom_key_issue, 0, sizeof (denom_key_issue)); + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (key_enc, 52, + &denom_key_issue.denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "value"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &value)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_withdraw"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_withdraw)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_deposit"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_deposit)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_refresh"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_refresh)); + denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); + denom_key_issue.purpose.size = htonl + (sizeof (struct TALER_MINT_DenomKeyIssue) - + offsetof (struct TALER_MINT_DenomKeyIssue, purpose)); + denom_key_issue.master = *master_key; + denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); + denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until); + denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until); + denom_key_issue.value = TALER_amount_hton (value); + denom_key_issue.fee_withdraw = TALER_amount_hton (fee_withdraw); + denom_key_issue.fee_deposit = TALER_amount_hton (fee_deposit); + denom_key_issue.fee_refresh = TALER_amount_hton (fee_refresh); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM, + &denom_key_issue.purpose, + &sig, + master_key)); + denom_key = GNUNET_new (struct TALER_MINT_DenomPublicKey); + denom_key->key = denom_key_issue.denom_pub; + denom_key->valid_from = valid_from; + denom_key->withdraw_valid_until = withdraw_valid_until; + denom_key->deposit_valid_until = deposit_valid_until; + denom_key->value = value; + denom_key->fee_withdraw = fee_withdraw; + denom_key->fee_deposit = fee_deposit; + denom_key->fee_refresh = fee_refresh; + *_denom_key = denom_key; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + +static int +parse_response_keys_get (const char *in, size_t size, + struct TALER_MINT_SigningPublicKey ***_sign_keys, + unsigned int *_n_sign_keys, + struct TALER_MINT_DenomPublicKey ***_denom_keys, + unsigned int *_n_denom_keys) +{ + json_t *resp_obj; + struct TALER_MINT_DenomPublicKey **denom_keys; + struct GNUNET_CRYPTO_EddsaPublicKey master_key; + struct GNUNET_TIME_Absolute list_issue_date; + struct TALER_MINT_SigningPublicKey **sign_keys; + unsigned int n_denom_keys; + unsigned int n_sign_keys; + json_error_t error; + unsigned int index; + int OK; + + denom_keys = NULL; + n_denom_keys = 0; + sign_keys = NULL; + n_sign_keys = 0; + OK = 0; + resp_obj = json_loadb (in, size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, + &error); + if (NULL == resp_obj) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse received data as JSON object\n"); + return GNUNET_SYSERR; + } + + EXITIF (JSON_OBJECT != json_typeof (resp_obj)); + { + /* parse the master public key */ + json_t *master_key_obj; + const char *master_key_enc; + + EXITIF (NULL == (master_key_obj = json_object_get (resp_obj, "master_pub"))); + EXITIF (NULL == (master_key_enc = json_string_value (master_key_obj))); + EXITIF (52 != strlen (master_key_enc)); /* strlen(base32(char[32])) = 52 */ + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (master_key_enc, + 52, + &master_key)); + } + { + /* parse the issue date of the response */ + json_t *list_issue_date_obj; + const char *tstamp_enc; + + EXITIF (NULL == (list_issue_date_obj = + json_object_get(resp_obj, "list_issue_date"))); + EXITIF (NULL == (tstamp_enc = json_string_value (list_issue_date_obj))); + EXITIF (GNUNET_SYSERR == parse_timestamp (&list_issue_date, tstamp_enc)); + } + { + /* parse the signing keys */ + json_t *sign_keys_array; + json_t *sign_key_obj; + + EXITIF (NULL == (sign_keys_array = + json_object_get (resp_obj, "signkeys"))); + EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); + EXITIF (0 == (n_sign_keys = json_array_size (sign_keys_array))); + sign_keys = GNUNET_malloc (sizeof (struct TALER_MINT_SigningPublicKey *) + * (n_sign_keys + 1)); + index = 0; + json_array_foreach (sign_keys_array, index, sign_key_obj) { + EXITIF (GNUNET_SYSERR == parse_json_signkey (&sign_keys[index], + sign_key_obj, + &master_key)); + } + } + { + /* parse the denomination keys */ + json_t *denom_keys_array; + json_t *denom_key_obj; + + EXITIF (NULL == (denom_keys_array = json_object_get (resp_obj, "denoms"))); + EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); + EXITIF (0 == (n_denom_keys = json_array_size (denom_keys_array))); + denom_keys = GNUNET_malloc (sizeof (struct TALER_MINT_DenomPublicKey *) + * (n_denom_keys + 1)); + index = 0; + json_array_foreach (denom_keys_array, index, denom_key_obj) { + EXITIF (GNUNET_SYSERR == parse_json_denomkey (&denom_keys[index], + denom_key_obj, + &master_key)); + } + } + OK = 1; + + EXITIF_exit: + json_decref (resp_obj); + if (!OK) + { + if (NULL != sign_keys) + { + for (index=0; NULL != sign_keys[index]; index++) + GNUNET_free_non_null (sign_keys[index]); + GNUNET_free (sign_keys); + } + if (NULL != denom_keys) + { + for (index=0; NULL != denom_keys[index]; index++) + GNUNET_free_non_null (denom_keys[index]); + GNUNET_free (denom_keys); + } + return GNUNET_SYSERR; + } + + *_sign_keys = sign_keys; + *_n_sign_keys = n_sign_keys; + *_denom_keys = denom_keys; + *_n_denom_keys = n_denom_keys; + return GNUNET_OK; +} + + +int +parse_deposit_response (void *buf, size_t size, int *r_status, json_t **r_obj) +{ + json_t *obj; + const char *status_str; + json_error_t error; + + status_str = NULL; + obj = NULL; + obj = json_loadb (buf, size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); + if (NULL == obj) + { + JSON_WARN (error); + return GNUNET_SYSERR; + } + EXITIF (-1 == json_unpack (obj, "{s:s}", "status", &status_str)); + LOG_DEBUG ("Received deposit response: %s from mint\n", status_str); + if (0 == strcmp ("DEPOSIT_OK", status_str)) + *r_status = 1; + else if (0 == strcmp ("DEPOSIT_QUEUED", status_str)) + *r_status = 2; + else + *r_status = 0; + *r_obj = obj; + + return GNUNET_OK; + EXITIF_exit: + json_decref (obj); + return GNUNET_SYSERR; +} + +#undef EXITIF + +static void +mint_connect (struct TALER_MINT_Handle *mint) +{ + struct TALER_MINT_Context *ctx = mint->ctx; + + GNUNET_assert (0 == mint->connected); + GNUNET_assert (CURLM_OK == curl_multi_add_handle (ctx->multi, mint->curl)); + mint->connected = GNUNET_YES; +} + +static void +mint_disconnect (struct TALER_MINT_Handle *mint) +{ + struct TALER_MINT_Context *ctx = mint->ctx; + + GNUNET_assert (GNUNET_YES == mint->connected); + GNUNET_break (CURLM_OK == curl_multi_remove_handle (ctx->multi, + mint->curl)); + mint->connected = GNUNET_NO; + GNUNET_free_non_null (mint->buf); + mint->buf = NULL; + mint->buf_size = 0; + mint->req_type = REQUEST_TYPE_NONE; + mint->req.none = NULL; +} + +static void +cleanup_keys_get (struct TALER_MINT_KeysGetHandle *gh) +{ + GNUNET_free (gh->url); + GNUNET_free (gh); +} + +static void +cleanup_deposit (struct TALER_MINT_DepositHandle *dh) +{ + curl_slist_free_all (dh->headers); + GNUNET_free_non_null (dh->json_enc); + GNUNET_free (dh->url); + GNUNET_free (dh); +} + +static void +request_failed (struct TALER_MINT_Handle *mint, long resp_code) +{ + switch (mint->req_type) + { + case REQUEST_TYPE_NONE: + GNUNET_assert (0); + break; + case REQUEST_TYPE_KEYSGET: + { + struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get; + TALER_MINT_ContinuationCallback cont_cb; + void *cont_cls; + GNUNET_assert (NULL != gh); + cont_cb = gh->cont_cb; + cont_cls = gh->cont_cls; + cleanup_keys_get (gh); + mint_disconnect (mint); + cont_cb (cont_cls, mint->emsg); + } + break; + case REQUEST_TYPE_DEPOSIT: + { + struct TALER_MINT_DepositHandle *dh = mint->req.deposit; + TALER_MINT_DepositResultCallback cb = dh->cb; + void *cls = dh->cls; + GNUNET_assert (NULL != dh); + cleanup_deposit (dh); + mint_disconnect (mint); + cb (cls, 0, NULL, mint->emsg); + } + break; + } +} + +static void +request_succeeded (struct TALER_MINT_Handle *mint, long resp_code) +{ + char *emsg; + + emsg = NULL; + switch (mint->req_type) + { + case REQUEST_TYPE_NONE: + GNUNET_assert (0); + break; + case REQUEST_TYPE_KEYSGET: + { + struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get; + TALER_MINT_ContinuationCallback cont_cb; + void *cont_cls; + struct TALER_MINT_SigningPublicKey **sign_keys; + struct TALER_MINT_DenomPublicKey **denom_keys; + unsigned int n_sign_keys; + unsigned int n_denom_keys; + + GNUNET_assert (NULL != gh); + cont_cb = gh->cont_cb; + cont_cls = gh->cont_cls; + if (200 == resp_code) + { + /* parse JSON object from the mint->buf which is of size mint->buf_size */ + if (GNUNET_OK == + parse_response_keys_get (mint->buf, mint->buf_size, + &sign_keys, &n_sign_keys, + &denom_keys, &n_denom_keys)) + gh->cb (gh->cls, sign_keys, denom_keys); + else + emsg = GNUNET_strdup ("Error parsing response"); + } + else + GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code); + cleanup_keys_get (gh); + mint_disconnect (mint); + cont_cb (cont_cls, emsg); + } + break; + case REQUEST_TYPE_DEPOSIT: + { + struct TALER_MINT_DepositHandle *dh = mint->req.deposit; + TALER_MINT_DepositResultCallback cb; + void *cls; + int status; + json_t *obj; + + GNUNET_assert (NULL != dh); + obj = NULL; + cb = dh->cb; + cls = dh->cls; + status = 0; + if (200 == resp_code) + { + /* parse JSON object from the mint->buf which is of size mint->buf_size */ + if (GNUNET_OK != + parse_deposit_response (mint->buf, mint->buf_size, + &status, &obj)) + emsg = GNUNET_strdup ("Error parsing response"); + } + else + GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code); + cleanup_deposit (dh); + mint_disconnect (mint); + cb (cls, status, obj, emsg); + } + break; + } + GNUNET_free_non_null (emsg); +} + + +static void +do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); + +static void +perform (struct TALER_MINT_Context *ctx) +{ + fd_set fd_rs; + fd_set fd_ws; + struct GNUNET_NETWORK_FDSet rs; + struct GNUNET_NETWORK_FDSet ws; + CURLMsg *cmsg; + struct TALER_MINT_Handle *mint; + long timeout; + long resp_code; + static unsigned int n_old; + int n_running; + int n_completed; + int max_fd; + + n_completed = 0; + curl_multi_perform (ctx->multi, &n_running); + GNUNET_assert (0 <= n_running); + if ((0 == n_running) || (n_running < n_old)) + { + /* some requests were completed -- handle them */ + while (NULL != (cmsg = curl_multi_info_read (ctx->multi, &n_completed))) + { + GNUNET_break (CURLMSG_DONE == cmsg->msg); /* curl only has CURLMSG_DONE */ + GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, + CURLINFO_PRIVATE, + (char *) &mint)); + GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, + CURLINFO_RESPONSE_CODE, + &resp_code)); + GNUNET_assert (ctx == mint->ctx); /* did we get the correct one? */ + if (CURLE_OK == cmsg->data.result) + request_succeeded (mint, resp_code); + else + request_failed (mint, resp_code); + } + } + n_old = n_running; + /* reschedule perform() */ + if (0 != n_old) + { + FD_ZERO (&fd_rs); + FD_ZERO (&fd_ws); + GNUNET_assert (CURLM_OK == curl_multi_fdset (ctx->multi, + &fd_rs, + &fd_ws, + NULL, + &max_fd)); + if (-1 == max_fd) + { + ctx->perform_task = GNUNET_SCHEDULER_add_delayed + (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), + &do_perform, ctx); + return; + } + GNUNET_assert (CURLM_OK == curl_multi_timeout (ctx->multi, &timeout)); + if (-1 == timeout) + { + timeout = 1000 * 60 * 5; + } + GNUNET_NETWORK_fdset_zero (&rs); + GNUNET_NETWORK_fdset_zero (&ws); + GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1); + GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1); + ctx->perform_task = GNUNET_SCHEDULER_add_select + (GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, timeout), + &rs, &ws, + &do_perform, ctx); + } +} + + +static void +do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct TALER_MINT_Context *ctx = cls; + + GNUNET_assert (NULL != ctx->perform_task); + ctx->perform_task = NULL; + perform (ctx); +} + +static void +perform_now (struct TALER_MINT_Context *ctx) +{ + if (NULL != ctx->perform_task) + { + GNUNET_SCHEDULER_cancel (ctx->perform_task); + ctx->perform_task = NULL; + } + ctx->perform_task = GNUNET_SCHEDULER_add_now (&do_perform, ctx); +} + + +/* This function gets called by libcurl as soon as there is data received that */ +/* needs to be saved. The size of the data pointed to by ptr is size */ +/* multiplied with nmemb, it will not be zero terminated. Return the number */ +/* of bytes actually taken care of. If that amount differs from the amount passed */ +/* to your function, it'll signal an error to the library. This will abort the */ +/* transfer and return CURLE_WRITE_ERROR. */ + +/* From 7.18.0, the function can return CURL_WRITEFUNC_PAUSE which then will */ +/* cause writing to this connection to become paused. See */ +/* curl_easy_pause(3) for further details. */ + +/* This function may be called with zero bytes data if the transferred file is */ +/* empty. */ + +/* Set this option to NULL to get the internal default function. The internal */ +/* default function will write the data to the FILE * given with */ +/* CURLOPT_WRITEDATA. */ + +/* Set the userdata argument with the CURLOPT_WRITEDATA option. */ + +/* The callback function will be passed as much data as possible in all invokes, */ +/* but you cannot possibly make any assumptions. It may be one byte, it may be */ +/* thousands. The maximum amount of body data that can be passed to the write */ +/* callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE (the usual */ +/* default is 16K). If you however have CURLOPT_HEADER set, which sends */ +/* header data to the write callback, you can get up to */ +/* CURL_MAX_HTTP_HEADER bytes of header data passed into it. This usually */ +/* means 100K. */ +static size_t +download (char *bufptr, size_t size, size_t nitems, void *cls) +{ + struct TALER_MINT_Handle *mint = cls; + size_t msize; + void *buf; + + if (0 == size * nitems) + { + /* file is empty */ + return 0; + } + msize = size * nitems; + mint->buf = GNUNET_realloc (mint->buf, mint->buf_size + msize); + buf = mint->buf + mint->buf_size; + memcpy (buf, bufptr, msize); + mint->buf_size += msize; + return msize; +} + + +/** + * Initialise a connection to the mint. + * + * @param ctx the context + * @param hostname the hostname of the mint + * @param port the point where the mint's HTTP service is running. + * @param mint_key the public key of the mint. This is used to verify the + * responses of the mint. + * @return the mint handle; NULL upon error + */ +struct TALER_MINT_Handle * +TALER_MINT_connect (struct TALER_MINT_Context *ctx, + const char *hostname, + uint16_t port, + struct GNUNET_CRYPTO_EddsaPublicKey *mint_key) +{ + struct TALER_MINT_Handle *mint; + + mint = GNUNET_new (struct TALER_MINT_Handle); + mint->ctx = ctx; + mint->hostname = GNUNET_strdup (hostname); + mint->port = (0 != port) ? port : 80; + mint->curl = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_SHARE, ctx->share)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_ERRORBUFFER, mint->emsg)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_WRITEFUNCTION, &download)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_WRITEDATA, mint)); + GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_PRIVATE, mint)); + return mint; +} + +/** + * Disconnect from the mint + * + * @param mint the mint handle + */ +void +TALER_MINT_disconnect (struct TALER_MINT_Handle *mint) +{ + if (GNUNET_YES == mint->connected) + mint_disconnect (mint); + curl_easy_cleanup (mint->curl); + GNUNET_free (mint->hostname); + GNUNET_free (mint); +} + +/** + * Get the signing and denomination key of the mint. + * + * @param mint handle to the mint + * @param cb the callback to call with each retrieved denomination key + * @param cls closure for the above callback + * @param cont_cb the callback to call after completing this asynchronous call + * @param cont_cls the closure for the continuation callback + * @return a handle to this asynchronous call; NULL upon eror + */ +struct TALER_MINT_KeysGetHandle * +TALER_MINT_keys_get (struct TALER_MINT_Handle *mint, + TALER_MINT_KeysGetCallback cb, void *cls, + TALER_MINT_ContinuationCallback cont_cb, void *cont_cls) +{ + struct TALER_MINT_KeysGetHandle *gh; + + GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type); + gh = GNUNET_new (struct TALER_MINT_KeysGetHandle); + gh->mint = mint; + mint->req_type = REQUEST_TYPE_KEYSGET; + mint->req.keys_get = gh; + gh->cb = cb; + gh->cls = cls; + gh->cont_cb = cont_cb; + gh->cont_cls = cont_cls; + GNUNET_asprintf (&gh->url, "http://%s:%hu/keys", mint->hostname, mint->port); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_URL, gh->url)); + if (GNUNET_NO == mint->connected) + mint_connect (mint); + perform_now (mint->ctx); + return gh; +} + + +/** + * Cancel the asynchronous call initiated by TALER_MINT_keys_get(). This + * should not be called if either of the @a TALER_MINT_KeysGetCallback or + * @a TALER_MINT_ContinuationCallback passed to TALER_MINT_keys_get() have + * been called. + * + * @param get the handle for retrieving the keys + */ +void +TALER_MINT_keys_get_cancel (struct TALER_MINT_KeysGetHandle *get) +{ + struct TALER_MINT_Handle *mint = get->mint; + + mint_disconnect (mint); + cleanup_keys_get (get); +} + +/** + * Submit a deposit permission to the mint and get the mint's response + * + * @param mint the mint handle + * @param cb the callback to call when a reply for this request is available + * @param cls closure for the above callback + * @param deposit_obj the deposit permission received from the customer along + * with the wireformat JSON object + * @return a handle for this request; NULL if the JSON object could not be + * parsed or is of incorrect format or any other error. In this case, + * the callback is not called. + */ +struct TALER_MINT_DepositHandle * +TALER_MINT_deposit_submit_json (struct TALER_MINT_Handle *mint, + TALER_MINT_DepositResultCallback cb, + void *cls, + json_t *deposit_obj) +{ + struct TALER_MINT_DepositHandle *dh; + + GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type); + dh = GNUNET_new (struct TALER_MINT_DepositHandle); + dh->mint = mint; + mint->req_type = REQUEST_TYPE_DEPOSIT; + mint->req.deposit = dh; + dh->cb = cb; + dh->cls = cls; + GNUNET_asprintf (&dh->url, "http://%s:%hu/deposit", mint->hostname, mint->port); + GNUNET_assert (NULL != (dh->json_enc = json_dumps (deposit_obj, JSON_COMPACT))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_URL, dh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDS, + dh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDSIZE, + strlen (dh->json_enc))); + GNUNET_assert (NULL != (dh->headers = + curl_slist_append (dh->headers, "Content-Type: application/json"))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_HTTPHEADER, dh->headers)); + if (GNUNET_NO == mint->connected) + mint_connect (mint); + perform_now (mint->ctx); + return dh; +} + + +/** + * Cancel a deposit permission request. This function cannot be used on a + * request handle if a response is already served for it. + * + * @param the deposit permission request handle + */ +void +TALER_MINT_deposit_submit_cancel (struct TALER_MINT_DepositHandle *deposit) +{ + struct TALER_MINT_Handle *mint = deposit->mint; + + mint_disconnect (mint); + cleanup_deposit (deposit); +} + + +/** + * Initialise this library. This function should be called before using any of + * the following functions. + * + * @return library context + */ +struct TALER_MINT_Context * +TALER_MINT_init () +{ + struct TALER_MINT_Context *ctx; + CURLM *multi; + CURLSH *share; + + if (fail) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "cURL was not initialised properly\n"); + return NULL; + } + if (NULL == (multi = curl_multi_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL multi handle\n"); + return NULL; + } + if (NULL == (share = curl_share_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL share handle\n"); + return NULL; + } + ctx = GNUNET_new (struct TALER_MINT_Context); + ctx->multi = multi; + ctx->share = share; + return ctx; +} + + +/** + * Cleanup library initialisation resources. This function should be called + * after using this library to cleanup the resources occupied during library's + * initialisation. + * + * @param ctx the library context + */ +void +TALER_MINT_cleanup (struct TALER_MINT_Context *ctx) +{ + curl_share_cleanup (ctx->share); + curl_multi_cleanup (ctx->multi); + if (NULL != ctx->perform_task) + { + GNUNET_break (0); /* investigate why this happens */ + GNUNET_SCHEDULER_cancel (ctx->perform_task); + } + GNUNET_free (ctx); +} + + +__attribute__ ((constructor)) +void +TALER_MINT_constructor__ (void) +{ + CURLcode ret; + if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT))) + { + CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, "curl_global_init", ret); + fail = 1; + } +} + +__attribute__ ((destructor)) +void +TALER_MINT_destructor__ (void) +{ + if (fail) + return; + curl_global_cleanup (); +} diff --git a/src/mint/mint_common.c b/src/mint/mint_common.c new file mode 100644 index 000000000..4afbf072b --- /dev/null +++ b/src/mint/mint_common.c @@ -0,0 +1,283 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint_common.c + * @brief Common functionality for the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Sree Harsha Totakura + */ + +#include "platform.h" +#include "mint.h" + +struct SignkeysIterateContext +{ + TALER_MINT_SignkeyIterator it; + void *it_cls; +}; + + +struct DenomkeysIterateContext +{ + const char *alias; + TALER_MINT_DenomkeyIterator it; + void *it_cls; +}; + + +static int +signkeys_iterate_dir_iter (void *cls, + const char *filename) +{ + + struct SignkeysIterateContext *skc = cls; + ssize_t nread; + struct TALER_MINT_SignKeyIssue issue; + nread = GNUNET_DISK_fn_read (filename, + &issue, + sizeof (struct TALER_MINT_SignKeyIssue)); + if (nread != sizeof (struct TALER_MINT_SignKeyIssue)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid signkey file: '%s'\n", filename); + return GNUNET_OK; + } + return skc->it (skc->it_cls, &issue); +} + + +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, void *cls) +{ + char *signkey_dir; + size_t len; + struct SignkeysIterateContext skc; + + len = GNUNET_asprintf (&signkey_dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mint_base_dir); + GNUNET_assert (len > 0); + + skc.it = it; + skc.it_cls = cls; + + return GNUNET_DISK_directory_scan (signkey_dir, &signkeys_iterate_dir_iter, &skc); +} + + +/** + * Import a denomination key from the given file + * + * @param filename the file to import the key from + * @param dki pointer to return the imported denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_MINT_DenomKeyIssue *dki) +{ + uint64_t size; + size_t offset; + void *data; + struct TALER_RSA_PrivateKey *priv; + int ret; + + ret = GNUNET_SYSERR; + data = NULL; + offset = sizeof (struct TALER_MINT_DenomKeyIssue) + - offsetof (struct TALER_MINT_DenomKeyIssue, signature); + if (GNUNET_OK != GNUNET_DISK_file_size (filename, + &size, + GNUNET_YES, + GNUNET_YES)) + goto cleanup; + if (size <= offset) + { + GNUNET_break (0); + goto cleanup; + } + data = GNUNET_malloc (size); + if (size != GNUNET_DISK_fn_read (filename, + data, + size)) + goto cleanup; + if (NULL == (priv = TALER_RSA_decode_key (data + offset, size - offset))) + goto cleanup; + dki->denom_priv = priv; + (void) memcpy (&dki->signature, data, offset); + ret = GNUNET_OK; + + cleanup: + GNUNET_free_non_null (data); + return ret; +} + + +/** + * Exports a denomination key to the given file + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_MINT_DenomKeyIssue *dki) +{ + struct TALER_RSA_PrivateKeyBinaryEncoded *priv_enc; + struct GNUNET_DISK_FileHandle *fh; + ssize_t wrote; + size_t wsize; + int ret; + + fh = NULL; + priv_enc = NULL; + ret = GNUNET_SYSERR; + if (NULL == (fh = GNUNET_DISK_file_open + (filename, + GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE, + GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE))) + goto cleanup; + if (NULL == (priv_enc = TALER_RSA_encode_key (dki->denom_priv))) + goto cleanup; + wsize = sizeof (struct TALER_MINT_DenomKeyIssue) + - offsetof (struct TALER_MINT_DenomKeyIssue, signature); + if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, + &dki->signature, + wsize))) + goto cleanup; + if (wrote != wsize) + goto cleanup; + wsize = ntohs (priv_enc->len); + if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, + priv_enc, + wsize))) + goto cleanup; + if (wrote != wsize) + goto cleanup; + ret = GNUNET_OK; + cleanup: + GNUNET_free_non_null (priv_enc); + if (NULL != fh) + (void) GNUNET_DISK_file_close (fh); + return ret; +} + + +static int +denomkeys_iterate_keydir_iter (void *cls, + const char *filename) +{ + + struct DenomkeysIterateContext *dic = cls; + struct TALER_MINT_DenomKeyIssue issue; + + if (GNUNET_OK != TALER_MINT_read_denom_key (filename, &issue)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid denomkey file: '%s'\n", filename); + return GNUNET_OK; + } + return dic->it (dic->it_cls, dic->alias, &issue); +} + + +static int +denomkeys_iterate_topdir_iter (void *cls, + const char *filename) +{ + + struct DenomkeysIterateContext *dic = cls; + dic->alias = GNUNET_STRINGS_get_short_name (filename); + + // FIXME: differentiate between error case and normal iteration abortion + if (0 > GNUNET_DISK_directory_scan (filename, &denomkeys_iterate_keydir_iter, dic)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, void *cls) +{ + char *dir; + size_t len; + struct DenomkeysIterateContext dic; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS), + mint_base_dir); + GNUNET_assert (len > 0); + + dic.it = it; + dic.it_cls = cls; + + // scan over alias dirs + return GNUNET_DISK_directory_scan (dir, &denomkeys_iterate_topdir_iter, &dic); +} + + +struct GNUNET_CONFIGURATION_Handle * +TALER_MINT_config_load (const char *mint_base_dir) +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + char *cfg_dir; + int res; + + res = GNUNET_asprintf (&cfg_dir, "%s" DIR_SEPARATOR_STR "config", mint_base_dir); + GNUNET_assert (res > 0); + + cfg = GNUNET_CONFIGURATION_create (); + res = GNUNET_CONFIGURATION_load_from (cfg, cfg_dir); + GNUNET_free (cfg_dir); + if (GNUNET_OK != res) + return NULL; + return cfg; +} + +int +TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row, + int indices[3], struct TALER_AmountNBO *denom_nbo) +{ + if ((indices[0] < 0) || (indices[1] < 0) || (indices[2] < 0)) + return GNUNET_NO; + if (sizeof (uint32_t) != PQgetlength (result, row, indices[0])) + return GNUNET_SYSERR; + if (sizeof (uint32_t) != PQgetlength (result, row, indices[1])) + return GNUNET_SYSERR; + if (PQgetlength (result, row, indices[2]) > TALER_CURRENCY_LEN) + return GNUNET_SYSERR; + denom_nbo->value = *(uint32_t *) PQgetvalue (result, row, indices[0]); + denom_nbo->fraction = *(uint32_t *) PQgetvalue (result, row, indices[1]); + memset (denom_nbo->currency, 0, TALER_CURRENCY_LEN); + memcpy (denom_nbo->currency, PQgetvalue (result, row, indices[2]), PQgetlength (result, row, indices[2])); + return GNUNET_OK; +} + + +int +TALER_TALER_DB_extract_amount (PGresult *result, unsigned int row, + int indices[3], struct TALER_Amount *denom) +{ + struct TALER_AmountNBO denom_nbo; + int res; + + res = TALER_TALER_DB_extract_amount_nbo (result, row, indices, &denom_nbo); + if (GNUNET_OK != res) + return res; + *denom = TALER_amount_ntoh (denom_nbo); + return GNUNET_OK; +} + +/* end of mint_common.c */ diff --git a/src/mint/mint_db.c b/src/mint/mint_db.c new file mode 100644 index 000000000..6dc025877 --- /dev/null +++ b/src/mint/mint_db.c @@ -0,0 +1,1838 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint_db.c + * @brief Database access for the mint + * @author Florian Dold + */ +#include "platform.h" +#include "taler_db_lib.h" +#include "taler_signatures.h" +#include "mint_db.h" +#include "mint.h" +#include <pthread.h> + +/** + * Thread-local database connection. + * Contains a pointer to PGconn or NULL. + */ +static pthread_key_t db_conn_threadlocal; + + +/** + * Database connection string, as read from + * the configuration. + */ +static char *TALER_MINT_db_connection_cfg_str; + + +#define break_db_err(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +int +TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, + struct TALER_RSA_BlindedSignaturePurpose *blind_ev, + struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (blind_ev), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (db_conn, "get_collectable_blindcoins", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("blind_ev_sig", &collectable->ev_sig), + TALER_DB_RESULT_SPEC("denom_pub", &collectable->denom_pub), + TALER_DB_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), + TALER_DB_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + (void) memcpy (&collectable->ev, blind_ev, sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, + const struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (&collectable->ev), + TALER_DB_QUERY_PARAM_PTR (&collectable->ev_sig), + TALER_DB_QUERY_PARAM_PTR (&collectable->denom_pub), + TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_pub), + TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (db_conn, "insert_collectable_blindcoins", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert failed (updated '%s' tupes instead of '1')\n", + PQcmdTuples (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_reserve (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub, + struct Reserve *reserve) +{ + PGresult *result; + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve_pub), + TALER_DB_QUERY_PARAM_END + }; + + result = TALER_DB_exec_prepared (db_conn, "get_reserve", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + reserve->reserve_pub = *reserve_pub; + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("status_sig", &reserve->status_sig), + TALER_DB_RESULT_SPEC("status_sign_pub", &reserve->status_sign_pub), + TALER_DB_RESULT_SPEC_END + }; + + res = TALER_DB_extract_result (result, rs, 0); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + { + int fnums[] = { + PQfnumber (result, "balance_value"), + PQfnumber (result, "balance_fraction"), + PQfnumber (result, "balance_currency"), + }; + if (GNUNET_OK != TALER_TALER_DB_extract_amount_nbo (result, 0, fnums, &reserve->balance)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + } + + /* FIXME: Add expiration?? */ + + PQclear (result); + return GNUNET_OK; +} + + +/* If fresh is GNUNET_YES, set some fields to NULL as they are not actually valid */ +int +TALER_MINT_DB_update_reserve (PGconn *db_conn, + const struct Reserve *reserve, + int fresh) +{ + PGresult *result; + uint64_t stamp_sec; + + stamp_sec = GNUNET_ntohll (GNUNET_TIME_absolute_ntoh (reserve->expiration).abs_value_us / 1000000); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (&reserve->reserve_pub), + TALER_DB_QUERY_PARAM_PTR (&reserve->balance.value), + TALER_DB_QUERY_PARAM_PTR (&reserve->balance.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (&reserve->balance.currency, + strlen (reserve->balance.currency)), + TALER_DB_QUERY_PARAM_PTR (&reserve->status_sig), + TALER_DB_QUERY_PARAM_PTR (&reserve->status_sign_pub), + TALER_DB_QUERY_PARAM_PTR (&stamp_sec), + TALER_DB_QUERY_PARAM_END + }; + + /* set some fields to NULL if they are not actually valid */ + + if (GNUNET_YES == fresh) + { + unsigned i; + for (i = 4; i <= 7; i += 1) + { + params[i].data = NULL; + params[i].size = 0; + } + } + + result = TALER_DB_exec_prepared (db_conn, "update_reserve", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Update failed (updated '%s' tupes instead of '1')\n", + PQcmdTuples (result)); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + + +int +TALER_MINT_DB_prepare (PGconn *db_conn) +{ + PGresult *result; + + result = PQprepare (db_conn, "get_reserve", + "SELECT " + " balance_value, balance_fraction, balance_currency " + ",expiration_date, blind_session_pub, blind_session_priv" + ",status_sig, status_sign_pub " + "FROM reserves " + "WHERE reserve_pub=$1 " + "LIMIT 1; ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "update_reserve", + "UPDATE reserves " + "SET" + " balance_value=$2 " + ",balance_fraction=$3 " + ",balance_currency=$4 " + ",status_sig=$5 " + ",status_sign_pub=$6 " + ",expiration_date=$7 " + "WHERE reserve_pub=$1 ", + 9, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + result = PQprepare (db_conn, "insert_collectable_blindcoins", + "INSERT INTO collectable_blindcoins ( " + " blind_ev, blind_ev_sig " + ",denom_pub, reserve_pub, reserve_sig) " + "VALUES ($1, $2, $3, $4, $5)", + 6, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_collectable_blindcoins", + "SELECT " + "blind_ev_sig, denom_pub, reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_ev = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_reserve_order", + "SELECT " + " blind_ev, blind_ev_sig, denom_pub, reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_session_pub = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + /* FIXME: does it make sense to store these computed values in the DB? */ + result = PQprepare (db_conn, "get_refresh_session", + "SELECT " + " (SELECT count(*) FROM refresh_melt WHERE session_pub = $1)::INT2 as num_oldcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_pub = $1 and cnc_index = 0)::INT2 as num_newcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_pub = $1 and newcoin_index = 0)::INT2 as kappa " + ",noreveal_index" + ",session_commit_sig " + ",reveal_ok " + "FROM refresh_sessions " + "WHERE session_pub = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_known_coin", + "SELECT " + " coin_pub, denom_pub, denom_sig " + ",expended_value, expended_fraction, expended_currency " + ",refresh_session_pub " + "FROM known_coins " + "WHERE coin_pub = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "update_known_coin", + "UPDATE known_coins " + "SET " + " denom_pub = $2 " + ",denom_sig = $3 " + ",expended_value = $4 " + ",expended_fraction = $5 " + ",expended_currency = $6 " + ",refresh_session_pub = $7 " + "WHERE " + " coin_pub = $1 ", + 7, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_known_coin", + "INSERT INTO known_coins (" + " coin_pub" + ",denom_pub" + ",denom_sig" + ",expended_value" + ",expended_fraction" + ",expended_currency" + ",refresh_session_pub" + ")" + "VALUES ($1,$2,$3,$4,$5,$6,$7)", + 7, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_commit_link", + "SELECT " + " transfer_pub " + ",link_secret_enc " + "FROM refresh_commit_link " + "WHERE session_pub = $1 AND cnc_index = $2 AND oldcoin_index = $3", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_commit_coin", + "SELECT " + " link_vector_enc " + ",coin_ev " + "FROM refresh_commit_coin " + "WHERE session_pub = $1 AND cnc_index = $2 AND newcoin_index = $3", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_order", + "INSERT INTO refresh_order ( " + " newcoin_index " + ",session_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_melt", + "INSERT INTO refresh_melt ( " + " session_pub " + ",oldcoin_index " + ",coin_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3, $4) ", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_order", + "SELECT denom_pub " + "FROM refresh_order " + "WHERE session_pub = $1 AND newcoin_index = $2", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_collectable", + "SELECT ev_sig " + "FROM refresh_collectable " + "WHERE session_pub = $1 AND newcoin_index = $2", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_melt", + "SELECT coin_pub " + "FROM refresh_melt " + "WHERE session_pub = $1 AND oldcoin_index = $2", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_session", + "INSERT INTO refresh_sessions ( " + " session_pub " + ",noreveal_index " + ") " + "VALUES ($1, $2) ", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_commit_link", + "INSERT INTO refresh_commit_link ( " + " session_pub " + ",transfer_pub " + ",cnc_index " + ",oldcoin_index " + ",link_secret_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_commit_coin", + "INSERT INTO refresh_commit_coin ( " + " session_pub " + ",coin_ev " + ",cnc_index " + ",newcoin_index " + ",link_vector_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_collectable", + "INSERT INTO refresh_collectable ( " + " session_pub " + ",newcoin_index " + ",ev_sig " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "set_reveal_ok", + "UPDATE refresh_sessions " + "SET reveal_ok = TRUE " + "WHERE session_pub = $1 ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_link", + "SELECT link_vector_enc, ro.denom_pub, ev_sig " + "FROM refresh_melt rm " + " JOIN refresh_order ro USING (session_pub) " + " JOIN refresh_commit_coin rcc USING (session_pub) " + " JOIN refresh_sessions rs USING (session_pub) " + " JOIN refresh_collectable rc USING (session_pub) " + "WHERE rm.coin_pub = $1 " + "AND ro.newcoin_index = rcc.newcoin_index " + "AND ro.newcoin_index = rc.newcoin_index " + "AND rcc.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE rcc2.newcoin_index = 0 AND rcc2.session_pub = rs.session_pub " + " ) ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_transfer", + "SELECT transfer_pub, link_secret_enc " + "FROM refresh_melt rm " + " JOIN refresh_commit_link rcl USING (session_pub) " + " JOIN refresh_sessions rs USING (session_pub) " + "WHERE rm.coin_pub = $1 " + "AND rm.oldcoin_index = rcl.oldcoin_index " + "AND rcl.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE newcoin_index = 0 AND rcc2.session_pub = rm.session_pub " + " ) ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + if (GNUNET_OK != TALER_MINT_DB_prepare_deposits (db_conn)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param db_conn the database connection + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_rollback (PGconn *db_conn) +{ + PGresult *result; + + result = PQexec(db_conn, "ROLLBACK"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param db_conn the database connection + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_commit (PGconn *db_conn) +{ + PGresult *result; + + result = PQexec(db_conn, "COMMIT"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Start a transaction. + * + * @param db_conn the database connection + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_transaction (PGconn *db_conn) +{ + PGresult *result; + + result = PQexec(db_conn, "BEGIN"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Can't start transaction: %s\n", PQresultErrorMessage (result)); + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Insert a refresh order into the database. + */ +int +TALER_MINT_DB_insert_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(denom_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_order", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + struct RefreshSession *session) +{ + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_session", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* We're done if the caller is only interested in + * whether the session exists or not */ + + if (NULL == session) + return GNUNET_YES; + + memset (session, 0, sizeof (struct RefreshSession)); + + session->session_pub = *refresh_session_pub; + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("num_oldcoins", &session->num_oldcoins), + TALER_DB_RESULT_SPEC("num_newcoins", &session->num_newcoins), + TALER_DB_RESULT_SPEC("kappa", &session->kappa), + TALER_DB_RESULT_SPEC("noreveal_index", &session->noreveal_index), + TALER_DB_RESULT_SPEC("reveal_ok", &session->reveal_ok), + TALER_DB_RESULT_SPEC_END + }; + + res = TALER_DB_extract_result (result, rs, 0); + + if (GNUNET_OK != res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + if (TALER_DB_field_isnull (result, 0, "session_commit_sig")) + session->has_commit_sig = GNUNET_NO; + else + session->has_commit_sig = GNUNET_YES; + + session->num_oldcoins = ntohs (session->num_oldcoins); + session->num_newcoins = ntohs (session->num_newcoins); + session->kappa = ntohs (session->kappa); + session->noreveal_index = ntohs (session->noreveal_index); + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_get_known_coin (PGconn *db_conn, struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct KnownCoin *known_coin) +{ + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_known_coin", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* extract basic information about the known coin */ + + { + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("coin_pub", &known_coin->public_info.coin_pub), + TALER_DB_RESULT_SPEC("denom_pub", &known_coin->public_info.denom_pub), + TALER_DB_RESULT_SPEC("denom_sig", &known_coin->public_info.denom_sig), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != (res = TALER_DB_extract_result (result, rs, 0))) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + } + + /* extract the expended amount of the coin */ + + if (GNUNET_OK != TALER_DB_extract_amount (result, 0, + "expended_value", + "expended_fraction", + "expended_currency", + &known_coin->expended_balance)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + /* extract the refresh session of the coin or mark it as missing */ + + { + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("refresh_session_pub", &known_coin->refresh_session_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_SYSERR == (res = TALER_DB_extract_result (result, rs, 0))) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + if (GNUNET_NO == res) + { + known_coin->is_refreshed = GNUNET_NO; + memset (&known_coin->refresh_session_pub, 0, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + } + else + { + known_coin->is_refreshed = GNUNET_YES; + } + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_create_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + uint16_t noreveal_index; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&noreveal_index), + TALER_DB_QUERY_PARAM_END + }; + + noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); + noreveal_index = htonl (noreveal_index); + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_session", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_set_commit_signature (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct GNUNET_CRYPTO_EddsaSignature *commit_sig) +{ + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +int +TALER_MINT_DB_set_reveal_ok (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "set_reveal_ok", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_update_known_coin (PGconn *db_conn, + const struct KnownCoin *known_coin) +{ + struct TALER_AmountNBO expended_nbo = TALER_amount_hton (known_coin->expended_balance); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.coin_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_sig), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.value), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED(expended_nbo.currency, strlen (expended_nbo.currency)), + TALER_DB_QUERY_PARAM_PTR(&known_coin->refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + if (GNUNET_NO == known_coin->is_refreshed) + { + // Mind the magic index! + params[6].data = NULL; + params[6].size = 0; + } + + PGresult *result = TALER_DB_exec_prepared (db_conn, "update_known_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + PQclear (result); + // return 'no' here (don't fail) so that we can + // insert if update fails (=> "upsert") + return GNUNET_NO; + } + + PQclear (result); + return GNUNET_YES; +} + +int +TALER_MINT_DB_insert_known_coin (PGconn *db_conn, + const struct KnownCoin *known_coin) +{ + struct TALER_AmountNBO expended_nbo = TALER_amount_hton (known_coin->expended_balance); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.coin_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_sig), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.value), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED(&expended_nbo.currency, strlen (expended_nbo.currency)), + TALER_DB_QUERY_PARAM_PTR(&known_coin->refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + if (GNUNET_NO == known_coin->is_refreshed) + { + // Mind the magic index! + params[6].data = NULL; + params[6].size = 0; + } + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_known_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + PQclear (result); + // return 'no' here (don't fail) so that we can + // update if insert fails (=> "upsert") + return GNUNET_NO; + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_upsert_known_coin (PGconn *db_conn, struct KnownCoin *known_coin) +{ + int ret; + ret = TALER_MINT_DB_update_known_coin (db_conn, known_coin); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES == ret) + return GNUNET_YES; + return TALER_MINT_DB_insert_known_coin (db_conn, known_coin); +} + + +int +TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, struct RefreshCommitLink *commit_link) +{ + uint16_t cnc_index_nbo = htons (commit_link->cnc_index); + uint16_t oldcoin_index_nbo = htons (commit_link->oldcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&commit_link->session_pub), + TALER_DB_QUERY_PARAM_PTR(&commit_link->transfer_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR_SIZED(&commit_link->shared_secret_enc, sizeof (struct GNUNET_HashCode)), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_link", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, struct RefreshCommitCoin *commit_coin) +{ + uint16_t cnc_index_nbo = htons (commit_coin->cnc_index); + uint16_t newcoin_index_nbo = htons (commit_coin->newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&commit_coin->session_pub), + TALER_DB_QUERY_PARAM_PTR(&commit_coin->coin_ev), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR_SIZED(&commit_coin->link_enc, sizeof (struct LinkData)), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int cnc_index, int oldcoin_index, + struct RefreshCommitLink *cc) +{ + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + cc->cnc_index = cnc_index; + cc->oldcoin_index = oldcoin_index; + cc->session_pub = *refresh_session_pub; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_link", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("transfer_pub", &cc->transfer_pub), + TALER_DB_RESULT_SPEC_SIZED("link_secret_enc", &cc->shared_secret_enc, + TALER_REFRESH_SHARED_SECRET_LENGTH), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_free (cc); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int cnc_index, int newcoin_index, + struct RefreshCommitCoin *cc) +{ + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t newcoin_index_nbo = htons (newcoin_index); + + cc->cnc_index = cnc_index; + cc->newcoin_index = newcoin_index; + cc->session_pub = *refresh_session_pub; + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_coin", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("coin_ev", &cc->coin_ev), + TALER_DB_RESULT_SPEC_SIZED("link_vector_enc", &cc->link_enc, + TALER_REFRESH_LINK_LENGTH), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_get_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + uint16_t newcoin_index_nbo = htons (newcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_order", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + GNUNET_assert (1 == PQntuples (result)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("denom_pub", denom_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_Signature *ev_sig) +{ + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(ev_sig), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_collectable", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_Signature *ev_sig) +{ + + uint16_t newcoin_index_nbo = htons (newcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_collectable", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + GNUNET_assert (1 == PQntuples (result)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("ev_sig", ev_sig), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + + +int +TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_PTR(denom_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_melt", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + + +int +TALER_MINT_DB_get_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub) +{ + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_melt", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + GNUNET_assert (1 == PQntuples (result)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("coin_pub", coin_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_db_get_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + LinkIterator link_iter, + void *cls) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_link", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + + int i = 0; + int res; + + for (i = 0; i < PQntuples (result); i++) + { + struct LinkDataEnc link_data_enc; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_RSA_Signature ev_sig; + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("link_vector_enc", &link_data_enc), + TALER_DB_RESULT_SPEC("denom_pub", &denom_pub), + TALER_DB_RESULT_SPEC("ev_sig", &ev_sig), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, i)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != (res = link_iter (cls, &link_data_enc, &denom_pub, &ev_sig))) + { + GNUNET_assert (GNUNET_SYSERR != res); + PQclear (result); + return res; + } + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_db_get_transfer (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, + struct SharedSecretEnc *shared_secret_enc) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_transfer", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "got %d tuples for get_transfer\n", PQntuples (result)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("transfer_pub", transfer_pub), + TALER_DB_RESULT_SPEC("link_secret_enc", shared_secret_enc), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_init_deposits (PGconn *conn, int tmp) +{ + const char *tmp_str = (1 == tmp) ? "TEMPORARY" : ""; + char *sql; + PGresult *res; + int ret; + + res = NULL; + (void) GNUNET_asprintf (&sql, + "CREATE %1$s TABLE IF NOT EXISTS deposits (" + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL CHECK (length(denom_pub)=32)" + ",transaction_id INT8 NOT NULL" + ",amount_value INT4 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",merchant_pub BYTEA NOT NULL" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" + ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" + ",wire TEXT NOT NULL" + ")", + tmp_str); + res = PQexec (conn, sql); + GNUNET_free (sql); + if (PGRES_COMMAND_OK != PQresultStatus (res)) + { + break_db_err (res); + ret = GNUNET_SYSERR; + } + else + ret = GNUNET_OK; + PQclear (res); + return ret; +} + +int +TALER_MINT_DB_prepare_deposits (PGconn *db_conn) +{ + PGresult *result; + + result = PQprepare (db_conn, "insert_deposit", + "INSERT INTO deposits (" + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig," + "wire" + ") VALUES (" + "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11" + ")", + 11, NULL); + EXITIF (PGRES_COMMAND_OK != PQresultStatus(result)); + PQclear (result); + + result = PQprepare (db_conn, "get_deposit", + "SELECT " + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig" + " FROM deposits WHERE (" + "coin_pub = $1" + ")", + 1, NULL); + EXITIF (PGRES_COMMAND_OK != PQresultStatus(result)); + PQclear (result); + + return GNUNET_OK; + + EXITIF_exit: + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; +} + + +int +TALER_MINT_DB_insert_deposit (PGconn *db_conn, + const struct Deposit *deposit) +{ + struct TALER_DB_QueryParam params[]= { + TALER_DB_QUERY_PARAM_PTR (&deposit->coin_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->denom_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_DB_QUERY_PARAM_PTR (&deposit->amount.value), + TALER_DB_QUERY_PARAM_PTR (&deposit->amount.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (deposit->amount.currency, strlen (deposit->amount.currency)), + TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->h_contract), + TALER_DB_QUERY_PARAM_PTR (&deposit->h_wire), + TALER_DB_QUERY_PARAM_PTR (&deposit->coin_sig), + TALER_DB_QUERY_PARAM_PTR_SIZED (deposit->wire, strlen(deposit->wire)), + TALER_DB_QUERY_PARAM_END + }; + PGresult *result; + + result = TALER_DB_exec_prepared (db_conn, "insert_deposit", params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + +int +TALER_MINT_DB_get_deposit (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, + struct Deposit **r_deposit) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (coin_pub), + TALER_DB_QUERY_PARAM_END + }; + PGresult *result; + struct Deposit *deposit; + + deposit = NULL; + result = TALER_DB_exec_prepared (db_conn, "get_deposit", params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + goto EXITIF_exit; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_break (0); + goto EXITIF_exit; + } + + { + deposit = GNUNET_malloc (sizeof (struct Deposit)); /* Without wire data */ + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC ("coin_pub", &deposit->coin_pub), + TALER_DB_RESULT_SPEC ("denom_pub", &deposit->denom_pub), + TALER_DB_RESULT_SPEC ("coin_sig", &deposit->coin_sig), + TALER_DB_RESULT_SPEC ("transaction_id", &deposit->transaction_id), + TALER_DB_RESULT_SPEC ("merchant_pub", &deposit->merchant_pub), + TALER_DB_RESULT_SPEC ("h_contract", &deposit->h_contract), + TALER_DB_RESULT_SPEC ("h_wire", &deposit->h_wire), + TALER_DB_RESULT_SPEC_END + }; + EXITIF (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)); + EXITIF (GNUNET_OK != TALER_DB_extract_amount_nbo (result, 0, + "amount_value", + "amount_fraction", + "amount_currency", + &deposit->amount)); + deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT); + deposit->purpose.size = htonl (sizeof (struct Deposit) + - offsetof (struct Deposit, purpose)); + } + + PQclear (result); + *r_deposit = deposit; + return GNUNET_OK; + +EXITIF_exit: + PQclear (result); + GNUNET_free_non_null (deposit); + deposit = NULL; + return GNUNET_SYSERR; +} + + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param the database connection, or NULL on error + */ +PGconn * +TALER_MINT_DB_get_connection (void) +{ + PGconn *db_conn; + + if (NULL != (db_conn = pthread_getspecific (db_conn_threadlocal))) + return db_conn; + + db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); + + if (CONNECTION_OK != PQstatus (db_conn)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "db connection failed: %s\n", + PQerrorMessage (db_conn)); + GNUNET_break (0); + return NULL; + } + + if (GNUNET_OK != TALER_MINT_DB_prepare (db_conn)) + { + GNUNET_break (0); + return NULL; + } + if (0 != pthread_setspecific (db_conn_threadlocal, db_conn)) + { + GNUNET_break (0); + return NULL; + } + return db_conn; +} + + +/** + * Close thread-local database connection when a thread is destroyed. + * + * @param closure we get from pthreads (the db handle) + */ +static void +db_conn_destroy (void *cls) +{ + PGconn *db_conn = cls; + if (NULL != db_conn) + PQfinish (db_conn); +} + + +/** + * Initialize database subsystem. + * + * @param connection_cfg configuration to use to talk to DB + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_init (const char *connection_cfg) +{ + + if (0 != pthread_key_create (&db_conn_threadlocal, &db_conn_destroy)) + { + fprintf (stderr, + "Can't create pthread key.\n"); + return GNUNET_SYSERR; + } + TALER_MINT_db_connection_cfg_str = GNUNET_strdup (connection_cfg); + return GNUNET_OK; +} diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h new file mode 100644 index 000000000..4f47aac1c --- /dev/null +++ b/src/mint/mint_db.h @@ -0,0 +1,344 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/mint_db.h + * @brief Mint-specific database access + * @author Florian Dold + */ + +#ifndef _NEURO_MINT_DB_H +#define _NEURO_MINT_DB_H + +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_types.h" +#include "taler_rsa.h" + + +/** + * Reserve row. Corresponds to table 'reserves' in + * the mint's database. + */ +struct Reserve +{ + /** + * Signature over the purse. + * Only valid if (blind_session_missing==GNUNET_YES). + */ + struct GNUNET_CRYPTO_EddsaSignature status_sig; + /** + * Signature with purpose TALER_SIGNATURE_PURSE. + * Only valid if (blind_session_missing==GNUNET_YES). + */ + struct GNUNET_CRYPTO_EccSignaturePurpose status_sig_purpose; + /** + * Signing key used to sign the purse. + * Only valid if (blind_session_missing==GNUNET_YES). + */ + struct GNUNET_CRYPTO_EddsaPublicKey status_sign_pub; + /** + * Withdraw public key, identifies the purse. + * Only the customer knows the corresponding private key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + /** + * Remaining balance in the purse. + */ + struct TALER_AmountNBO balance; + + /** + * Expiration date for the purse. + */ + struct GNUNET_TIME_AbsoluteNBO expiration; +}; + + +struct CollectableBlindcoin +{ + struct TALER_RSA_BlindedSignaturePurpose ev; + struct TALER_RSA_Signature ev_sig; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + struct GNUNET_CRYPTO_EddsaSignature reserve_sig; +}; + + +struct RefreshSession +{ + int has_commit_sig; + struct GNUNET_CRYPTO_EddsaSignature commit_sig; + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; + uint16_t num_oldcoins; + uint16_t num_newcoins; + uint16_t kappa; + uint16_t noreveal_index; + uint8_t reveal_ok; +}; + + +#define TALER_REFRESH_SHARED_SECRET_LENGTH (sizeof (struct GNUNET_HashCode)) +#define TALER_REFRESH_LINK_LENGTH (sizeof (struct LinkData)) + +struct RefreshCommitLink +{ + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub; + uint16_t cnc_index; + uint16_t oldcoin_index; + char shared_secret_enc[sizeof (struct GNUNET_HashCode)]; +}; + +struct LinkData +{ + struct GNUNET_CRYPTO_EcdsaPrivateKey coin_priv; + struct TALER_RSA_BlindingKeyBinaryEncoded bkey_enc; +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct SharedSecretEnc +{ + char data[TALER_REFRESH_SHARED_SECRET_LENGTH]; +}; + + +struct LinkDataEnc +{ + char data[sizeof (struct LinkData)]; +}; + +GNUNET_NETWORK_STRUCT_END + +struct RefreshCommitCoin +{ + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; + struct TALER_RSA_BlindedSignaturePurpose coin_ev; + uint16_t cnc_index; + uint16_t newcoin_index; + char link_enc[sizeof (struct LinkData)]; +}; + + +struct KnownCoin +{ + struct TALER_CoinPublicInfo public_info; + struct TALER_Amount expended_balance; + int is_refreshed; + /** + * Refreshing session, only valid if + * is_refreshed==1. + */ + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; +}; + +GNUNET_NETWORK_STRUCT_BEGIN + +struct Deposit +{ + /* FIXME: should be TALER_CoinPublicInfo */ + struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_RSA_Signature coin_sig; + struct TALER_RSA_SignaturePurpose purpose; + uint64_t transaction_id; + struct TALER_AmountNBO amount; + struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub; + struct GNUNET_HashCode h_contract; + struct GNUNET_HashCode h_wire; + /* TODO: uint16_t wire_size */ + char wire[]; /* string encoded wire JSON object */ +}; + +GNUNET_NETWORK_STRUCT_END + +int +TALER_MINT_DB_prepare (PGconn *db_conn); + +int +TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, + struct TALER_RSA_BlindedSignaturePurpose *blind_ev, + struct CollectableBlindcoin *collectable); + +int +TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, + const struct CollectableBlindcoin *collectable); + + +int +TALER_MINT_DB_rollback (PGconn *db_conn); + + +int +TALER_MINT_DB_transaction (PGconn *db_conn); + + +int +TALER_MINT_DB_commit (PGconn *db_conn); + + +int +TALER_MINT_DB_get_reserve (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub, + struct Reserve *reserve_res); + +int +TALER_MINT_DB_update_reserve (PGconn *db_conn, + const struct Reserve *reserve, + int fresh); + + +int +TALER_MINT_DB_insert_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + +int +TALER_MINT_DB_get_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + struct RefreshSession *r_session); + + +int +TALER_MINT_DB_get_known_coin (PGconn *db_conn, struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct KnownCoin *known_coin); + + +int +TALER_MINT_DB_upsert_known_coin (PGconn *db_conn, struct KnownCoin *known_coin); + + +int +TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, struct RefreshCommitLink *commit_link); + +int +TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, struct RefreshCommitCoin *commit_coin); + + +int +TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int i, int j, + struct RefreshCommitLink *commit_link); + + +int +TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int i, int j, + struct RefreshCommitCoin *commit_coin); + + +int +TALER_MINT_DB_create_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey + *session_pub); + + +int +TALER_MINT_DB_get_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + + +int +TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_Signature *ev_sig); +int +TALER_MINT_DB_get_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_Signature *ev_sig); +int +TALER_MINT_DB_set_reveal_ok (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub); + +int +TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + + +int +TALER_MINT_DB_get_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub); + + +typedef +int (*LinkIterator) (void *cls, + const struct LinkDataEnc *link_data_enc, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub, + const struct TALER_RSA_Signature *ev_sig); + +int +TALER_db_get_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + LinkIterator link_iter, + void *cls); + + +int +TALER_db_get_transfer (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, + struct SharedSecretEnc *shared_secret_enc); + +int +TALER_MINT_DB_init_deposits (PGconn *db_conn, int temporary); + +int +TALER_MINT_DB_prepare_deposits (PGconn *db_conn); + +int +TALER_MINT_DB_insert_deposit (PGconn *db_conn, + const struct Deposit *deposit); + +int +TALER_MINT_DB_get_deposit (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, + struct Deposit **r_deposit); +int +TALER_MINT_DB_insert_known_coin (PGconn *db_conn, + const struct KnownCoin *known_coin); + + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param the database connection, or NULL on error + */ +PGconn * +TALER_MINT_DB_get_connection (void); + + +int +TALER_MINT_DB_init (const char *connection_cfg); + + + +#endif /* _NEURO_MINT_DB_H */ diff --git a/src/mint/taler-mint-dbinit.c b/src/mint/taler-mint-dbinit.c new file mode 100644 index 000000000..d877f62c6 --- /dev/null +++ b/src/mint/taler-mint-dbinit.c @@ -0,0 +1,285 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-dbinit.c + * @brief Create tables for the mint database. + * @author Florian Dold + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "mint.h" + + +#define break_db_err(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + PQclear (result); \ + } while (0) + + +static char *mint_base_dir; +static struct GNUNET_CONFIGURATION_Handle *cfg; +static PGconn *db_conn; +static char *TALER_MINT_db_connection_cfg_str; + + +int +TALER_MINT_init_withdraw_tables (PGconn *conn) +{ + PGresult *result; + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS reserves" + "(" + " reserve_pub BYTEA PRIMARY KEY" + ",balance_value INT4 NOT NULL" + ",balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",status_sig BYTEA" + ",status_sign_pub BYTEA" + ",expiration_date INT8 NOT NULL" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS collectable_blindcoins" + "(" + "blind_ev BYTEA PRIMARY KEY" + ",blind_ev_sig BYTEA NOT NULL" + ",denom_pub BYTEA NOT NULL" + ",reserve_sig BYTEA NOT NULL" + ",reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub)" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS known_coins " + "(" + " coin_pub BYTEA NOT NULL PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" + ",denom_sig BYTEA NOT NULL" + ",expended_value INT4 NOT NULL" + ",expended_fraction INT4 NOT NULL" + ",expended_currency VARCHAR(4) NOT NULL" + ",refresh_session_pub BYTEA" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_sessions " + "(" + " session_pub BYTEA PRIMARY KEY CHECK (length(session_pub) = 32)" + ",session_melt_sig BYTEA" + ",session_commit_sig BYTEA" + ",noreveal_index INT2 NOT NULL" + // non-zero if all reveals were ok + // and the new coin signatures are ready + ",reveal_ok BOOLEAN NOT NULL DEFAULT false" + ") "); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_order " + "( " + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" + ",newcoin_index INT2 NOT NULL " + ",denom_pub BYTEA NOT NULL " + ",PRIMARY KEY (session_pub, newcoin_index)" + ") "); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_commit_link" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" + ",transfer_pub BYTEA NOT NULL" + ",link_secret_enc BYTEA NOT NULL" + // index of the old coin in the customer's request + ",oldcoin_index INT2 NOT NULL" + // index for cut and choose, + // ranges from 0 to kappa-1 + ",cnc_index INT2 NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_commit_coin" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",link_vector_enc BYTEA NOT NULL" + // index of the new coin in the customer's request + ",newcoin_index INT2 NOT NULL" + // index for cut and choose, + ",cnc_index INT2 NOT NULL" + ",coin_ev BYTEA NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_melt" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " + ",denom_pub BYTEA NOT NULL " + ",oldcoin_index INT2 NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_collectable" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",ev_sig BYTEA NOT NULL" + ",newcoin_index INT2 NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS deposits " + "( " + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL CHECK (length(denom_pub)=32)" + ",transaction_id INT8 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",amount_value INT4 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",merchant_pub BYTEA NOT NULL" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" + ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" + ",wire TEXT NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + return GNUNET_OK; +} + + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory", 1, + &GNUNET_GETOPT_set_filename, &mint_base_dir}, + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_GETOPT_run ("taler-mint-serve", options, argc, argv) < 0) + return 1; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-dbinit", "INFO", NULL)); + + if (NULL == mint_base_dir) + { + fprintf (stderr, "Mint base directory not given.\n"); + return 1; + } + + cfg = TALER_MINT_config_load (mint_base_dir); + if (NULL == cfg) + { + fprintf (stderr, "Can't load mint configuration.\n"); + return 1; + } + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "mint", "db", &TALER_MINT_db_connection_cfg_str)) + { + fprintf (stderr, "Configuration 'mint.db' not found.\n"); + return 42; + } + db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); + if (CONNECTION_OK != PQstatus (db_conn)) + { + fprintf (stderr, "Database connection failed: %s\n", PQerrorMessage (db_conn)); + return 1; + } + + if (GNUNET_OK != TALER_MINT_init_withdraw_tables (db_conn)) + { + fprintf (stderr, "Failed to initialize database.\n"); + return 1; + } + + return 0; +} + diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c new file mode 100644 index 000000000..6d69813c0 --- /dev/null +++ b/src/mint/taler-mint-httpd.c @@ -0,0 +1,376 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd.c + * @brief Serve the HTTP interface of the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_deposit.h" +#include "taler-mint-httpd_withdraw.h" +#include "taler-mint-httpd_refresh.h" + + +/** + * Base directory of the mint (global) + */ +char *mintdir; + +/** + * The mint's configuration (global) + */ +struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Master public key (according to the + * configuration in the mint directory). + */ +struct GNUNET_CRYPTO_EddsaPublicKey master_pub; + +/** + * The HTTP Daemon. + */ +static struct MHD_Daemon *mydaemon; + +/** + * The kappa value for refreshing. + */ +static unsigned int refresh_security_parameter; + +/** + * Port to run the daemon on. + */ +static uint16_t serve_port; + + +/** + * Convert a string representing an EdDSA signature to an EdDSA + * signature. + * + * FIXME: this should be in GNUnet. + * FIXME: why? this code is dead, even here! + * + * @param enc encoded EdDSA signature + * @param enclen number of bytes in @a enc (without 0-terminator) + * @param pub where to store the EdDSA signature + * @return #GNUNET_OK on success + */ +int +TALER_eddsa_signature_from_string (const char *enc, + size_t enclen, + struct GNUNET_CRYPTO_EddsaSignature *sig) +{ + size_t keylen = (sizeof (struct GNUNET_CRYPTO_EddsaSignature)) * 8; + + if (keylen % 5 > 0) + keylen += 5 - keylen % 5; + keylen /= 5; + if (enclen != keylen) + return GNUNET_SYSERR; + + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, enclen, + sig, + sizeof (struct GNUNET_CRYPTO_EddsaSignature))) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Handle a request coming from libmicrohttpd. + * + * @param cls closure for MHD daemon (unused) + * @param connection the connection + * @param url the requested url + * @param method the method (POST, GET, ...) + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request (a `struct Buffer *`) + * @return MHD result code + */ +static int +handle_mhd_request (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + static struct RequestHandler handlers[] = + { + { "/", MHD_HTTP_METHOD_GET, "text/plain", + "Hello, I'm the mint\n", 0, + &TALER_MINT_handler_static_response, MHD_HTTP_OK }, + { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", + NULL, 0, + &TALER_MINT_handler_agpl_redirect, MHD_HTTP_FOUND }, + { "/keys", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_keys, MHD_HTTP_OK }, + { "/keys", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/withdraw/status", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_withdraw_status, MHD_HTTP_OK }, + { "/withdraw/status", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/withdraw/sign", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_withdraw_sign, MHD_HTTP_OK }, + { "/withdraw/sign", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_melt, MHD_HTTP_OK }, + { "/refresh/melt", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/commit", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_commit, MHD_HTTP_OK }, + { "/refresh/commit", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_melt, MHD_HTTP_OK }, + { "/refresh/reveal", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/link", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_link, MHD_HTTP_OK }, + { "/refresh/link", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/reveal", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_reveal, MHD_HTTP_OK }, + { "/refresh/reveal", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/deposit", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_deposit, MHD_HTTP_OK }, + { "/deposit", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { NULL, NULL, NULL, NULL, 0, 0 } + }; + static struct RequestHandler h404 = + { + "", NULL, "text/html", + "<html><title>404: not found</title></html>", 0, + &TALER_MINT_handler_static_response, MHD_HTTP_NOT_FOUND + }; + struct RequestHandler *rh; + unsigned int i; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling request for URL '%s'\n", + url); + for (i=0;NULL != handlers[i].url;i++) + { + rh = &handlers[i]; + if ( (0 == strcasecmp (url, + rh->url)) && + ( (NULL == rh->method) || + (0 == strcasecmp (method, + rh->method)) ) ) + return rh->handler (rh, + connection, + con_cls, + upload_data, + upload_data_size); + } + return TALER_MINT_handler_static_response (&h404, + connection, + con_cls, + upload_data, + upload_data_size); +} + + + +/** + * Load configuration parameters for the mint + * server into the corresponding global variables. + * + * @param param mint_directory the mint's directory + * @return GNUNET_OK on success + */ +static int +mint_serve_process_config (const char *mint_directory) +{ + unsigned long long port; + unsigned long long kappa; + char *master_pub_str; + char *db_cfg; + + cfg = TALER_MINT_config_load (mint_directory); + if (NULL == cfg) + { + fprintf (stderr, + "can't load mint configuration\n"); + return 1; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "master_pub", + &master_pub_str)) + { + fprintf (stderr, + "No master public key given in mint configuration."); + return GNUNET_NO; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (master_pub_str, + strlen (master_pub_str), + &master_pub)) + { + fprintf (stderr, + "Invalid master public key given in mint configuration."); + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "db", + &db_cfg)) + { + fprintf (stderr, + "invalid configuration: mint.db\n"); + return GNUNET_NO; + } + if (GNUNET_OK != + TALER_MINT_DB_init (db_cfg)) + { + fprintf (stderr, + "failed to initialize DB subsystem\n"); + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "mint", "port", + &port)) + { + fprintf (stderr, + "invalid configuration: mint.port\n"); + return GNUNET_NO; + } + + if ((port == 0) || (port > UINT16_MAX)) + { + fprintf (stderr, + "invalid configuration (value out of range): mint.port\n"); + return GNUNET_NO; + } + serve_port = port; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "mint", "refresh_security_parameter", + &kappa)) + { + fprintf (stderr, + "invalid configuration: mint.refresh_security_parameter\n"); + return GNUNET_NO; + } + refresh_security_parameter = kappa; + + return GNUNET_OK; +} + + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + GNUNET_GETOPT_OPTION_END + }; + int ret; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-serve", + "INFO", + NULL)); + if (GNUNET_GETOPT_run ("taler-mint-serve", + options, + argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, + "no mint dir given\n"); + return 1; + } + + if (GNUNET_OK != mint_serve_process_config (mintdir)) + return 1; + + + mydaemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, + serve_port, + NULL, NULL, + &handle_mhd_request, NULL, + MHD_OPTION_END); + + if (NULL == mydaemon) + { + fprintf (stderr, + "Failed to start MHD.\n"); + return 1; + } + + ret = TALER_MINT_key_reload_loop (); + MHD_stop_daemon (mydaemon); + return (GNUNET_OK == ret) ? 0 : 1; +} + diff --git a/src/mint/taler-mint-httpd.h b/src/mint/taler-mint-httpd.h new file mode 100644 index 000000000..59f38aadb --- /dev/null +++ b/src/mint/taler-mint-httpd.h @@ -0,0 +1,106 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd.h + * @brief Global declarations for the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_H +#define TALER_MINT_HTTPD_H + + +/** + * Cut-and-choose size for refreshing. + * FIXME: maybe make it a config option? + */ +#define KAPPA 3 + + +/** + * The mint's configuration. + */ +extern struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Main directory with mint data. + */ +extern char *mintdir; + +/** + * Master public key (according to the + * configuration in the mint directory). + */ +extern struct GNUNET_CRYPTO_EddsaPublicKey master_pub; + + +/** + * Struct describing an URL and the handler for it. + */ +struct RequestHandler +{ + + /** + * URL the handler is for. + */ + const char *url; + + /** + * Method the handler is for, NULL for "all". + */ + const char *method; + + /** + * Mime type to use in reply (hint, can be NULL). + */ + const char *mime_type; + + /** + * Raw data for the @e handler + */ + const void *data; + + /** + * Number of bytes in @e data, 0 for 0-terminated. + */ + size_t data_size; + + /** + * Function to call to handle the request. + * + * @param rh this struct + * @param mime_type the @e mime_type for the reply (hint, can be NULL) + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ + int (*handler)(struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + /** + * Default response code. + */ + int response_code; +}; + + +#endif diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c new file mode 100644 index 000000000..ecbc5c13b --- /dev/null +++ b/src/mint/taler-mint-httpd_deposit.c @@ -0,0 +1,270 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_deposit.c + * @brief Handle /deposit requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_deposit.h" + + +/** + * Send confirmation of deposit success to client. + * + * @param connection connection to the client + * @param deposit deposit request to confirm + * @return MHD result code + */ +static int +helper_deposit_send_response_success (struct MHD_Connection *connection, + struct Deposit *deposit) +{ + // FIXME: return more information here + return request_send_json_pack (connection, MHD_HTTP_OK, + "{s:s}", "status", "DEPOSIT_OK"); +} + + +/** + * Handle a "/deposit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_deposit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *json; + struct Deposit *deposit; + json_t *wire; + json_t *resp; + char *wire_enc = NULL; + const char *deposit_type; + struct MintKeyState *key_state; + struct TALER_CoinPublicInfo coin_info; + struct TALER_RSA_Signature ubsig; + size_t len; + int resp_code; + PGconn *db_conn; + int res; + + res = process_post_json (connection, + connection_cls, + upload_data, upload_data_size, + &json); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + deposit = NULL; + wire = NULL; + resp = NULL; + if (-1 == json_unpack (json, + "{s:s s:o}", + "type", &deposit_type, + "wire", &wire)) + { + GNUNET_break_op (0); + resp = json_pack ("{s:s}", "error", "Bad format"); + resp_code = MHD_HTTP_BAD_REQUEST; + goto EXITIF_exit; + } + if (NULL == (wire_enc = json_dumps (wire, JSON_COMPACT|JSON_SORT_KEYS))) + { + GNUNET_break_op (0); + resp = json_pack ("{s:s}", "error", "Bad format"); + resp_code = MHD_HTTP_BAD_REQUEST; + goto EXITIF_exit; + } + len = strlen (wire_enc) + 1; + deposit = GNUNET_malloc (sizeof (struct Deposit) + len); +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) +#define PARSE_DATA(field, addr) \ + EXITIF (GNUNET_OK != request_json_require_nav \ + (connection, json, \ + JNAV_FIELD, field, JNAV_RET_DATA, addr, sizeof (*addr))) + PARSE_DATA ("coin_pub", &deposit->coin_pub); + PARSE_DATA ("denom_pub", &deposit->denom_pub); + PARSE_DATA ("ubsig", &ubsig); + PARSE_DATA ("merchant_pub", &deposit->merchant_pub); + PARSE_DATA ("H_a", &deposit->h_contract); + PARSE_DATA ("H_wire", &deposit->h_wire); + PARSE_DATA ("csig", &deposit->coin_sig); + PARSE_DATA ("transaction_id", &deposit->transaction_id); +#undef PARSE_DATA + if (0 == strcmp ("DIRECT_DEPOSIT", deposit_type)) + deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT); + else if (0 == strcmp ("INCREMENTAL_DEPOSIT", deposit_type)) + deposit->purpose.purpose = htonl (TALER_SIGNATURE_INCREMENTAL_DEPOSIT); + else + { + GNUNET_break_op (0); + resp = json_pack ("{s:s}", "error", "Bad format"); + resp_code = MHD_HTTP_BAD_REQUEST; + goto EXITIF_exit; + } + deposit->purpose.size = htonl (sizeof (struct Deposit) + - offsetof (struct Deposit, purpose)); + memcpy (&coin_info.coin_pub, + &deposit->coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + coin_info.denom_pub = deposit->denom_pub; + coin_info.denom_sig = ubsig; + key_state = TALER_MINT_key_state_acquire (); + if (GNUNET_YES != TALER_MINT_test_coin_valid (key_state, + &coin_info)) + { + TALER_MINT_key_state_release (key_state); + resp = json_pack ("{s:s}", "error", "Coin is not valid"); + resp_code = MHD_HTTP_NOT_FOUND; + goto EXITIF_exit; + } + TALER_MINT_key_state_release (key_state); + /* + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_DEPOSIT, + &deposit->purpose, + &deposit->coin_sig, + &deposit->coin_pub)) + { + resp = json_pack ("{s:s}", "error", "Signature verfication failed"); + resp_code = MHD_HTTP_NOT_FOUND; + goto EXITIF_exit; + } + */ + + /* Check if we already received the same deposit permission, + * or the coin was already deposited */ + + { + struct Deposit *existing_deposit; + int res; + + res = TALER_MINT_DB_get_deposit (db_conn, + &deposit->coin_pub, + &existing_deposit); + if (GNUNET_YES == res) + { + // FIXME: memory leak + if (0 == memcmp (existing_deposit, deposit, sizeof (struct Deposit))) + return helper_deposit_send_response_success (connection, deposit); + // FIXME: in the future, check if there's enough credits + // left on the coin. For now: refuse + // FIXME: return more information here + return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN, + "{s:s}", + "error", "double spending"); + } + + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + { + struct KnownCoin known_coin; + int res; + + res = TALER_MINT_DB_get_known_coin (db_conn, &coin_info.coin_pub, &known_coin); + if (GNUNET_YES == res) + { + // coin must have been refreshed + // FIXME: check + // FIXME: return more information here + return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN, + "{s:s}", + "error", "coin was refreshed"); + } + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* coin valid but not known => insert into DB */ + known_coin.is_refreshed = GNUNET_NO; + known_coin.expended_balance = TALER_amount_ntoh (deposit->amount); + known_coin.public_info = coin_info; + + if (GNUNET_OK != TALER_MINT_DB_insert_known_coin (db_conn, &known_coin)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + if (GNUNET_OK != TALER_MINT_DB_insert_deposit (db_conn, deposit)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return helper_deposit_send_response_success (connection, deposit); + + EXITIF_exit: + if (NULL != resp) + res = send_response_json (connection, resp, resp_code); + else + res = MHD_NO; + if (NULL != wire) + json_decref (wire); + if (NULL != deposit) + GNUNET_free (deposit); + if (NULL != wire_enc) + GNUNET_free (wire_enc); + return res; +#undef EXITIF +#undef PARSE_DATA +} + +/* end of taler-mint-httpd_deposit.c */ diff --git a/src/mint/taler-mint-httpd_deposit.h b/src/mint/taler-mint-httpd_deposit.h new file mode 100644 index 000000000..dd7b8c133 --- /dev/null +++ b/src/mint/taler-mint-httpd_deposit.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_deposit.h + * @brief Handle /deposit requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_DEPOSIT_H +#define TALER_MINT_HTTPD_DEPOSIT_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Handle a "/deposit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_deposit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/mint/taler-mint-httpd_keys.c b/src/mint/taler-mint-httpd_keys.c new file mode 100644 index 000000000..ba023fe69 --- /dev/null +++ b/src/mint/taler-mint-httpd_keys.c @@ -0,0 +1,512 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_keys.c + * @brief Handle /keys requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" + + +/** + * Mint key state. Never use directly, instead access via + * #TALER_MINT_key_state_acquire and #TALER_MINT_key_state_release. + */ +static struct MintKeyState *internal_key_state; + +/** + * Mutex protecting access to #internal_key_state. + */ +static pthread_mutex_t internal_key_state_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** + * Pipe used for signaling reloading of our key state. + */ +static int reload_pipe[2]; + + +/** + * Convert the public part of a denomination key + * issue to a JSON object. + * + * @param dki the denomination key issue + * @return a JSON object describing the denomination key isue (public part) + */ +static json_t * +denom_key_issue_to_json (const struct TALER_MINT_DenomKeyIssue *dki) +{ + json_t *dk_json = json_object (); + json_object_set_new (dk_json, "master_sig", + TALER_JSON_from_data (&dki->signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); + json_object_set_new (dk_json, "stamp_start", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->start))); + json_object_set_new (dk_json, "stamp_expire_withdraw", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->expire_withdraw))); + json_object_set_new (dk_json, "stamp_expire_deposit", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->expire_spend))); + json_object_set_new (dk_json, "denom_pub", + TALER_JSON_from_data (&dki->denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))); + json_object_set_new (dk_json, "value", + TALER_JSON_from_amount (TALER_amount_ntoh (dki->value))); + json_object_set_new (dk_json, + "fee_withdraw", + TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_withdraw))); + json_object_set_new (dk_json, + "fee_deposit", + TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_deposit))); + json_object_set_new (dk_json, + "fee_refresh", + TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_refresh))); + return dk_json; +} + + +/** + * Convert the public part of a sign key + * issue to a JSON object. + * + * @param ski the sign key issue + * @return a JSON object describing the sign key isue (public part) + */ +static json_t * +sign_key_issue_to_json (const struct TALER_MINT_SignKeyIssue *ski) +{ + json_t *sk_json = json_object (); + json_object_set_new (sk_json, "stamp_start", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (ski->start))); + json_object_set_new (sk_json, "stamp_expire", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (ski->expire))); + json_object_set_new (sk_json, "master_sig", + TALER_JSON_from_data (&ski->signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); + json_object_set_new (sk_json, "key", + TALER_JSON_from_data (&ski->signkey_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))); + return sk_json; +} + + +/** + * Get the relative time value that describes how + * far in the future do we want to provide coin keys. + * + * @return the provide duration + */ +static struct GNUNET_TIME_Relative +TALER_MINT_conf_duration_provide () +{ + struct GNUNET_TIME_Relative rel; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + "mint_keys", + "lookahead_provide", + &rel)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "mint_keys.lookahead_provide not valid or not given\n"); + GNUNET_abort (); + } + return rel; +} + + +/** + * Iterator for denomination keys. + * + * @param cls closure + * @param dki the denomination key issue + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +reload_keys_denom_iter (void *cls, + const char *alias, + const struct TALER_MINT_DenomKeyIssue *dki) +{ + struct MintKeyState *ctx = cls; + struct GNUNET_TIME_Absolute stamp_provide; + struct GNUNET_HashCode denom_key_hash; + int res; + + stamp_provide = GNUNET_TIME_absolute_add (ctx->reload_time, + TALER_MINT_conf_duration_provide ()); + + if (GNUNET_TIME_absolute_ntoh (dki->expire_spend).abs_value_us < ctx->reload_time.abs_value_us) + { + // this key is expired + return GNUNET_OK; + } + if (GNUNET_TIME_absolute_ntoh (dki->start).abs_value_us > stamp_provide.abs_value_us) + { + // we are to early for this key + return GNUNET_OK; + } + + GNUNET_CRYPTO_hash (&dki->denom_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), &denom_key_hash); + + res = GNUNET_CONTAINER_multihashmap_put (ctx->denomkey_map, + &denom_key_hash, + GNUNET_memdup (dki, sizeof (struct TALER_MINT_DenomKeyIssue)), + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + if (GNUNET_OK != res) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Duplicate denomination key\n"); + + json_array_append_new (ctx->denom_keys_array, + denom_key_issue_to_json (dki)); + + return GNUNET_OK; +} + + +/** + * Iterator for sign keys. + * + * @param cls closure + * @param ski the sign key issue + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +reload_keys_sign_iter (void *cls, + const struct TALER_MINT_SignKeyIssue *ski) +{ + struct MintKeyState *ctx = cls; + struct GNUNET_TIME_Absolute stamp_provide; + + stamp_provide = GNUNET_TIME_absolute_add (ctx->reload_time, TALER_MINT_conf_duration_provide (cfg)); + + if (GNUNET_TIME_absolute_ntoh (ski->expire).abs_value_us < ctx->reload_time.abs_value_us) + { + // this key is expired + return GNUNET_OK; + } + + if (GNUNET_TIME_absolute_ntoh (ski->start).abs_value_us > stamp_provide.abs_value_us) + { + // we are to early for this key + return GNUNET_OK; + } + + // the signkey is valid for now, check + // if it's more recent than the current one! + if (GNUNET_TIME_absolute_ntoh (ctx->current_sign_key_issue.start).abs_value_us > + GNUNET_TIME_absolute_ntoh (ski->start).abs_value_us) + ctx->current_sign_key_issue = *ski; + + + ctx->next_reload = GNUNET_TIME_absolute_min (ctx->next_reload, + GNUNET_TIME_absolute_ntoh (ski->expire)); + + json_array_append_new (ctx->sign_keys_array, + sign_key_issue_to_json (ski)); + + return GNUNET_OK; +} + + +/** + * Load the mint's key state from disk. + * + * @return fresh key state (with reference count 1) + */ +static struct MintKeyState * +reload_keys () +{ + struct MintKeyState *key_state; + json_t *keys; + + key_state = GNUNET_new (struct MintKeyState); + key_state->refcnt = 1; + + key_state->next_reload = GNUNET_TIME_UNIT_FOREVER_ABS; + + key_state->denom_keys_array = json_array (); + GNUNET_assert (NULL != key_state->denom_keys_array); + + key_state->sign_keys_array = json_array (); + GNUNET_assert (NULL != key_state->sign_keys_array); + + key_state->denomkey_map = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO); + GNUNET_assert (NULL != key_state->denomkey_map); + + key_state->reload_time = GNUNET_TIME_absolute_get (); + + TALER_MINT_denomkeys_iterate (mintdir, &reload_keys_denom_iter, key_state); + TALER_MINT_signkeys_iterate (mintdir, &reload_keys_sign_iter, key_state); + + keys = json_pack ("{s:o, s:o, s:o, s:o}", + "master_pub", TALER_JSON_from_data (&master_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)), + "signkeys", key_state->sign_keys_array, + "denoms", key_state->denom_keys_array, + "list_issue_date", TALER_JSON_from_abs (key_state->reload_time)); + + key_state->keys_json = json_dumps (keys, JSON_INDENT(2)); + + return key_state; +} + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * + * @param key_state the key state to release + */ +void +TALER_MINT_key_state_release (struct MintKeyState *key_state) +{ + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + GNUNET_assert (0 != key_state->refcnt); + key_state->refcnt += 1; + if (key_state->refcnt == 0) { + GNUNET_free (key_state); + } + GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); +} + + +/** + * Acquire the key state of the mint. Updates keys if necessary. + * For every call to #TALER_MINT_key_state_acquire, a matching call + * to #TALER_MINT_key_state_release must be made. + * + * @return the key state + */ +struct MintKeyState * +TALER_MINT_key_state_acquire (void) +{ + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct MintKeyState *key_state; + + // FIXME: the locking we have is very coarse-grained, + // using multiple locks might be nicer ... + + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + if (NULL == internal_key_state) + { + internal_key_state = reload_keys (); + } + else if (internal_key_state->next_reload.abs_value_us <= now.abs_value_us) + { + GNUNET_assert (0 != internal_key_state->refcnt); + internal_key_state->refcnt--; + if (0 == internal_key_state->refcnt) + GNUNET_free (internal_key_state); + internal_key_state = reload_keys (); + } + key_state = internal_key_state; + key_state->refcnt += 1; + GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); + + return key_state; +} + + +/** + * Look up the issue for a denom public key. + * + * @param key state to look in + * @param denom_pub denomination public key + * @return the denomination key issue, + * or NULL if denom_pub could not be found + */ +struct TALER_MINT_DenomKeyIssue * +TALER_MINT_get_denom_key (const struct MintKeyState *key_state, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + struct TALER_MINT_DenomKeyIssue *issue; + struct GNUNET_HashCode hash; + + GNUNET_CRYPTO_hash (denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded), &hash); + issue = GNUNET_CONTAINER_multihashmap_get (key_state->denomkey_map, &hash); + return issue; +} + + +/** + * Check if a coin is valid; that is, whether the denomination key exists, + * is not expired, and the signature is correct. + * + * @param key_state the key state to use for checking the coin's validity + * @param coin_public_info the coin public info to check for validity + * @return GNUNET_YES if the coin is valid, + * GNUNET_NO if it is invalid + * GNUNET_SYSERROR if an internal error occured + */ +int +TALER_MINT_test_coin_valid (const struct MintKeyState *key_state, + struct TALER_CoinPublicInfo *coin_public_info) +{ + struct TALER_MINT_DenomKeyIssue *dki; + + dki = TALER_MINT_get_denom_key (key_state, &coin_public_info->denom_pub); + if (NULL == dki) + return GNUNET_NO; + if (GNUNET_OK != TALER_RSA_verify (&coin_public_info->coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &coin_public_info->denom_sig, + &dki->denom_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "coin signature is invalid\n"); + return GNUNET_NO; + } + return GNUNET_YES; +} + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_keys (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MintKeyState *key_state; + struct MHD_Response *response; + int ret; + + key_state = TALER_MINT_key_state_acquire (); + response = MHD_create_response_from_buffer (strlen (key_state->keys_json), + key_state->keys_json, + MHD_RESPMEM_MUST_COPY); + TALER_MINT_key_state_release (key_state); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + (void) MHD_add_response_header (response, + "Content-Type", + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Handle a signal, writing relevant signal numbers + * (currently just SIGUSR1) to a pipe. + * + * @param signal_number the signal number + */ +static void +handle_signal (int signal_number) +{ + size_t res; + char c = signal_number; + + if (SIGUSR1 == signal_number) + { + errno = 0; + res = write (reload_pipe[1], &c, 1); + if ((res < 0) && (EINTR != errno)) + { + GNUNET_break (0); + return; + } + if (0 == res) + { + GNUNET_break (0); + return; + } + } +} + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is read from the pipe. + */ +int +TALER_MINT_key_reload_loop (void) +{ + struct sigaction act; + + if (0 != pipe (reload_pipe)) + { + fprintf (stderr, + "Failed to create pipe.\n"); + return GNUNET_SYSERR; + } + memset (&act, 0, sizeof (struct sigaction)); + act.sa_handler = &handle_signal; + + if (0 != sigaction (SIGUSR1, &act, NULL)) + { + fprintf (stderr, + "Failed to set signal handler.\n"); + return GNUNET_SYSERR; + } + + while (1) + { + char c; + ssize_t res; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "(re-)loading keys\n"); + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + if (NULL != internal_key_state) + { + GNUNET_assert (0 != internal_key_state->refcnt); + internal_key_state->refcnt -= 1; + if (0 == internal_key_state->refcnt) + GNUNET_free (internal_key_state); + } + internal_key_state = reload_keys (); + GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); +read_again: + errno = 0; + res = read (reload_pipe[0], &c, 1); + if ((res < 0) && (EINTR != errno)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (EINTR == errno) + goto read_again; + } + return GNUNET_OK; +} + + +/* end of taler-mint-httpd_keys.c */ diff --git a/src/mint/taler-mint-httpd_keys.h b/src/mint/taler-mint-httpd_keys.h new file mode 100644 index 000000000..640a9c916 --- /dev/null +++ b/src/mint/taler-mint-httpd_keys.h @@ -0,0 +1,155 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_keys.h + * @brief Handle /keys requests and manage key state + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_KEYS_H +#define TALER_MINT_HTTPD_KEYS_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Snapshot of the (coin and signing) + * keys (including private keys) of the mint. + */ +struct MintKeyState +{ + /** + * When did we initiate the key reloading? + */ + struct GNUNET_TIME_Absolute reload_time; + + /** + * JSON array with denomination keys. + */ + json_t *denom_keys_array; + + /** + * JSON array with signing keys. + */ + json_t *sign_keys_array; + + /** + * Mapping from denomination keys to denomination key issue struct. + */ + struct GNUNET_CONTAINER_MultiHashMap *denomkey_map; + + /** + * When is the next key invalid and we have to reload? + */ + struct GNUNET_TIME_Absolute next_reload; + + /** + * Mint signing key that should be used currently. + */ + struct TALER_MINT_SignKeyIssue current_sign_key_issue; + + /** + * Cached JSON text that the mint will send for + * a /keys request. + */ + char *keys_json; + + /** + * Reference count. + */ + unsigned int refcnt; +}; + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * + * @param key_state the key state to release + */ +void +TALER_MINT_key_state_release (struct MintKeyState *key_state); + + +/** + * Acquire the key state of the mint. Updates keys if necessary. + * For every call to #TALER_MINT_key_state_acquire, a matching call + * to #TALER_MINT_key_state_release must be made. + * + * @return the key state + */ +struct MintKeyState * +TALER_MINT_key_state_acquire (void); + + +/** + * Look up the issue for a denom public key. + * + * @param key state to look in + * @param denom_pub denomination public key + * @return the denomination key issue, + * or NULL if denom_pub could not be found + */ +struct TALER_MINT_DenomKeyIssue * +TALER_MINT_get_denom_key (const struct MintKeyState *key_state, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + + +/** + * Check if a coin is valid; that is, whether the denomination key exists, + * is not expired, and the signature is correct. + * + * @param key_state the key state to use for checking the coin's validity + * @param coin_public_info the coin public info to check for validity + * @return GNUNET_YES if the coin is valid, + * GNUNET_NO if it is invalid + * GNUNET_SYSERROR if an internal error occured + */ +int +TALER_MINT_test_coin_valid (const struct MintKeyState *key_state, + struct TALER_CoinPublicInfo *coin_public_info); + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is read from the pipe. + * + * @return GNUNET_OK if we terminated normally, GNUNET_SYSERR on error + */ +int +TALER_MINT_key_reload_loop (void); + + +/** + * Handle a "/keys" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_keys (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/mint/taler-mint-httpd_mhd.c b/src/mint/taler-mint-httpd_mhd.c new file mode 100644 index 000000000..09f3025b8 --- /dev/null +++ b/src/mint/taler-mint-httpd_mhd.c @@ -0,0 +1,300 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.c + * @brief helpers for MHD interaction + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd.h" +#include "taler-mint-httpd_mhd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_static_response (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MHD_Response *response; + int ret; + + if (0 == rh->data_size) + rh->data_size = strlen ((const char *) rh->data); + response = MHD_create_response_from_buffer (rh->data_size, + (void *) rh->data, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_agpl_redirect (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + "http://www.git.taler.net/?p=mint.git"); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TALER_MINT_helper_send_json_pack (struct RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...) +{ + int ret; + json_t *json; + va_list argp; + char *json_str; + struct MHD_Response *response; + + va_start (argp, fmt); + json = json_vpack_ex (NULL, 0, fmt, argp); + va_end (argp); + if (NULL == json) + return MHD_NO; + json_str = json_dumps (json, JSON_INDENT(2)); + json_decref (json); + if (NULL == json_str) + return MHD_NO; + response = MHD_create_response_from_buffer (strlen (json_str), + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == response) + { + free (json_str); + return MHD_NO; + } + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_send_json_pack_error (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + return TALER_MINT_helper_send_json_pack (rh, + connection, + connection_cls, + 1, /* caching enabled */ + rh->response_code, + "{s:s}", + "error", + rh->data); +} + + +/** + * Send a response for an invalid argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is missing + * @return a GNUnet result code + */ +static int +request_arg_invalid (struct MHD_Connection *connection, + const char *param_name) +{ + json_t *json; + json = json_pack ("{ s:s, s:s }", + "error", "invalid parameter", + "parameter", param_name); + if (MHD_YES != send_response_json (connection, json, MHD_HTTP_BAD_REQUEST)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Get a GET paramater that is a string, + * or send an error response if the parameter is missing. + * + * @param connection the connection to get the parameter from / + * send the error response to + * @param param_name the parameter name + * @param str pointer to store the parameter string, + * must be freed by the caller + * @return GNUNET_YES if the parameter is present and valid, + * GNUNET_NO if the parameter is missing + * GNUNET_SYSERR on internal error + */ +static int +request_arg_require_string (struct MHD_Connection *connection, + const char *param_name, + const char **str) +{ + *str = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, param_name); + if (NULL == *str) + { + if (MHD_NO == + request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + "{ s:s, s:s }", + "error", "missing parameter", + "parameter", param_name)) + return GNUNET_SYSERR; + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Extraxt base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + * GNUNET_YES if the the argument is present + * GNUNET_NO if the argument is absent or malformed + * GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size) +{ + const char *str; + int ret; + + if (GNUNET_OK != (ret = request_arg_require_string (connection, param_name, &str))) + return ret; + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (str, strlen (str), out_data, out_size)) + return request_arg_invalid (connection, param_name); + return GNUNET_OK; +} + + + +/* end of taler-mint-httpd_mhd.c */ diff --git a/src/mint/taler-mint-httpd_mhd.h b/src/mint/taler-mint-httpd_mhd.h new file mode 100644 index 000000000..29ab7f64b --- /dev/null +++ b/src/mint/taler-mint-httpd_mhd.h @@ -0,0 +1,132 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.h + * @brief helpers for MHD interaction + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_MHD_H +#define TALER_MINT_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_static_response (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_agpl_redirect (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TALER_MINT_helper_send_json_pack (struct RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...); + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_send_json_pack_error (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Extraxt base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + * GNUNET_YES if the the argument is present + * GNUNET_NO if the argument is absent or malformed + * GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size); + +#endif diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c new file mode 100644 index 000000000..8121bb234 --- /dev/null +++ b/src/mint/taler-mint-httpd_refresh.c @@ -0,0 +1,1497 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_refresh.c + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_refresh.h" + + +/** + * Sign the message in @a purpose with the mint's signing + * key and encode the signature as a JSON object. + * + * @param purpose the message to sign + * @return signature as JSON object + */ +static json_t * +sign_as_json (struct GNUNET_CRYPTO_EccSignaturePurpose *purpose) +{ + json_t *sig_json; + struct GNUNET_CRYPTO_EddsaSignature sig; + struct MintKeyState *key_state; + + key_state = TALER_MINT_key_state_acquire (); + + sig_json = json_object (); + + GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv, + purpose, + &sig)); + + TALER_MINT_key_state_release (key_state); + + json_object_set (sig_json, "sig", TALER_JSON_from_data (&sig, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); + json_object_set (sig_json, "purpose", json_integer (ntohl (purpose->purpose))); + json_object_set (sig_json, "size", json_integer (ntohl (purpose->size))); + + return sig_json; +} + + +static int +link_iter (void *cls, + const struct LinkDataEnc *link_data_enc, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub, + const struct TALER_RSA_Signature *ev_sig) +{ + json_t *list = cls; + json_t *obj = json_object (); + + json_array_append_new (list, obj); + + json_object_set_new (obj, "link_enc", + TALER_JSON_from_data (link_data_enc, + sizeof (struct LinkDataEnc))); + + json_object_set_new (obj, "denom_pub", + TALER_JSON_from_data (denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))); + + json_object_set_new (obj, "ev_sig", + TALER_JSON_from_data (ev_sig, + sizeof (struct TALER_RSA_Signature))); + + return GNUNET_OK; +} + + +/** + * Insert all requested denominations into the db, and compute the + * required cost of the denominations, including fees. + * + * @param connection the connection to send an error response to + * @param db_conn the database connection + * @param key_state the mint's key state to use + * @param session_pub the refresh session public key + * @param root the request JSON object + * @param hash_context the hash context where accepted + * denominations will be hased into + * @param r_amount the sum of the cost (value+fee) for + * all requested coins + * @return FIXME! + */ +static int +refresh_accept_denoms (struct MHD_Connection *connection, + PGconn *db_conn, + const struct MintKeyState *key_state, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const json_t *root, + struct TALER_HashContext *hash_context, + struct TALER_Amount *r_amount) +{ + unsigned i; + int res; + json_t *new_denoms; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "new_denoms", + JNAV_RET_TYPED_JSON, + JSON_ARRAY, + &new_denoms); + if (GNUNET_OK != res) + return res; + + memset (r_amount, 0, sizeof (struct TALER_Amount)); + + for (i = 0; i < json_array_size (new_denoms); i++) + { + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + int res; + struct TALER_MINT_DenomKeyIssue *dki; + struct TALER_Amount cost; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "new_denoms", + JNAV_INDEX, (int) i, + JNAV_RET_DATA, + &denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + + if (GNUNET_OK != res) + return res; + + dki = TALER_MINT_get_denom_key (key_state, &denom_pub); + + TALER_hash_context_read (hash_context, + &denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + + cost = TALER_amount_add (TALER_amount_ntoh (dki->value), + TALER_amount_ntoh (dki->fee_withdraw)); + + *r_amount = TALER_amount_add (cost, *r_amount); + + /* Insert the requested coin into the DB, so we'll know later + * what denomination the request had */ + + if (GNUNET_OK != + TALER_MINT_DB_insert_refresh_order (db_conn, + i, + session_pub, + &denom_pub)) + return res; // ??? + } + return GNUNET_OK; +} + + +/** + * Get an amount in the mint's currency + * that is zero. + * + * @return zero amount in the mint's currency + */ +static struct TALER_Amount +mint_amount_native_zero () +{ + struct TALER_Amount amount; + + memset (&amount, 0, sizeof (amount)); + // FIXME: load from config + memcpy (amount.currency, "EUR", 3); + + return amount; +} + + +static int +check_confirm_signature (struct MHD_Connection *connection, + json_t *coin_info, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + struct RefreshMeltConfirmSignRequestBody body; + struct GNUNET_CRYPTO_EcdsaSignature sig; + int res; + + body.purpose.size = htonl (sizeof (struct RefreshMeltConfirmSignRequestBody)); + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_CONFIRM); + body.session_pub = *session_pub; + + res = request_json_require_nav (connection, coin_info, + JNAV_FIELD, "confirm_sig", + JNAV_RET_DATA, + &sig, + sizeof (struct GNUNET_CRYPTO_EcdsaSignature)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + if (GNUNET_OK != + GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_REFRESH_MELT_CONFIRM, + &body.purpose, + &sig, + coin_pub)) + { + if (MHD_YES != + request_send_json_pack (connection, + MHD_HTTP_UNAUTHORIZED, + "{s:s}", + "error", "signature invalid")) + return GNUNET_SYSERR; + return GNUNET_NO; + } + + return GNUNET_OK; +} + + +/** + * Extract public coin information from a JSON object. + * + * @param connection the connection to send error responses to + * @param root the JSON object to extract the coin info from + * @return GNUNET_YES if coin public info in JSON was valid + * GNUNET_NO otherwise + * GNUNET_SYSERR on internal error + */ +static int +request_json_require_coin_public_info (struct MHD_Connection *connection, + json_t *root, + struct TALER_CoinPublicInfo *r_public_info) +{ + int ret; + + GNUNET_assert (NULL != root); + + ret = request_json_require_nav (connection, root, + JNAV_FIELD, "coin_pub", + JNAV_RET_DATA, + &r_public_info->coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + if (GNUNET_OK != ret) + return ret; + + ret = request_json_require_nav (connection, root, + JNAV_FIELD, "denom_sig", + JNAV_RET_DATA, + &r_public_info->denom_sig, + sizeof (struct TALER_RSA_Signature)); + if (GNUNET_OK != ret) + return ret; + + ret = request_json_require_nav (connection, root, + JNAV_FIELD, "denom_pub", + JNAV_RET_DATA, + &r_public_info->denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + if (GNUNET_OK != ret) + return ret; + + return GNUNET_OK; +} + + +/** + * Parse coin melt requests from a JSON object and write them to + * the database. + * + * @param connection the connection to send errors to + * @param db_conn the database connection + * @param key_state the mint's key state + * @param session_pub the refresh session's public key + * @param root the JSON object + * @param hash_context the hash context that will receive + * the coin public keys of the melted coin + * @return a GNUnet result code, GNUNET_OK on success, + * GNUNET_NO if an error message was generated, + * GNUNET_SYSERR on internal errors (no response generated) + */ +static int +refresh_accept_melts (struct MHD_Connection *connection, + PGconn *db_conn, + const struct MintKeyState *key_state, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + json_t *root, + struct TALER_HashContext *hash_context, + struct TALER_Amount *r_melt_balance) +{ + size_t i; + int res; + json_t *melt_coins; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "melt_coins", + JNAV_RET_TYPED_JSON, + JSON_ARRAY, + &melt_coins); + if (GNUNET_OK != res) + return res; + + memset (r_melt_balance, 0, sizeof (struct TALER_Amount)); + + for (i = 0; i < json_array_size (melt_coins); i++) + { + struct TALER_CoinPublicInfo coin_public_info; + struct TALER_MINT_DenomKeyIssue *dki; + struct KnownCoin known_coin; + // money the customer gets by melting the current coin + struct TALER_Amount coin_gain; + + res = request_json_require_coin_public_info (connection, + json_array_get (melt_coins, i), + &coin_public_info); + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + if (GNUNET_OK != (res = check_confirm_signature (connection, + json_array_get (melt_coins, i), + &coin_public_info.coin_pub, + session_pub))) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + TALER_hash_context_read (hash_context, + &coin_public_info.coin_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + + dki = TALER_MINT_get_denom_key (key_state, &coin_public_info.denom_pub); + + if (NULL == dki) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "denom not found")) + ? GNUNET_NO : GNUNET_SYSERR; + + if (GNUNET_OK != TALER_MINT_test_coin_valid (key_state, &coin_public_info)) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "coin invalid")) + ? GNUNET_NO : GNUNET_SYSERR; + + res = TALER_MINT_DB_get_known_coin (db_conn, &coin_public_info.coin_pub, + &known_coin); + + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_YES == res) + { + if (GNUNET_YES == known_coin.is_refreshed) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "coin already refreshed")) ? GNUNET_NO : GNUNET_SYSERR; + } + else + { + known_coin.expended_balance = mint_amount_native_zero (); + known_coin.public_info = coin_public_info; + } + + known_coin.is_refreshed = GNUNET_YES; + known_coin.refresh_session_pub = *session_pub; + + if (GNUNET_OK != TALER_MINT_DB_upsert_known_coin (db_conn, &known_coin)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != TALER_MINT_DB_insert_refresh_melt (db_conn, session_pub, i, + &coin_public_info.coin_pub, + &coin_public_info.denom_pub)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + coin_gain = TALER_amount_ntoh (dki->value); + coin_gain = TALER_amount_subtract (coin_gain, known_coin.expended_balance); + + /* Refuse to refresh when the coin does not have enough money left to + * pay the refreshing fees of the coin. */ + + if (TALER_amount_cmp (coin_gain, TALER_amount_ntoh (dki->fee_refresh)) < 0) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "depleted")) ? GNUNET_NO : GNUNET_SYSERR; + + coin_gain = TALER_amount_subtract (coin_gain, TALER_amount_ntoh (dki->fee_refresh)); + + *r_melt_balance = TALER_amount_add (*r_melt_balance, coin_gain); + } + return GNUNET_OK; +} + + +/** + * Send a response for "/refresh/melt". + * + * @param connection the connection to send the response to + * @param db_conn the database connection to fetch values from + * @param session_pub the refresh session public key. + * @return a MHD result code + */ +static int +helper_refresh_send_melt_response (struct MHD_Connection *connection, + PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + struct RefreshSession session; + int res; + json_t *root; + json_t *list; + struct TALER_HashContext hash_context; + + if (GNUNET_OK != + (res = TALER_MINT_DB_get_refresh_session (db_conn, + session_pub, + &session))) + { + // FIXME: send internal error + GNUNET_break (0); + return MHD_NO; + } + + root = json_object (); + list = json_array (); + json_object_set_new (root, "blind_session_pubs", list); + + TALER_hash_context_start (&hash_context); + + { + struct RefreshMeltResponseSignatureBody body; + json_t *sig_json; + + body.purpose.size = htonl (sizeof (struct RefreshMeltResponseSignatureBody)); + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_RESPONSE); + TALER_hash_context_finish (&hash_context, &body.melt_response_hash); + sig_json = sign_as_json (&body.purpose); + GNUNET_assert (NULL != sig_json); + json_object_set (root, "signature", sig_json); + } + + return send_response_json (connection, + root, + MHD_HTTP_OK); +} + + +/** + * Verify a signature that is encoded in a JSON object + * + * @param connection the connection to send errors to + * @param root the JSON object with the signature + * @param the public key that the signature was created with + * @param purpose the signed message + * @return GNUNET_YES if the signature was valid + * GNUNET_NO if the signature was invalid + * GNUNET_SYSERR on internal error + */ +static int +request_json_check_signature (struct MHD_Connection *connection, + json_t *root, + struct GNUNET_CRYPTO_EddsaPublicKey *pub, + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose) +{ + struct GNUNET_CRYPTO_EddsaSignature signature; + int size; + uint32_t purpose_num; + int res; + json_t *el; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "sig", + JNAV_RET_DATA, + &signature, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + + if (GNUNET_OK != res) + return res; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "purpose", + JNAV_RET_TYPED_JSON, + JSON_INTEGER, + &el); + + if (GNUNET_OK != res) + return res; + + purpose_num = json_integer_value (el); + + if (purpose_num != ntohl (purpose->purpose)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (purpose wrong)\n"); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "signature invalid (purpose)"); + } + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "size", + JNAV_RET_TYPED_JSON, + JSON_INTEGER, + &el); + + if (GNUNET_OK != res) + return res; + + size = json_integer_value (el); + + if (size != ntohl (purpose->size)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (size wrong)\n"); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + GNUNET_NO, GNUNET_SYSERR, + "{s:s}", + "error", "signature invalid (size)"); + } + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (purpose_num, + purpose, + &signature, + pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (did not verify)\n"); + return request_send_json_pack (connection, MHD_HTTP_UNAUTHORIZED, + "{s:s}", + "error", "invalid signature (verification)"); + } + + return GNUNET_OK; +} + + +/** + * Handle a "/refresh/melt" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_melt (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *root; + PGconn *db_conn; + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; + int res; + struct MintKeyState *key_state; + struct TALER_Amount requested_cost; + struct TALER_Amount melt_balance; + struct TALER_HashContext hash_context; + struct GNUNET_HashCode melt_hash; + + res = process_post_json (connection, + connection_cls, + upload_data, + upload_data_size, &root); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + return GNUNET_SYSERR; + + /* session_pub field must always be present */ + res = request_json_require_nav (connection, root, + JNAV_FIELD, "session_pub", + JNAV_RET_DATA, + &refresh_session_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + /* Send response immediately if we already know the session. + * Do _not_ care about fields other than session_pub in this case. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, + &refresh_session_pub, + NULL); + if (GNUNET_YES == res) + return helper_refresh_send_melt_response (connection, + db_conn, + &refresh_session_pub); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + /* We incrementally update the db with other parameters in a transaction. + * The transaction is aborted if some parameter does not validate. */ + + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + if (GNUNET_OK != TALER_MINT_DB_create_refresh_session (db_conn, + &refresh_session_pub)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + TALER_MINT_DB_rollback (db_conn); + return MHD_NO; + } + + /* The next two operations must see the same key state, + * thus we acquire it here. */ + + key_state = TALER_MINT_key_state_acquire (); + + /* Write requested denominations to the DB, + * and sum the costs (value plus fees) */ + + TALER_hash_context_start (&hash_context); + + if (GNUNET_OK != (res = refresh_accept_denoms (connection, db_conn, key_state, + &refresh_session_pub, root, + &hash_context, + &requested_cost))) + { + TALER_MINT_key_state_release (key_state); + TALER_MINT_DB_rollback (db_conn); + // FIXME: hash_context_end? + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + /* Write old coins to db and sum their value */ + + if (GNUNET_OK != (res = refresh_accept_melts (connection, db_conn, key_state, + &refresh_session_pub, root, + &hash_context, + &melt_balance))) + { + TALER_MINT_key_state_release (key_state); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_finish (&hash_context, &melt_hash); + + TALER_MINT_key_state_release (key_state); + + /* check that signature from the session public key is ok */ + { + struct RefreshMeltSignatureBody body; + json_t *melt_sig_json; + + melt_sig_json = json_object_get (root, "melt_signature"); + if (NULL == melt_sig_json) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return request_send_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "melt_signature missing"); + } + + body.melt_hash = melt_hash; + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT); + body.purpose.size = htonl (sizeof (struct RefreshMeltSignatureBody)); + + if (GNUNET_OK != (res = request_json_check_signature (connection, + melt_sig_json, + &refresh_session_pub, + &body.purpose))) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + + + /* Request is only ok if cost of requested coins + * does not exceed value of melted coins. */ + + // FIXME: also, consider fees? + if (TALER_amount_cmp (melt_balance, requested_cost) < 0) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + + return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN, + "{s:s}", + "error", "not enough coins melted"); + } + + if (GNUNET_OK != TALER_MINT_DB_commit (db_conn)) + { + GNUNET_break (0); + return MHD_NO; + } + return helper_refresh_send_melt_response (connection, + db_conn, + &refresh_session_pub); +} + + +/** + * Send a response to a "/refresh/commit" request. + * + * @param connection the connection to send the response to + * @param db_conn the mint database + * @param refresh_session the refresh session + * @return a MHD status code + */ +static int +refresh_send_commit_response (struct MHD_Connection *connection, + PGconn *db_conn, + struct RefreshSession *refresh_session) +{ + struct RefreshCommitResponseSignatureBody body; + json_t *sig_json; + + body.purpose.size = htonl (sizeof (struct RefreshCommitResponseSignatureBody)); + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_COMMIT_RESPONSE); + body.noreveal_index = htons (refresh_session->noreveal_index); + sig_json = sign_as_json (&body.purpose); + GNUNET_assert (NULL != sig_json); + return request_send_json_pack (connection, MHD_HTTP_OK, + "{s:i, s:o}", + "noreveal_index", (int) refresh_session->noreveal_index, + "signature", sig_json); +} + + +/** + * Handle a "/refresh/commit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_commit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; + int res; + PGconn *db_conn; + struct RefreshSession refresh_session; + int i; + struct GNUNET_HashCode commit_hash; + struct TALER_HashContext hash_context; + json_t *root; + + res = process_post_json (connection, + connection_cls, + upload_data, + upload_data_size, &root); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "session_pub", + JNAV_RET_DATA, + &refresh_session_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + /* Send response immediately if we already know the session. + * Do _not_ care about fields other than session_pub in this case. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, + &refresh_session_pub, + &refresh_session); + if ( (GNUNET_YES == res) && + (GNUNET_YES == refresh_session.has_commit_sig) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "sending cached commit response\n"); + res = refresh_send_commit_response (connection, + db_conn, + &refresh_session); + GNUNET_break (res != GNUNET_SYSERR); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + /* Re-fetch the session information from the database, + * in case a concurrent transaction modified it. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, + &refresh_session_pub, + &refresh_session); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return MHD_NO; + } + + TALER_hash_context_start (&hash_context); + + for (i = 0; i < refresh_session.kappa; i++) + { + unsigned int j; + + for (j = 0; j < refresh_session.num_newcoins; j++) + { + struct RefreshCommitCoin commit_coin; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "coin_evs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "link_encs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + commit_coin.link_enc, + TALER_REFRESH_LINK_LENGTH); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + commit_coin.link_enc, + TALER_REFRESH_LINK_LENGTH); + + commit_coin.cnc_index = i; + commit_coin.newcoin_index = j; + commit_coin.session_pub = refresh_session_pub; + + if (GNUNET_OK != + TALER_MINT_DB_insert_refresh_commit_coin (db_conn, + &commit_coin)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return MHD_NO; + } + } + } + + for (i = 0; i < refresh_session.kappa; i++) + { + unsigned int j; + for (j = 0; j < refresh_session.num_oldcoins; j++) + { + struct RefreshCommitLink commit_link; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "transfer_pubs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + &commit_link.transfer_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + &commit_link.transfer_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "secret_encs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + commit_link.shared_secret_enc, + TALER_REFRESH_SHARED_SECRET_LENGTH); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + commit_link.shared_secret_enc, + TALER_REFRESH_SHARED_SECRET_LENGTH); + + commit_link.cnc_index = i; + commit_link.oldcoin_index = j; + commit_link.session_pub = refresh_session_pub; + + if (GNUNET_OK != TALER_MINT_DB_insert_refresh_commit_link (db_conn, &commit_link)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return MHD_NO; + } + } + } + + TALER_hash_context_finish (&hash_context, &commit_hash); + + { + struct RefreshCommitSignatureBody body; + json_t *commit_sig_json; + + commit_sig_json = json_object_get (root, "commit_signature"); + if (NULL == commit_sig_json) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "commit_signature missing"); + } + + body.commit_hash = commit_hash; + + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_COMMIT); + body.purpose.size = htonl (sizeof (struct RefreshCommitSignatureBody)); + + if (GNUNET_OK != (res = request_json_check_signature (connection, + commit_sig_json, + &refresh_session_pub, + &body.purpose))) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + + if (GNUNET_OK != TALER_MINT_DB_commit (db_conn)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + return refresh_send_commit_response (connection, db_conn, &refresh_session); +} + + +/** + * Send response for "/refresh/reveal". + * + * @param connection the MHD connection + * @param db_conn the connection to the mint's db + * @param refresh_session_pub the refresh session's public key + * @return a MHD result code + */ +static int +helper_refresh_reveal_send_response (struct MHD_Connection *connection, + PGconn *db_conn, + struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub) +{ + int res; + int newcoin_index; + struct RefreshSession refresh_session; + json_t *root; + json_t *list; + + res = TALER_MINT_DB_get_refresh_session (db_conn, + refresh_session_pub, + &refresh_session); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + GNUNET_assert (0 != refresh_session.reveal_ok); + + root = json_object (); + list = json_array (); + json_object_set_new (root, "ev_sigs", list); + + for (newcoin_index = 0; newcoin_index < refresh_session.num_newcoins; newcoin_index++) + { + struct TALER_RSA_Signature ev_sig; + + res = TALER_MINT_DB_get_refresh_collectable (db_conn, + newcoin_index, + refresh_session_pub, + &ev_sig); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + json_array_append_new (list, + TALER_JSON_from_data (&ev_sig, + sizeof (struct TALER_RSA_Signature))); + } + return send_response_json (connection, + root, + MHD_HTTP_OK); +} + + +/** + * Handle a "/refresh/reveal" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_reveal (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; + int res; + PGconn *db_conn; + struct RefreshSession refresh_session; + struct MintKeyState *key_state; + int i; + int j; + json_t *root; + + res = process_post_json (connection, + connection_cls, + upload_data, upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "session_pub", + JNAV_RET_DATA, + &refresh_session_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + GNUNET_break (0); + return MHD_NO; + } + + /* Send response immediately if we already know the session, + * and the session commited already. + * Do _not_ care about fields other than session_pub in this case. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, &refresh_session_pub, &refresh_session); + if (GNUNET_YES == res && 0 != refresh_session.reveal_ok) + return helper_refresh_reveal_send_response (connection, db_conn, &refresh_session_pub); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + + /* Check that the transfer private keys match their commitments. + * Then derive the shared secret for each kappa, and check that they match. */ + + for (i = 0; i < refresh_session.kappa; i++) + { + struct GNUNET_HashCode last_shared_secret; + int secret_initialized = GNUNET_NO; + + if (i == (refresh_session.noreveal_index % refresh_session.kappa)) + continue; + + for (j = 0; j < refresh_session.num_oldcoins; j++) + { + struct GNUNET_CRYPTO_EcdsaPrivateKey transfer_priv; + struct RefreshCommitLink commit_link; + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + struct GNUNET_HashCode transfer_secret; + struct GNUNET_HashCode shared_secret; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "transfer_privs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + &transfer_priv, + sizeof (struct GNUNET_CRYPTO_EddsaPrivateKey)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + res = TALER_MINT_DB_get_refresh_commit_link (db_conn, + &refresh_session_pub, + i, j, + &commit_link); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + res = TALER_MINT_DB_get_refresh_melt (db_conn, &refresh_session_pub, j, &coin_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* We're converting key types here, which is not very nice + * but necessary and harmless (keys will be thrown away later). */ + if (GNUNET_OK != GNUNET_CRYPTO_ecc_ecdh ((struct GNUNET_CRYPTO_EcdhePrivateKey *) &transfer_priv, + (struct GNUNET_CRYPTO_EcdhePublicKey *) &coin_pub, + &transfer_secret)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (0 >= TALER_refresh_decrypt (commit_link.shared_secret_enc, TALER_REFRESH_SHARED_SECRET_LENGTH, + &transfer_secret, &shared_secret)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "decryption failed\n"); + return GNUNET_SYSERR; + } + + if (GNUNET_NO == secret_initialized) + { + secret_initialized = GNUNET_YES; + last_shared_secret = shared_secret; + } + else if (0 != memcmp (&shared_secret, &last_shared_secret, sizeof (struct GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "shared secrets do not match\n"); + return GNUNET_SYSERR; + } + + { + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub_check; + GNUNET_CRYPTO_ecdsa_key_get_public (&transfer_priv, &transfer_pub_check); + if (0 != memcmp (&transfer_pub_check, &commit_link.transfer_pub, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "transfer keys do not match\n"); + return GNUNET_SYSERR; + } + } + } + + /* Check that the commitments for all new coins were correct */ + + for (j = 0; j < refresh_session.num_newcoins; j++) + { + struct RefreshCommitCoin commit_coin; + struct LinkData link_data; + struct TALER_RSA_BlindedSignaturePurpose *coin_ev_check; + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + struct TALER_RSA_BlindingKey *bkey; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + + bkey = NULL; + res = TALER_MINT_DB_get_refresh_commit_coin (db_conn, + &refresh_session_pub, + i, j, + &commit_coin); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + + if (0 >= TALER_refresh_decrypt (commit_coin.link_enc, sizeof (struct LinkData), + &last_shared_secret, &link_data)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "decryption failed\n"); + return GNUNET_SYSERR; + } + + GNUNET_CRYPTO_ecdsa_key_get_public (&link_data.coin_priv, &coin_pub); + if (NULL == (bkey = TALER_RSA_blinding_key_decode (&link_data.bkey_enc))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid blinding key\n"); + return GNUNET_SYSERR; + } + res = TALER_MINT_DB_get_refresh_order (db_conn, j, &refresh_session_pub, &denom_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == (coin_ev_check = + TALER_RSA_message_blind (&coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + bkey, + &denom_pub))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "blind failed\n"); + return GNUNET_SYSERR; + } + + if (0 != memcmp (&coin_ev_check, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "blind envelope does not match for kappa=%d, old=%d\n", + (int) i, (int) j); + return GNUNET_SYSERR; + } + } + } + + + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + { + GNUNET_break (0); + return MHD_NO; + } + + for (j = 0; j < refresh_session.num_newcoins; j++) + { + struct RefreshCommitCoin commit_coin; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_MINT_DenomKeyIssue *dki; + struct TALER_RSA_Signature ev_sig; + + res = TALER_MINT_DB_get_refresh_commit_coin (db_conn, + &refresh_session_pub, + refresh_session.noreveal_index % refresh_session.kappa, + j, + &commit_coin); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res = TALER_MINT_DB_get_refresh_order (db_conn, j, &refresh_session_pub, &denom_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + + key_state = TALER_MINT_key_state_acquire (); + dki = TALER_MINT_get_denom_key (key_state, &denom_pub); + TALER_MINT_key_state_release (key_state); + if (NULL == dki) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_RSA_sign (dki->denom_priv, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &ev_sig)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + res = TALER_MINT_DB_insert_refresh_collectable (db_conn, + j, + &refresh_session_pub, + &ev_sig); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + /* mark that reveal was successful */ + + res = TALER_MINT_DB_set_reveal_ok (db_conn, &refresh_session_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != TALER_MINT_DB_commit (db_conn)) + { + GNUNET_break (0); + return MHD_NO; + } + + return helper_refresh_reveal_send_response (connection, db_conn, &refresh_session_pub); +} + + +/** + * Handle a "/refresh/link" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_link (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + int res; + json_t *root; + json_t *list; + PGconn *db_conn; + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub; + struct SharedSecretEnc shared_secret_enc; + + res = TALER_MINT_mhd_request_arg_data (connection, + "coin_pub", + &coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + list = json_array (); + root = json_object (); + json_object_set_new (root, "new_coins", list); + + res = TALER_db_get_transfer (db_conn, + &coin_pub, + &transfer_pub, + &shared_secret_enc); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + { + return request_send_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "link data not found (transfer)"); + } + GNUNET_assert (GNUNET_OK == res); + + res = TALER_db_get_link (db_conn, &coin_pub, link_iter, list); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + { + return request_send_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "link data not found (link)"); + } + GNUNET_assert (GNUNET_OK == res); + json_object_set_new (root, "transfer_pub", + TALER_JSON_from_data (&transfer_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))); + json_object_set_new (root, "secret_enc", + TALER_JSON_from_data (&shared_secret_enc, + sizeof (struct SharedSecretEnc))); + return send_response_json (connection, root, MHD_HTTP_OK); +} + + +/* end of taler-mint-httpd_refresh.c */ diff --git a/src/mint/taler-mint-httpd_refresh.h b/src/mint/taler-mint-httpd_refresh.h new file mode 100644 index 000000000..20e7d97c2 --- /dev/null +++ b/src/mint/taler-mint-httpd_refresh.h @@ -0,0 +1,103 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_refresh.h + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_REFRESH_H +#define TALER_MINT_HTTPD_REFRESH_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Handle a "/refresh/melt" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_melt (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/commit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_commit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/link" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_link (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/reveal" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_reveal (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/mint/taler-mint-httpd_withdraw.c b/src/mint/taler-mint-httpd_withdraw.c new file mode 100644 index 000000000..7ffa45706 --- /dev/null +++ b/src/mint/taler-mint-httpd_withdraw.c @@ -0,0 +1,400 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_withdraw.c + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_withdraw.h" + + +/** + * Convert a signature (with purpose) to + * a JSON object representation. + * + * @param purpose purpose of the signature + * @param signature the signature + * @return the JSON reporesentation of the signature with purpose + */ +static json_t * +sig_to_json (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + const struct GNUNET_CRYPTO_EddsaSignature *signature) +{ + json_t *root; + json_t *el; + + root = json_object (); + + el = json_integer ((json_int_t) ntohl (purpose->size)); + json_object_set_new (root, "size", el); + + el = json_integer ((json_int_t) ntohl (purpose->purpose)); + json_object_set_new (root, "purpose", el); + + el = TALER_JSON_from_data (signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + json_object_set_new (root, "sig", el); + + return root; +} + + +/** + * Sign a reserve's status with the current signing key. + * + * @param reserve the reserve to sign + * @param key_state the key state containing the current + * signing private key + */ +static void +sign_reserve (struct Reserve *reserve, + struct MintKeyState *key_state) +{ + reserve->status_sign_pub = key_state->current_sign_key_issue.signkey_pub; + reserve->status_sig_purpose.purpose = htonl (TALER_SIGNATURE_RESERVE_STATUS); + reserve->status_sig_purpose.size = htonl (sizeof (struct Reserve) - + offsetof (struct Reserve, status_sig_purpose)); + GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv, + &reserve->status_sig_purpose, + &reserve->status_sig); +} + + +/** + * Handle a "/withdraw/status" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_status (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + PGconn *db_conn; + int res; + struct Reserve reserve; + struct MintKeyState *key_state; + int must_update = GNUNET_NO; + json_t *json; + + res = TALER_MINT_mhd_request_arg_data (connection, + "reserve_pub", + &reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + res = TALER_MINT_DB_get_reserve (db_conn, + &reserve_pub, + &reserve); + if (GNUNET_SYSERR == res) + return TALER_MINT_helper_send_json_pack (rh, + connection, + connection_cls, + 0 /* no caching */, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "Reserve not found"); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + key_state = TALER_MINT_key_state_acquire (); + if (0 != memcmp (&key_state->current_sign_key_issue.signkey_pub, + &reserve.status_sign_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + sign_reserve (&reserve, key_state); + must_update = GNUNET_YES; + } + if ((GNUNET_YES == must_update) && + (GNUNET_OK != TALER_MINT_DB_update_reserve (db_conn, &reserve, !must_update))) + { + GNUNET_break (0); + return MHD_YES; + } + + /* Convert the public information of a reserve (i.e. + excluding private key) to a JSON object. */ + json = json_object (); + json_object_set_new (json, + "balance", + TALER_JSON_from_amount (TALER_amount_ntoh (reserve.balance))); + json_object_set_new (json, + "expiration", + TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (reserve.expiration))); + json_object_set_new (json, + "signature", + sig_to_json (&reserve.status_sig_purpose, + &reserve.status_sig)); + + return send_response_json (connection, + json, + MHD_HTTP_OK); +} + + +/** + * Send positive, normal response for "/withdraw/sign". + * + * @param connection the connection to send the response to + * @param collectable the collectable blindcoin (i.e. the blindly signed coin) + * @return a MHD result code + */ +static int +helper_withdraw_sign_send_reply (struct MHD_Connection *connection, + const struct CollectableBlindcoin *collectable) +{ + json_t *root = json_object (); + + json_object_set_new (root, "ev_sig", + TALER_JSON_from_data (&collectable->ev_sig, + sizeof (struct TALER_RSA_Signature))); + return send_response_json (connection, + root, + MHD_HTTP_OK); +} + + +/** + * Handle a "/withdraw/sign" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_WithdrawRequest wsrd; + int res; + PGconn *db_conn; + struct Reserve reserve; + struct MintKeyState *key_state; + struct CollectableBlindcoin collectable; + struct TALER_MINT_DenomKeyIssue *dki; + struct TALER_RSA_Signature ev_sig; + struct TALER_Amount amount_required; + + memset (&wsrd, + 0, + sizeof (struct TALER_WithdrawRequest)); + res = TALER_MINT_mhd_request_arg_data (connection, + "reserve_pub", + &wsrd.reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + res = TALER_MINT_mhd_request_arg_data (connection, + "denom_pub", + &wsrd.denomination_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + res = TALER_MINT_mhd_request_arg_data (connection, + "coin_ev", + &wsrd.coin_envelope, + sizeof (struct TALER_RSA_Signature)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + res = TALER_MINT_mhd_request_arg_data (connection, + "reserve_sig", + &wsrd.sig, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + res = TALER_MINT_DB_get_collectable_blindcoin (db_conn, + &wsrd.coin_envelope, + &collectable); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + /* Don't sign again if we have already signed the coin */ + if (GNUNET_YES == res) + return helper_withdraw_sign_send_reply (connection, + &collectable); + GNUNET_assert (GNUNET_NO == res); + res = TALER_MINT_DB_get_reserve (db_conn, + &wsrd.reserve_pub, + &reserve); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return request_send_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "Reserve not found"); + + // fill out all the missing info in the request before + // we can check the signature on the request + + wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW); + wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest) - + offsetof (struct TALER_WithdrawRequest, purpose)); + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WITHDRAW, + &wsrd.purpose, + &wsrd.sig, + &wsrd.reserve_pub)) + return request_send_json_pack (connection, + MHD_HTTP_UNAUTHORIZED, + "{s:s}", + "error", "Invalid Signature"); + + key_state = TALER_MINT_key_state_acquire (); + dki = TALER_MINT_get_denom_key (key_state, + &wsrd.denomination_pub); + TALER_MINT_key_state_release (key_state); + if (NULL == dki) + return request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "Denomination not found"); + + amount_required = TALER_amount_ntoh (dki->value); + amount_required = TALER_amount_add (amount_required, + TALER_amount_ntoh (dki->fee_withdraw)); + + if (0 < TALER_amount_cmp (amount_required, + TALER_amount_ntoh (reserve.balance))) + return request_send_json_pack (connection, + MHD_HTTP_PAYMENT_REQUIRED, + "{s:s}", + "error", "Insufficient funds"); + if (GNUNET_OK != TALER_RSA_sign (dki->denom_priv, + &wsrd.coin_envelope, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &ev_sig)) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + reserve.balance = TALER_amount_hton (TALER_amount_subtract (TALER_amount_ntoh (reserve.balance), + amount_required)); + if (GNUNET_OK != + TALER_MINT_DB_update_reserve (db_conn, + &reserve, + GNUNET_YES)) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + collectable.ev = wsrd.coin_envelope; + collectable.ev_sig = ev_sig; + collectable.reserve_pub = wsrd.reserve_pub; + collectable.reserve_sig = wsrd.sig; + if (GNUNET_OK != + TALER_MINT_DB_insert_collectable_blindcoin (db_conn, + &collectable)) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return GNUNET_NO;; + } + return helper_withdraw_sign_send_reply (connection, + &collectable); +} + +/* end of taler-mint-httpd_withdraw.c */ diff --git a/src/mint/taler-mint-httpd_withdraw.h b/src/mint/taler-mint-httpd_withdraw.h new file mode 100644 index 000000000..1d292ebd9 --- /dev/null +++ b/src/mint/taler-mint-httpd_withdraw.h @@ -0,0 +1,65 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_withdraw.h + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_WITHDRAW_H +#define TALER_MINT_HTTPD_WITHDRAW_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + +/** + * Handle a "/withdraw/status" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_status (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/withdraw/sign" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/mint/taler-mint-keycheck.c b/src/mint/taler-mint-keycheck.c new file mode 100644 index 000000000..c6186859c --- /dev/null +++ b/src/mint/taler-mint-keycheck.c @@ -0,0 +1,169 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-keycheck.c + * @brief Check mint keys for validity. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "mint.h" +#include "taler_signatures.h" + + +static char *mintdir; +static struct GNUNET_CONFIGURATION_Handle *kcfg; + + +static int +signkeys_iter (void *cls, const struct TALER_MINT_SignKeyIssue *ski) +{ + struct GNUNET_TIME_Absolute start; + + printf ("iterating over key for start time %s\n", + GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (ski->start))); + + start = GNUNET_TIME_absolute_ntoh (ski->start); + + if (ntohl (ski->purpose.size) != + (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signkey with start %s has invalid purpose field (timestamp: %llu)\n", + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY, + &ski->purpose, + &ski->signature, + &ski->master_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signkey with start %s has invalid signature (timestamp: %llu)\n", + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + printf ("key valid\n"); + return GNUNET_OK; +} + + +static int +mint_signkeys_check () +{ + if (0 > TALER_MINT_signkeys_iterate (mintdir, signkeys_iter, NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +static int denomkeys_iter (void *cls, + const char *alias, + const struct TALER_MINT_DenomKeyIssue *dki) +{ + struct GNUNET_TIME_Absolute start; + + start = GNUNET_TIME_absolute_ntoh (dki->start); + + if (ntohl (dki->purpose.size) != + (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Denomkey for '%s' with start %s has invalid purpose field (timestamp: %llu)\n", + alias, + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM, + &dki->purpose, + &dki->signature, + &dki->master)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Denomkey for '%s'with start %s has invalid signature (timestamp: %llu)\n", + alias, + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + printf ("denom key valid\n"); + + return GNUNET_OK; +} + + +static int +mint_denomkeys_check () +{ + if (0 > TALER_MINT_denomkeys_iterate (mintdir, denomkeys_iter, NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +static int +mint_keys_check (void) +{ + if (GNUNET_OK != mint_signkeys_check ()) + return GNUNET_NO; + return mint_denomkeys_check (); +} + + +/** + * The main function of the keyup tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keycheck", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + if (GNUNET_OK != mint_keys_check ()) + return 1; + return 0; +} + diff --git a/src/mint/taler-mint-keyup.c b/src/mint/taler-mint-keyup.c new file mode 100644 index 000000000..8a1a77882 --- /dev/null +++ b/src/mint/taler-mint-keyup.c @@ -0,0 +1,657 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-keyup.c + * @brief Update the mint's keys for coins and signatures, + * using the mint's offline master key. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_signatures.h" +#include "mint.h" + +#define HASH_CUTOFF 20 + +/** + * Macro to round microseconds to seconds in GNUNET_TIME_* structs. + */ +#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000); + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct CoinTypeNBO +{ + struct GNUNET_TIME_RelativeNBO duration_spend; + struct GNUNET_TIME_RelativeNBO duration_withdraw; + struct TALER_AmountNBO value; + struct TALER_AmountNBO fee_withdraw; + struct TALER_AmountNBO fee_deposit; + struct TALER_AmountNBO fee_refresh; +}; + +GNUNET_NETWORK_STRUCT_END + +struct CoinTypeParams +{ + struct GNUNET_TIME_Relative duration_spend; + struct GNUNET_TIME_Relative duration_withdraw; + struct GNUNET_TIME_Relative duration_overlap; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct GNUNET_TIME_Absolute anchor; +}; + + +/** + * Filename of the master private key. + */ +static char *masterkeyfile; + +/** + * Director of the mint, containing the keys. + */ +static char *mintdir; + +/** + * Time to pretend when the key update is executed. + */ +static char *pretend_time_str; + +/** + * Handle to the mint's configuration + */ +static struct GNUNET_CONFIGURATION_Handle *kcfg; + +/** + * Time when the key update is executed. Either the actual current time, or a + * pretended time. + */ +static struct GNUNET_TIME_Absolute now; + +/** + * Master private key of the mint. + */ +static struct GNUNET_CRYPTO_EddsaPrivateKey *master_priv; + +/** + * Master public key of the mint. + */ +static struct GNUNET_CRYPTO_EddsaPublicKey *master_pub; + +/** + * Until what time do we provide keys? + */ +static struct GNUNET_TIME_Absolute lookahead_sign_stamp; + + +int +config_get_denom (const char *section, const char *option, struct TALER_Amount *denom) +{ + char *str; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, section, option, &str)) + return GNUNET_NO; + if (GNUNET_OK != TALER_string_to_amount (str, denom)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +char * +get_signkey_dir () +{ + char *dir; + size_t len; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mintdir); + GNUNET_assert (len > 0); + return dir; +} + + +char * +get_signkey_file (struct GNUNET_TIME_Absolute start) +{ + char *dir; + size_t len; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS DIR_SEPARATOR_STR "%llu"), + mintdir, (long long) start.abs_value_us); + GNUNET_assert (len > 0); + return dir; +} + + + +/** + * Hash the data defining the coin type. + * Exclude information that may not be the same for all + * instances of the coin type (i.e. the anchor, overlap). + */ +void +hash_coin_type (const struct CoinTypeParams *p, struct GNUNET_HashCode *hash) +{ + struct CoinTypeNBO p_nbo; + + memset (&p_nbo, 0, sizeof (struct CoinTypeNBO)); + + p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); + p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); + p_nbo.value = TALER_amount_hton (p->value); + p_nbo.fee_withdraw = TALER_amount_hton (p->fee_withdraw); + p_nbo.fee_deposit = TALER_amount_hton (p->fee_deposit); + p_nbo.fee_refresh = TALER_amount_hton (p->fee_refresh); + + GNUNET_CRYPTO_hash (&p_nbo, sizeof (struct CoinTypeNBO), hash); +} + + +static const char * +get_cointype_dir (const struct CoinTypeParams *p) +{ + static char dir[4096]; + size_t len; + struct GNUNET_HashCode hash; + char *hash_str; + char *val_str; + unsigned int i; + + hash_coin_type (p, &hash); + hash_str = TALER_data_to_string_alloc (&hash, sizeof (struct GNUNET_HashCode)); + GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); + GNUNET_assert (NULL != hash_str); + hash_str[HASH_CUTOFF] = 0; + + val_str = TALER_amount_to_string (p->value); + for (i = 0; i < strlen (val_str); i++) + if (':' == val_str[i] || '.' == val_str[i]) + val_str[i] = '_'; + + len = GNUNET_snprintf (dir, sizeof (dir), + ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS DIR_SEPARATOR_STR "%s-%s"), + mintdir, val_str, hash_str); + GNUNET_assert (len > 0); + GNUNET_free (hash_str); + return dir; +} + + +static const char * +get_cointype_file (struct CoinTypeParams *p, + struct GNUNET_TIME_Absolute start) +{ + const char *dir; + static char filename[4096]; + size_t len; + dir = get_cointype_dir (p); + len = GNUNET_snprintf (filename, sizeof (filename), ("%s" DIR_SEPARATOR_STR "%llu"), + dir, (unsigned long long) start.abs_value_us); + GNUNET_assert (len > 0); + return filename; +} + + +/** + * Get the latest key file from the past. + * + * @param cls closure + * @param filename complete filename (absolute path) + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +get_anchor_iter (void *cls, + const char *filename) +{ + struct GNUNET_TIME_Absolute stamp; + struct GNUNET_TIME_Absolute *anchor = cls; + const char *base; + char *end = NULL; + + base = GNUNET_STRINGS_get_short_name (filename); + stamp.abs_value_us = strtol (base, &end, 10); + + if ((NULL == end) || (0 != *end)) + { + fprintf(stderr, "Ignoring unexpected file '%s'.\n", filename); + return GNUNET_OK; + } + + // TODO: check if it's actually a valid key file + + if ((stamp.abs_value_us <= now.abs_value_us) && (stamp.abs_value_us > anchor->abs_value_us)) + *anchor = stamp; + + return GNUNET_OK; +} + + +/** + * Get the timestamp where the first new key should be generated. + * Relies on correctly named key files. + * + * @param dir directory with the signed stuff + * @param duration how long is one key valid? + * @param overlap what's the overlap between the keys validity period? + * @param[out] anchor the timestamp where the first new key should be generated + */ +void +get_anchor (const char *dir, + struct GNUNET_TIME_Relative duration, + struct GNUNET_TIME_Relative overlap, + struct GNUNET_TIME_Absolute *anchor) +{ + GNUNET_assert (0 == duration.rel_value_us % 1000000); + GNUNET_assert (0 == overlap.rel_value_us % 1000000); + if (GNUNET_YES != GNUNET_DISK_directory_test (dir, GNUNET_YES)) + { + *anchor = now; + printf ("Can't look for anchor (%s)\n", dir); + return; + } + + *anchor = GNUNET_TIME_UNIT_ZERO_ABS; + if (-1 == GNUNET_DISK_directory_scan (dir, &get_anchor_iter, anchor)) + { + *anchor = now; + return; + } + + if ((GNUNET_TIME_absolute_add (*anchor, duration)).abs_value_us < now.abs_value_us) + { + // there's no good anchor, start from now + // (existing keys are too old) + *anchor = now; + } + else if (anchor->abs_value_us != now.abs_value_us) + { + // we have a good anchor + *anchor = GNUNET_TIME_absolute_add (*anchor, duration); + *anchor = GNUNET_TIME_absolute_subtract (*anchor, overlap); + } + // anchor is now the stamp where we need to create a new key +} + +static void +create_signkey_issue (struct GNUNET_TIME_Absolute start, + struct GNUNET_TIME_Relative duration, + struct TALER_MINT_SignKeyIssue *issue) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_assert (NULL != priv); + issue->signkey_priv = *priv; + GNUNET_free (priv); + issue->master_pub = *master_pub; + issue->start = GNUNET_TIME_absolute_hton (start); + issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, duration)); + + GNUNET_CRYPTO_eddsa_key_get_public (&issue->signkey_priv, &issue->signkey_pub); + + issue->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY); + issue->purpose.size = htonl (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose)); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &issue->purpose, &issue->signature)) + { + GNUNET_abort (); + } +} + + +static int +check_signkey_valid (const char *signkey_filename) +{ + // FIXME: do real checks + return GNUNET_OK; +} + + +int +mint_keys_update_signkeys () +{ + struct GNUNET_TIME_Relative signkey_duration; + struct GNUNET_TIME_Absolute anchor; + char *signkey_dir; + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "signkey_duration", &signkey_duration)) + { + fprintf (stderr, "Can't read config value mint_keys.signkey_duration\n"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (signkey_duration, rel_value_us); + signkey_dir = get_signkey_dir (); + // make sure the directory exists + if (GNUNET_OK != GNUNET_DISK_directory_create (signkey_dir)) + { + fprintf (stderr, "Cant create signkey dir\n"); + return GNUNET_SYSERR; + } + + get_anchor (signkey_dir, signkey_duration, GNUNET_TIME_UNIT_ZERO, &anchor); + + while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) { + char *skf; + skf = get_signkey_file (anchor); + if (GNUNET_YES != GNUNET_DISK_file_test (skf)) + { + struct TALER_MINT_SignKeyIssue signkey_issue; + ssize_t nwrite; + printf ("Generating signing key for %s.\n", GNUNET_STRINGS_absolute_time_to_string (anchor)); + create_signkey_issue (anchor, signkey_duration, &signkey_issue); + nwrite = GNUNET_DISK_fn_write (skf, &signkey_issue, sizeof (struct TALER_MINT_SignKeyIssue), + (GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ)); + if (nwrite != sizeof (struct TALER_MINT_SignKeyIssue)) + { + fprintf (stderr, "Can't write to file '%s'\n", skf); + return GNUNET_SYSERR; + } + } + else if (GNUNET_OK != check_signkey_valid (skf)) + { + return GNUNET_SYSERR; + } + anchor = GNUNET_TIME_absolute_add (anchor, signkey_duration); + } + return GNUNET_OK; +} + + +int +get_cointype_params (const char *ct, struct CoinTypeParams *params) +{ + const char *dir; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_withdraw", ct, ¶ms->duration_withdraw)) + { + fprintf (stderr, "Withdraw duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_withdraw, rel_value_us); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_spend", ct, ¶ms->duration_spend)) + { + fprintf (stderr, "Spend duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_spend, rel_value_us); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_overlap", ct, ¶ms->duration_overlap)) + { + fprintf (stderr, "Overlap duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_overlap, rel_value_us); + + if (GNUNET_OK != config_get_denom ("mint_denom_value", ct, ¶ms->value)) + { + fprintf (stderr, "Value not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_withdraw", ct, ¶ms->fee_withdraw)) + { + fprintf (stderr, "Withdraw fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_deposit", ct, ¶ms->fee_deposit)) + { + fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_refresh", ct, ¶ms->fee_refresh)) + { + fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + dir = get_cointype_dir (params); + get_anchor (dir, params->duration_spend, params->duration_overlap, ¶ms->anchor); + return GNUNET_OK; +} + + +static void +create_denomkey_issue (struct CoinTypeParams *params, struct TALER_MINT_DenomKeyIssue *dki) +{ + GNUNET_assert (NULL != (dki->denom_priv = TALER_RSA_key_create ())); + TALER_RSA_key_get_public (dki->denom_priv, &dki->denom_pub); + dki->master = *master_pub; + dki->start = GNUNET_TIME_absolute_hton (params->anchor); + dki->expire_withdraw = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_withdraw)); + dki->expire_spend = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_spend)); + dki->value = TALER_amount_hton (params->value); + dki->fee_withdraw = TALER_amount_hton (params->fee_withdraw); + dki->fee_deposit = TALER_amount_hton (params->fee_deposit); + dki->fee_refresh = TALER_amount_hton (params->fee_refresh); + + dki->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); + dki->purpose.size = htonl (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose)); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &dki->purpose, &dki->signature)) + { + GNUNET_abort (); + } +} + + +static int +check_cointype_valid (const char *filename, struct CoinTypeParams *params) +{ + // FIXME: add real checks + return GNUNET_OK; +} + + +int +mint_keys_update_cointype (const char *coin_alias) +{ + struct CoinTypeParams p; + const char *cointype_dir; + + if (GNUNET_OK != get_cointype_params (coin_alias, &p)) + return GNUNET_SYSERR; + + cointype_dir = get_cointype_dir (&p); + if (GNUNET_OK != GNUNET_DISK_directory_create (cointype_dir)) + return GNUNET_SYSERR; + + while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) { + const char *dkf; + dkf = get_cointype_file (&p, p.anchor); + + if (GNUNET_YES != GNUNET_DISK_file_test (dkf)) + { + struct TALER_MINT_DenomKeyIssue denomkey_issue; + int ret; + printf ("Generating denomination key for type '%s', start %s.\n", + coin_alias, GNUNET_STRINGS_absolute_time_to_string (p.anchor)); + printf ("Target path: %s\n", dkf); + create_denomkey_issue (&p, &denomkey_issue); + ret = TALER_MINT_write_denom_key (dkf, &denomkey_issue); + TALER_RSA_key_free (denomkey_issue.denom_priv); + if (GNUNET_OK != ret) + { + fprintf (stderr, "Can't write to file '%s'\n", dkf); + return GNUNET_SYSERR; + } + } + else if (GNUNET_OK != check_cointype_valid (dkf, &p)) + { + return GNUNET_SYSERR; + } + p.anchor = GNUNET_TIME_absolute_add (p.anchor, p.duration_spend); + p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, p.duration_overlap); + } + return GNUNET_OK; +} + + +int +mint_keys_update_denomkeys () +{ + char *coin_types; + char *ct; + char *tok_ctx; + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint_keys", "coin_types", &coin_types)) + { + fprintf (stderr, "mint_keys.coin_types not in configuration\n"); + return GNUNET_SYSERR; + } + + for (ct = strtok_r (coin_types, " ", &tok_ctx); + ct != NULL; + ct = strtok_r (NULL, " ", &tok_ctx)) + { + if (GNUNET_OK != mint_keys_update_cointype (ct)) + { + GNUNET_free (coin_types); + return GNUNET_SYSERR; + } + } + GNUNET_free (coin_types); + return GNUNET_OK; +} + + +static int +mint_keys_update () +{ + int ret; + struct GNUNET_TIME_Relative lookahead_sign; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "lookahead_sign", &lookahead_sign)) + { + fprintf (stderr, "mint_keys.lookahead_sign not found\n"); + return GNUNET_SYSERR; + } + if (lookahead_sign.rel_value_us == 0) + { + fprintf (stderr, "mint_keys.lookahead_sign must not be zero\n"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (lookahead_sign, rel_value_us); + lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, lookahead_sign); + + ret = mint_keys_update_signkeys (); + if (GNUNET_OK != ret) + return GNUNET_SYSERR; + + return mint_keys_update_denomkeys (); +} + + +/** + * The main function of the keyup tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'m', "master-key", "FILE", + "master key file (private key)", 1, + &GNUNET_GETOPT_set_filename, &masterkeyfile}, + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + {'t', "time", "TIMESTAMP", + "pretend it is a different time for the update", 0, + &GNUNET_GETOPT_set_string, &pretend_time_str}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keyup", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + if (NULL != pretend_time_str) + { + if (GNUNET_OK != GNUNET_STRINGS_fancy_time_to_absolute (pretend_time_str, &now)) + { + fprintf (stderr, "timestamp invalid\n"); + return 1; + } + } + else + { + now = GNUNET_TIME_absolute_get (); + } + ROUND_TO_SECS (now, abs_value_us); + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + + if (NULL == masterkeyfile) + { + fprintf (stderr, "master key file not given\n"); + return 1; + } + master_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); + if (NULL == master_priv) + { + fprintf (stderr, "master key invalid\n"); + return 1; + } + + master_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); + GNUNET_CRYPTO_eddsa_key_get_public (master_priv, master_pub); + + // check if key from file matches the one from the configuration + { + struct GNUNET_CRYPTO_EddsaPublicKey master_pub_from_cfg; + if (GNUNET_OK != TALER_configuration_get_data (kcfg, "mint", "master_pub", + &master_pub_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + fprintf (stderr, "master key missing in configuration (mint.master_pub)\n"); + return 1; + } + if (0 != memcmp (master_pub, &master_pub_from_cfg, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + fprintf (stderr, "Mismatch between key from mint configuration and master private key file from command line.\n"); + return 1; + } + } + + if (GNUNET_OK != mint_keys_update ()) + return 1; + return 0; +} + diff --git a/src/mint/taler-mint-reservemod.c b/src/mint/taler-mint-reservemod.c new file mode 100644 index 000000000..3dd94f84b --- /dev/null +++ b/src/mint/taler-mint-reservemod.c @@ -0,0 +1,215 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-reservemod.c + * @brief Modify reserves. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "taler_util.h" +#include "taler_signatures.h" +#include "mint.h" + +static char *mintdir; +static struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub; +static struct GNUNET_CONFIGURATION_Handle *kcfg; +static PGconn *db_conn; + + + +/** + * Create a new or add to existing reserve. + * Fails if currencies do not match. + * + * @param denom denomination to add + * + * @return ... + */ +int +reservemod_add (struct TALER_Amount denom) +{ + PGresult *result; + { + const void *param_values[] = { reserve_pub }; + int param_lengths[] = {sizeof(struct GNUNET_CRYPTO_EddsaPublicKey)}; + int param_formats[] = {1}; + result = PQexecParams (db_conn, + "select balance_value, balance_fraction, balance_currency from reserves where reserve_pub=$1 limit 1;", + 1, NULL, (const char * const *) param_values, param_lengths, param_formats, 1); + } + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + fprintf (stderr, "Select failed: %s\n", PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + struct GNUNET_TIME_AbsoluteNBO exnbo; + exnbo = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add ( GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_YEARS)); + + uint32_t value = htonl (denom.value); + uint32_t fraction = htonl (denom.fraction); + const void *param_values[] = { + reserve_pub, + &value, + &fraction, + denom.currency, + &exnbo}; + int param_lengths[] = {32, 4, 4, strlen(denom.currency), 8}; + int param_formats[] = {1, 1, 1, 1, 1}; + result = PQexecParams (db_conn, + "insert into reserves (reserve_pub, balance_value, balance_fraction, balance_currency, " + " expiration_date )" + "values ($1,$2,$3,$4,$5);", + 5, NULL, (const char **) param_values, param_lengths, param_formats, 1); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, "Insert failed: %s\n", PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + } + else + { + struct TALER_Amount old_denom; + struct TALER_Amount new_denom; + struct TALER_AmountNBO new_denom_nbo; + int denom_indices[] = {0, 1, 2}; + int param_lengths[] = {4, 4, 32}; + int param_formats[] = {1, 1, 1}; + const void *param_values[] = { + &new_denom_nbo.value, + &new_denom_nbo.fraction, + reserve_pub + }; + + GNUNET_assert (GNUNET_OK == TALER_TALER_DB_extract_amount (result, 0, denom_indices, &old_denom)); + new_denom = TALER_amount_add (old_denom, denom); + new_denom_nbo = TALER_amount_hton (new_denom); + result = PQexecParams (db_conn, + "UPDATE reserves " + "SET balance_value = $1, balance_fraction = $2, " + " status_sig = NULL, status_sign_pub = NULL " + "WHERE reserve_pub = $3 ", + 3, NULL, (const char **) param_values, param_lengths, param_formats, 1); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, "Update failed: %s\n", PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + fprintf (stderr, "Update failed (updated '%s' tupes instead of '1')\n", + PQcmdTuples (result)); + return GNUNET_SYSERR; + } + + } + return GNUNET_OK; +} + + +/** + * The main function of the reservemod tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static char *reserve_pub_str; + static char *add_str; + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + {'R', "reserve", "KEY", + "reserve (public key) to modify", 1, + &GNUNET_GETOPT_set_string, &reserve_pub_str}, + {'a', "add", "DENOM", + "value to add", 1, + &GNUNET_GETOPT_set_string, &add_str}, + GNUNET_GETOPT_OPTION_END + }; + char *TALER_MINT_db_connection_cfg_str; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keycheck", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + reserve_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); + if ((NULL == reserve_pub_str) || + (GNUNET_OK != GNUNET_STRINGS_string_to_data (reserve_pub_str, + strlen (reserve_pub_str), + reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))) + { + fprintf (stderr, "reserve key invalid\n"); + return 1; + } + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint", "db", &TALER_MINT_db_connection_cfg_str)) + { + fprintf (stderr, "db configuration string not found\n"); + return 42; + } + db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); + if (CONNECTION_OK != PQstatus (db_conn)) + { + fprintf (stderr, "db connection failed: %s\n", PQerrorMessage (db_conn)); + return 1; + } + + if (NULL != add_str) + { + struct TALER_Amount add_value; + if (GNUNET_OK != TALER_string_to_amount (add_str, &add_value)) + { + fprintf (stderr, "could not read value\n"); + return 1; + } + if (GNUNET_OK != reservemod_add (add_value)) + { + fprintf (stderr, "adding value failed\n"); + return 1; + } + } + return 0; +} + diff --git a/src/mint/test_mint_api.c b/src/mint/test_mint_api.c new file mode 100644 index 000000000..965d607f5 --- /dev/null +++ b/src/mint/test_mint_api.c @@ -0,0 +1,211 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/test_mint_api.c + * @brief testcase to test mint's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_mint_service.h" + +struct TALER_MINT_Context *ctx; + +struct TALER_MINT_Handle *mint; + +struct TALER_MINT_KeysGetHandle *dkey_get; + +struct TALER_MINT_DepositHandle *dh; + +static GNUNET_SCHEDULER_TaskIdentifier shutdown_task; + +static int result; + + +static void +do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + shutdown_task = GNUNET_SCHEDULER_NO_TASK; + if (NULL != dkey_get) + TALER_MINT_keys_get_cancel (dkey_get); + dkey_get = NULL; + if (NULL != dh) + TALER_MINT_deposit_submit_cancel (dh); + dh = NULL; + TALER_MINT_disconnect (mint); + mint = NULL; + TALER_MINT_cleanup (ctx); + ctx = NULL; +} + + +/** + * Callbacks of this type are used to serve the result of submitting a deposit + * permission object to a mint + * + * @param cls closure + * @param status 1 for successful deposit, 2 for retry, 0 for failure + * @param obj the received JSON object; can be NULL if it cannot be constructed + * from the reply + * @param emsg in case of unsuccessful deposit, this contains a human readable + * explanation. + */ +static void +deposit_status (void *cls, + int status, + json_t *obj, + char *emsg) +{ + char *json_enc; + + dh = NULL; + json_enc = NULL; + if (NULL != obj) + { + json_enc = json_dumps (obj, JSON_INDENT(2)); + fprintf (stderr, "%s", json_enc); + } + if (1 == status) + result = GNUNET_OK; + else + GNUNET_break (0); + if (NULL != emsg) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Deposit failed: %s\n", emsg); + GNUNET_SCHEDULER_shutdown (); +} +/** + * Functions of this type are called to signal completion of an asynchronous call. + * + * @param cls closure + * @param emsg if the asynchronous call could not be completed due to an error, + * this parameter contains a human readable error message + */ +static void +cont (void *cls, const char *emsg) +{ + json_t *dp; + char rnd_32[32]; + char rnd_64[64]; + char *enc_32; + char *enc_64; + + GNUNET_assert (NULL == cls); + dkey_get = NULL; + if (NULL != emsg) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s\n", emsg); + + enc_32 = TALER_data_to_string_alloc (rnd_32, sizeof (rnd_32)); + enc_64 = TALER_data_to_string_alloc (rnd_64, sizeof (rnd_64)); + dp = json_pack ("{s:s s:o s:s s:s s:s s:s s:s s:s s:s s:s}", + "type", "DIRECT_DEPOSIT", + "wire", json_pack ("{s:s}", "type", "SEPA"), + "C", enc_32, + "K", enc_32, + "ubsig", enc_64, + "M", enc_32, + "H_a", enc_64, + "H_wire", enc_64, + "csig", enc_64, + "m", "B1C5GP2RB1C5G"); + GNUNET_free (enc_32); + GNUNET_free (enc_64); + dh = TALER_MINT_deposit_submit_json (mint, + deposit_status, + NULL, + dp); + json_decref (dp); +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the mint. No TALER_MINT_*() functions should be called + * in this callback. + * + * @param cls closure passed to TALER_MINT_keys_get() + * @param sign_keys NULL-terminated array of pointers to the mint's signing + * keys. NULL if no signing keys are retrieved. + * @param denom_keys NULL-terminated array of pointers to the mint's + * denomination keys; will be NULL if no signing keys are retrieved. + */ +static void +read_denom_key (void *cls, + struct TALER_MINT_SigningPublicKey **sign_keys, + struct TALER_MINT_DenomPublicKey **denom_keys) +{ + unsigned int cnt; + GNUNET_assert (NULL == cls); +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); return; } while (0) + ERR (NULL == sign_keys); + ERR (NULL == denom_keys); + for (cnt = 0; NULL != sign_keys[cnt]; cnt++) + GNUNET_free (sign_keys[cnt]); + ERR (0 == cnt); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u signing keys\n", cnt); + GNUNET_free (sign_keys); + for (cnt = 0; NULL != denom_keys[cnt]; cnt++) + GNUNET_free (denom_keys[cnt]); + ERR (0 == cnt); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u denomination keys\n", cnt); + GNUNET_free (denom_keys); +#undef ERR + return; +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + ctx = TALER_MINT_init (); + mint = TALER_MINT_connect (ctx, "localhost", 4241, NULL); + GNUNET_assert (NULL != mint); + dkey_get = TALER_MINT_keys_get (mint, + &read_denom_key, NULL, + &cont, NULL); + GNUNET_assert (NULL != dkey_get); + shutdown_task = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 5), + &do_shutdown, NULL); +} + +int +main (int argc, char * const *argv) +{ + static struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + result = GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, "test-mint-api", + gettext_noop + ("Testcase to test mint's HTTP API interface"), + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/mint/test_mint_common.c b/src/mint/test_mint_common.c new file mode 100644 index 000000000..b7cad3ea4 --- /dev/null +++ b/src/mint/test_mint_common.c @@ -0,0 +1,83 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e. V. (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/test_mint_common.c + * @brief test cases for some functions in mint/mint_common.c + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_rsa.h" +#include "mint.h" + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +int +main (int argc, const char *const argv[]) +{ + struct TALER_MINT_DenomKeyIssue dki; + struct TALER_RSA_PrivateKeyBinaryEncoded *enc; + struct TALER_MINT_DenomKeyIssue dki_read; + struct TALER_RSA_PrivateKeyBinaryEncoded *enc_read; + char *tmpfile; + + int ret; + + ret = 1; + enc = NULL; + enc_read = NULL; + tmpfile = NULL; + dki.denom_priv = NULL; + dki_read.denom_priv = NULL; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &dki.signature, + sizeof (dki) - offsetof (struct TALER_MINT_DenomKeyIssue, + signature)); + dki.denom_priv = TALER_RSA_key_create (); + EXITIF (NULL == (enc = TALER_RSA_encode_key (dki.denom_priv))); + EXITIF (NULL == (tmpfile = GNUNET_DISK_mktemp ("test_mint_common"))); + EXITIF (GNUNET_OK != TALER_MINT_write_denom_key (tmpfile, &dki)); + EXITIF (GNUNET_OK != TALER_MINT_read_denom_key (tmpfile, &dki_read)); + EXITIF (NULL == (enc_read = TALER_RSA_encode_key (dki_read.denom_priv))); + EXITIF (enc->len != enc_read->len); + EXITIF (0 != memcmp (enc, + enc_read, + ntohs(enc->len))); + EXITIF (0 != memcmp (&dki.signature, + &dki_read.signature, + sizeof (dki) - offsetof (struct TALER_MINT_DenomKeyIssue, + signature))); + ret = 0; + + EXITIF_exit: + GNUNET_free_non_null (enc); + if (NULL != tmpfile) + { + (void) unlink (tmpfile); + GNUNET_free (tmpfile); + } + GNUNET_free_non_null (enc_read); + if (NULL != dki.denom_priv) + TALER_RSA_key_free (dki.denom_priv); + if (NULL != dki_read.denom_priv) + TALER_RSA_key_free (dki_read.denom_priv); + return ret; +} diff --git a/src/mint/test_mint_deposits.c b/src/mint/test_mint_deposits.c new file mode 100644 index 000000000..776bc15d2 --- /dev/null +++ b/src/mint/test_mint_deposits.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/test_mint_deposits.c + * @brief testcase for mint deposits + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "mint_db.h" + +#define DB_URI "postgres:///taler" + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * DB connection handle + */ +static PGconn *conn; + +/** + * Should we not interact with a temporary table? + */ +static int persistent; + +/** + * Testcase result + */ +static int result; + + +static void +do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + if (NULL != conn) + PQfinish (conn); + conn = NULL; +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + static const char wire[] = "{" + "\"type\":\"SEPA\"," + "\"IBAN\":\"DE67830654080004822650\"," + "\"NAME\":\"GNUNET E.V\"," + "\"BIC\":\"GENODEF1SRL\"" + "}"; + struct Deposit *deposit; + struct Deposit *q_deposit; + uint64_t transaction_id; + + deposit = NULL; + q_deposit = NULL; + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, NULL); + EXITIF (NULL == (conn = PQconnectdb(DB_URI))); + EXITIF (GNUNET_OK != TALER_MINT_DB_init_deposits (conn, !persistent)); + EXITIF (GNUNET_OK != TALER_MINT_DB_prepare_deposits (conn)); + deposit = GNUNET_malloc (sizeof (struct Deposit) + sizeof (wire)); + /* Makeup a random coin public key */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + deposit, + sizeof (struct Deposit)); + /* Makeup a random 64bit transaction ID */ + transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT64_MAX); + deposit->transaction_id = GNUNET_htonll (transaction_id); + /* Random amount */ + deposit->amount.value = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + deposit->amount.fraction = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + strcpy (deposit->amount.currency, "EUR"); + /* Copy wireformat */ + (void) memcpy (deposit->wire, wire, sizeof (wire)); + EXITIF (GNUNET_OK != TALER_MINT_DB_insert_deposit (conn, + deposit)); + EXITIF (GNUNET_OK != TALER_MINT_DB_get_deposit (conn, + &deposit->coin_pub, + &q_deposit)); + EXITIF (0 != memcmp (deposit, + q_deposit, + sizeof (struct Deposit) - offsetof(struct Deposit, + wire))); + EXITIF (transaction_id != GNUNET_ntohll (q_deposit->transaction_id)); + result = GNUNET_OK; + + EXITIF_exit: + GNUNET_free_non_null (deposit); + GNUNET_free_non_null (q_deposit); + GNUNET_SCHEDULER_shutdown (); + return; +} + + +int main(int argc, char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'T', "persist", NULL, + gettext_noop ("Use a persistent database table instead of a temporary one"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &persistent}, + GNUNET_GETOPT_OPTION_END + }; + + + persistent = GNUNET_NO; + result = GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "test-mint-deposits", + "testcase for mint deposits", + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/mint/test_mint_nayapaisa.ecc b/src/mint/test_mint_nayapaisa.ecc Binary files differnew file mode 100644 index 000000000..942110b5c --- /dev/null +++ b/src/mint/test_mint_nayapaisa.ecc diff --git a/src/mint/test_mint_nayapaisa/README b/src/mint/test_mint_nayapaisa/README new file mode 100644 index 000000000..fce5e0180 --- /dev/null +++ b/src/mint/test_mint_nayapaisa/README @@ -0,0 +1 @@ +This directory is a template for the mint directory. diff --git a/src/mint/test_mint_nayapaisa/config/mint-common.conf b/src/mint/test_mint_nayapaisa/config/mint-common.conf new file mode 100644 index 000000000..c1fede7a2 --- /dev/null +++ b/src/mint/test_mint_nayapaisa/config/mint-common.conf @@ -0,0 +1,6 @@ +[mint] +db = postgres:///taler +port = 4241 +master_pub = 6ZE0HEY2M0FWP61M0470HYBF4K6RRD5DP54372PD2TN9N9VX2VJG +refresh_security_parameter = 3 + diff --git a/src/mint/test_mint_nayapaisa/config/mint-keyup.conf b/src/mint/test_mint_nayapaisa/config/mint-keyup.conf new file mode 100644 index 000000000..1542d1a63 --- /dev/null +++ b/src/mint/test_mint_nayapaisa/config/mint-keyup.conf @@ -0,0 +1,79 @@ +[mint_keys] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long do we generate denomination and signing keys +# ahead of time? +lookahead_sign = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# what coin types do we have available? +coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000 + + + +[mint_denom_duration_overlap] +default_eur_ct_10 = 5 minutes +default_eur_5 = 5 minutes +default_eur_10 = 5 minutes +default_eur_1000 = 5 minutes + + + +[mint_denom_value] +default_eur_ct_10 = EUR:0.10 +default_eur_5 = EUR:5 +default_eur_10 = EUR:10 +default_eur_1000 = EUR:1000 + + + +[mint_denom_duration_withdraw] +default_eur_ct_10 = 7 days +default_eur_5 = 7 days +default_eur_10 = 7 days +default_eur_1000 = 1 day + + + +[mint_denom_duration_spend] +default_eur_ct_10 = 30 days +default_eur_5 = 30 days +default_eur_10 = 30 days +default_eur_1000 = 30 day + + + +[mint_denom_fee_withdraw] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + +[mint_denom_fee_deposit] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_fee_refresh] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_kappa] +default_eur_ct_10 = 3 +default_eur_5 = 3 +default_eur_10 = 3 +default_eur_1000 = 5 + diff --git a/src/mint/test_mint_nyadirahim.ecc b/src/mint/test_mint_nyadirahim.ecc new file mode 100644 index 000000000..9db920894 --- /dev/null +++ b/src/mint/test_mint_nyadirahim.ecc @@ -0,0 +1 @@ +WBf rЌ:Vj
\ No newline at end of file diff --git a/src/mint/test_mint_nyadirahim/README b/src/mint/test_mint_nyadirahim/README new file mode 100644 index 000000000..fce5e0180 --- /dev/null +++ b/src/mint/test_mint_nyadirahim/README @@ -0,0 +1 @@ +This directory is a template for the mint directory. diff --git a/src/mint/test_mint_nyadirahim/config/mint-common.conf b/src/mint/test_mint_nyadirahim/config/mint-common.conf new file mode 100644 index 000000000..c4d528634 --- /dev/null +++ b/src/mint/test_mint_nyadirahim/config/mint-common.conf @@ -0,0 +1,6 @@ +[mint] +db = postgres:///taler +port = 4241 +master_pub = 7995WKK71KPKTBBMA5BHNBSZFGNRZPYNXDJMQ8EK86V9598H03TG +refresh_security_parameter = 3 + diff --git a/src/mint/test_mint_nyadirahim/config/mint-keyup.conf b/src/mint/test_mint_nyadirahim/config/mint-keyup.conf new file mode 100644 index 000000000..1542d1a63 --- /dev/null +++ b/src/mint/test_mint_nyadirahim/config/mint-keyup.conf @@ -0,0 +1,79 @@ +[mint_keys] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long do we generate denomination and signing keys +# ahead of time? +lookahead_sign = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# what coin types do we have available? +coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000 + + + +[mint_denom_duration_overlap] +default_eur_ct_10 = 5 minutes +default_eur_5 = 5 minutes +default_eur_10 = 5 minutes +default_eur_1000 = 5 minutes + + + +[mint_denom_value] +default_eur_ct_10 = EUR:0.10 +default_eur_5 = EUR:5 +default_eur_10 = EUR:10 +default_eur_1000 = EUR:1000 + + + +[mint_denom_duration_withdraw] +default_eur_ct_10 = 7 days +default_eur_5 = 7 days +default_eur_10 = 7 days +default_eur_1000 = 1 day + + + +[mint_denom_duration_spend] +default_eur_ct_10 = 30 days +default_eur_5 = 30 days +default_eur_10 = 30 days +default_eur_1000 = 30 day + + + +[mint_denom_fee_withdraw] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + +[mint_denom_fee_deposit] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_fee_refresh] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_kappa] +default_eur_ct_10 = 3 +default_eur_5 = 3 +default_eur_10 = 3 +default_eur_1000 = 5 + diff --git a/src/util/Makefile.am b/src/util/Makefile.am new file mode 100644 index 000000000..f935802a6 --- /dev/null +++ b/src/util/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) $(POSTGRESQL_CPPFLAGS) + +lib_LTLIBRARIES = \ + libtalerutil.la + +libtalerutil_la_SOURCES = \ + util.c \ + json.c \ + db.c \ + microhttpd.c \ + rsa.c + +libtalerutil_la_LIBADD = \ + -lgnunetutil \ + $(LIBGCRYPT_LIBS) \ + -ljansson \ + -lmicrohttpd \ + -lpq + +libtalerutil_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -export-dynamic -no-undefined + +check_PROGRAMS = \ + test-hash-context \ + test-rsa + +TESTS = \ + $(check_PROGRAMS) + +test_hash_context_SOURCES = test_hash_context.c +test_hash_context_CPPFLAGS = $(AM_CPPFLAGS) $(LIBGCRYPT_CFLAGS) +test_hash_context_LDADD = libtalerutil.la \ + -lgnunetutil $(LIBGCRYPT_LIBS) + +test_rsa_SOURCES = test_rsa.c +test_rsa_LDADD = libtalerutil.la \ + -lgnunetutil $(LIBGCRYPT_LIBS) diff --git a/src/util/db.c b/src/util/db.c new file mode 100644 index 000000000..a0b234a06 --- /dev/null +++ b/src/util/db.c @@ -0,0 +1,196 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + +/** + * @file util/db.c + * @brief helper functions for DB interactions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Florian Dold + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_db_lib.h" + + +/** + * Execute a prepared statement. + */ +PGresult * +TALER_DB_exec_prepared (PGconn *db_conn, + const char *name, + const struct TALER_DB_QueryParam *params) +{ + unsigned len; + unsigned i; + + /* count the number of parameters */ + + { + const struct TALER_DB_QueryParam *x; + for (len = 0, x = params; + x->more; + len +=1, x += 1); + } + + /* new scope to allow stack allocation without alloca */ + + { + void *param_values[len]; + int param_lengths[len]; + int param_formats[len]; + + for (i = 0; i < len; i += 1) + { + param_values[i] = (void *) params[i].data; + param_lengths[i] = params[i].size; + param_formats[i] = 1; + } + return PQexecPrepared (db_conn, name, len, + (const char **) param_values, param_lengths, param_formats, 1); + } +} + + +/** + * Extract results from a query result according to the given specification. + * If colums are NULL, the destination is not modified, and GNUNET_NO + * is returned. + * + * @return + * GNUNET_YES if all results could be extracted + * GNUNET_NO if at least one result was NULL + * GNUNET_SYSERR if a result was invalid (non-existing field) + */ +int +TALER_DB_extract_result (PGresult *result, + struct TALER_DB_ResultSpec *rs, + int row) +{ + int had_null = GNUNET_NO; + + for (; NULL != rs->fname; rs += 1) + { + int fnum; + fnum = PQfnumber (result, rs->fname); + if (fnum < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "field '%s' does not exist in result\n", rs->fname); + return GNUNET_SYSERR; + } + + /* if a field is null, continue but + * remember that we now return a different result */ + + if (PQgetisnull (result, row, fnum)) + { + had_null = GNUNET_YES; + continue; + } + const char *res; + if (rs->dst_size != PQgetlength (result, row, fnum)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "field '%s' has wrong size (got %u, expected %u)\n", + rs->fname, (int) PQgetlength (result, row, fnum), (int) rs->dst_size); + return GNUNET_SYSERR; + } + res = PQgetvalue (result, row, fnum); + GNUNET_assert (NULL != res); + memcpy (rs->dst, res, rs->dst_size); + } + if (GNUNET_YES == had_null) + return GNUNET_NO; + return GNUNET_YES; +} + + +int +TALER_DB_field_isnull (PGresult *result, + int row, + const char *fname) +{ + int fnum; + fnum = PQfnumber (result, fname); + GNUNET_assert (fnum >= 0); + if (PQgetisnull (result, row, fnum)) + return GNUNET_YES; + return GNUNET_NO; +} + + +int +TALER_DB_extract_amount_nbo (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_AmountNBO *r_amount_nbo) +{ + int val_num; + int frac_num; + int curr_num; + int len; + + GNUNET_assert (NULL != strstr (val_name, "_val")); + GNUNET_assert (NULL != strstr (frac_name, "_frac")); + GNUNET_assert (NULL != strstr (curr_name, "_curr")); + + val_num = PQfnumber (result, val_name); + GNUNET_assert (val_num >= 0); + frac_num = PQfnumber (result, frac_name); + GNUNET_assert (frac_num >= 0); + curr_num = PQfnumber (result, curr_name); + GNUNET_assert (curr_num >= 0); + + r_amount_nbo->value = *(uint32_t *) PQgetvalue (result, row, val_num); + r_amount_nbo->fraction = *(uint32_t *) PQgetvalue (result, row, frac_num); + memset (r_amount_nbo->currency, 0, TALER_CURRENCY_LEN); + // FIXME: overflow? + len = PQgetlength (result, row, curr_num); + len = GNUNET_MIN (TALER_CURRENCY_LEN, len); + memcpy (r_amount_nbo->currency, PQgetvalue (result, row, curr_num), len); + r_amount_nbo->currency[TALER_CURRENCY_LEN - 1] = '\0'; + + return GNUNET_OK; +} + + +int +TALER_DB_extract_amount (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_Amount *r_amount) +{ + struct TALER_AmountNBO amount_nbo; + + (void) + TALER_DB_extract_amount_nbo (result, + row, + val_name, + frac_name, + curr_name, + &amount_nbo); + r_amount->value = ntohl (amount_nbo.value); + r_amount->fraction = ntohl (amount_nbo.fraction); + (void) strncpy (r_amount->currency, amount_nbo.currency, TALER_CURRENCY_LEN); + + return GNUNET_OK; +} + +/* end of util/db.c */ diff --git a/src/util/json.c b/src/util/json.c new file mode 100644 index 000000000..269e6cf26 --- /dev/null +++ b/src/util/json.c @@ -0,0 +1,194 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file util/json.c + * @brief helper functions for JSON processing using libjansson + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_json_lib.h" + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +/** + * Print JSON parsing related error information + */ +#define WARN_JSON(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)", \ + __FILE__, __LINE__, error.text, error.source) + +/** + * Shorthand for JSON parsing related exit jumps. + */ +#define UNPACK_EXITIF(cond) \ + do { \ + if (cond) { WARN_JSON(error); goto EXITIF_exit; } \ + } while (0) + +/** + * Convert a TALER amount to a JSON + * object. + * + * @param amount the amount + * @return a json object describing the amount + */ +json_t * +TALER_JSON_from_amount (struct TALER_Amount amount) +{ + json_t *j; + j = json_pack ("{s: s, s:I, s:I}", + "currency", amount.currency, + "value", (json_int_t) amount.value, + "fraction", (json_int_t) amount.fraction); + GNUNET_assert (NULL != j); + return j; +} + + +/** + * Convert absolute timestamp to a json string. + * + * @param the time stamp + * @return a json string with the timestamp in @a stamp + */ +json_t * +TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp) +{ + json_t *j; + char *mystr; + int ret; + ret = GNUNET_asprintf (&mystr, "%llu", + (long long) (stamp.abs_value_us / (1000 * 1000))); + GNUNET_assert (ret > 0); + j = json_string (mystr); + GNUNET_free (mystr); + return j; +} + + + +/** + * Convert binary data to a JSON string + * with the base32crockford encoding. + * + * @param data binary data + * @param size size of @a data in bytes + * @return json string that encodes @a data + */ +json_t * +TALER_JSON_from_data (const void *data, size_t size) +{ + char *buf; + json_t *json; + buf = TALER_data_to_string_alloc (data, size); + json = json_string (buf); + GNUNET_free (buf); + return json; +} + + +/** + * Parse given JSON object to Amount + * + * @param json the json object representing Amount + * @param r_amount where the amount has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_amount (json_t *json, + struct TALER_Amount *r_amount) +{ + char *currency; + json_int_t value; + json_int_t fraction; + json_error_t error; + + UNPACK_EXITIF (0 != json_unpack_ex (json, &error, JSON_STRICT, + "{s:s, s:I, s:I}", + "curreny", ¤cy, + "value", &value, + "fraction", &fraction)); + EXITIF (3 < strlen (currency)); + r_amount->value = (uint32_t) value; + r_amount->fraction = (uint32_t) fraction; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + + +/** + * Parse given JSON object to Amount + * + * @param json the json object representing Amount + * @param r_amount where the amount has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_abs (json_t *json, + struct GNUNET_TIME_Absolute *abs) +{ + const char *str; + unsigned long long abs_value_s; + + GNUNET_assert (NULL != abs); + EXITIF (NULL == (str = json_string_value (json))); + EXITIF (1 > sscanf (str, "%llu", &abs_value_s)); + abs->abs_value_us = abs_value_s * 1000 * 1000; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + +/** + * Parse given JSON object to data + * + * @param json the json object representing data + * @param out the pointer to hold the parsed data. + * @param out_size the size of r_data. + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_data (json_t *json, + void *out, + size_t out_size) +{ + const char *enc; + unsigned int len; + + EXITIF (NULL == (enc = json_string_value (json))); + len = strlen (enc); + EXITIF ((((len * 5) / 8) + ((((len * 5) % 8) == 0) ? 0 : 1)) == out_size); + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, len, out, out_size)); + return GNUNET_OK; + EXITIF_exit: + return GNUNET_SYSERR; +} + +/* End of util/json.c */ diff --git a/src/util/microhttpd.c b/src/util/microhttpd.c new file mode 100644 index 000000000..f3bea74f8 --- /dev/null +++ b/src/util/microhttpd.c @@ -0,0 +1,417 @@ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_microhttpd_lib.h" + + + +/** + * Initial size for POST + * request buffer. + */ +#define REQUEST_BUFFER_INITIAL 1024 + +/** + * Maximum POST request size + */ +#define REQUEST_BUFFER_MAX (1024*1024) + + +/** + * Buffer for POST requests. + */ +struct Buffer +{ + /** + * Allocated memory + */ + char *data; + + /** + * Number of valid bytes in buffer. + */ + size_t fill; + + /** + * Number of allocated bytes in buffer. + */ + size_t alloc; +}; + + +/** + * Initialize a buffer. + * + * @param buf the buffer to initialize + * @param data the initial data + * @param data_size size of the initial data + * @param alloc_size size of the buffer + * @param max_size maximum size that the buffer can grow to + * @return a GNUnet result code + */ +static int +buffer_init (struct Buffer *buf, const void *data, size_t data_size, size_t alloc_size, size_t max_size) +{ + if (data_size > max_size || alloc_size > max_size) + return GNUNET_SYSERR; + if (data_size > alloc_size) + alloc_size = data_size; + buf->data = GNUNET_malloc (alloc_size); + memcpy (buf->data, data, data_size); + return GNUNET_OK; +} + + +/** + * Free the data in a buffer. Does *not* free + * the buffer object itself. + * + * @param buf buffer to de-initialize + */ +static void +buffer_deinit (struct Buffer *buf) +{ + GNUNET_free (buf->data); + buf->data = NULL; +} + + +/** + * Append data to a buffer, growing the buffer if necessary. + * + * @param buf the buffer to append to + * @param data the data to append + * @param size the size of @a data + * @param max_size maximum size that the buffer can grow to + * @return GNUNET_OK on success, + * GNUNET_NO if the buffer can't accomodate for the new data + * GNUNET_SYSERR on fatal error (out of memory?) + */ +static int +buffer_append (struct Buffer *buf, const void *data, size_t data_size, size_t max_size) +{ + if (buf->fill + data_size > max_size) + return GNUNET_NO; + if (data_size + buf->fill > buf->alloc) + { + char *new_buf; + size_t new_size = buf->alloc; + while (new_size < buf->fill + data_size) + new_size += 2; + if (new_size > max_size) + return GNUNET_NO; + new_buf = GNUNET_malloc (new_size); + memcpy (new_buf, buf->data, buf->fill); + buf->data = new_buf; + buf->alloc = new_size; + } + memcpy (buf->data + buf->fill, data, data_size); + buf->fill += data_size; + return GNUNET_OK; +} + + +/** + * Send JSON object as response. Decreases the reference count of the + * JSON object. + * + * @param connection the MHD connection + * @param json the json object + * @param status_code the http status code + * @return MHD result code + */ +int +send_response_json (struct MHD_Connection *connection, + json_t *json, + unsigned int status_code) +{ + struct MHD_Response *resp; + char *json_str; + + json_str = json_dumps (json, JSON_INDENT(2)); + json_decref (json); + resp = MHD_create_response_from_buffer (strlen (json_str), json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + return MHD_NO; + return MHD_queue_response (connection, status_code, resp); +} + + +/** + * Send a JSON object via an MHD connection, + * specified with the JANSSON pack syntax (see json_pack). + * + * @param connection connection to send the JSON over + * @param http_code HTTP status for the response + * @param fmt format string for pack + * @param ... varargs + * @return MHD_YES on success or MHD_NO on error + */ +int +request_send_json_pack (struct MHD_Connection *connection, + unsigned int http_code, + const char *fmt, ...) +{ + json_t *msg; + va_list argp; + int ret; + + va_start(argp, fmt); + msg = json_vpack_ex (NULL, 0, fmt, argp); + va_end(argp); + if (NULL == msg) + return MHD_NO; + ret = send_response_json (connection, msg, http_code); + json_decref (msg); + return ret; +} + + +/** + * Process a POST request containing a JSON object. + * + * @param connection the MHD connection + * @param con_cs the closure (contains a 'struct Buffer *') + * @param upload_data the POST data + * @param upload_data_size the POST data size + * @param json the JSON object for a completed request + * + * @returns + * GNUNET_YES if json object was parsed + * GNUNET_NO is request incomplete or invalid + * GNUNET_SYSERR on internal error + */ +int +process_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json) +{ + struct Buffer *r = *con_cls; + + if (NULL == *con_cls) + { + /* We are seeing a fresh POST request. */ + + r = GNUNET_new (struct Buffer); + if (GNUNET_OK != buffer_init (r, upload_data, *upload_data_size, + REQUEST_BUFFER_INITIAL, REQUEST_BUFFER_MAX)) + { + *con_cls = NULL; + buffer_deinit (r); + GNUNET_free (r); + return GNUNET_SYSERR; + } + *upload_data_size = 0; + *con_cls = r; + return GNUNET_NO; + } + if (0 != *upload_data_size) + { + /* We are seeing an old request with more data available. */ + + if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, + REQUEST_BUFFER_MAX)) + { + /* Request too long or we're out of memory. */ + + *con_cls = NULL; + buffer_deinit (r); + GNUNET_free (r); + return GNUNET_SYSERR; + } + *upload_data_size = 0; + return GNUNET_NO; + } + + /* We have seen the whole request. */ + + *json = json_loadb (r->data, r->fill, 0, NULL); + buffer_deinit (r); + GNUNET_free (r); + if (NULL == *json) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Can't parse JSON request body\n"); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + GNUNET_NO, GNUNET_SYSERR, + "{s:s}", + "error", "invalid json"); + } + *con_cls = NULL; + + return GNUNET_YES; +} + + +/** + * Navigate through a JSON tree. + * + * Sends an error response if navigation is impossible (i.e. + * the JSON object is invalid) + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param ... navigation specification (see JNAV_*) + * @return GNUNET_YES if navigation was successful + * GNUNET_NO if json is malformed, error response was generated + * GNUNET_SYSERR on internal error + */ +int +request_json_require_nav (struct MHD_Connection *connection, + const json_t *root, ...) +{ + va_list argp; + int ignore = GNUNET_NO; + // what's our current path from 'root'? + json_t *path; + + path = json_array (); + + va_start(argp, root); + + while (1) + { + int command = va_arg(argp, int); + switch (command) + { + case JNAV_FIELD: + { + const char *fname = va_arg(argp, const char *); + if (GNUNET_YES == ignore) + break; + json_array_append_new (path, json_string (fname)); + root = json_object_get (root, fname); + if (NULL == root) + { + + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s,s:o}", + "error", "missing field in JSON", + "path", path); + ignore = GNUNET_YES; + break; + } + } + break; + case JNAV_INDEX: + { + int fnum = va_arg(argp, int); + if (GNUNET_YES == ignore) + break; + json_array_append_new (path, json_integer (fnum)); + root = json_array_get (root, fnum); + if (NULL == root) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:o}", + "error", "missing index in JSON", + "path", path); + ignore = GNUNET_YES; + break; + } + } + break; + case JNAV_RET_DATA: + { + void *where = va_arg (argp, void *); + size_t len = va_arg (argp, size_t); + const char *str; + int res; + + va_end(argp); + if (GNUNET_YES == ignore) + return GNUNET_NO; + str = json_string_value (root); + if (NULL == str) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:o}", + "error", "string expected", + "path", path); + return GNUNET_NO; + } + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + where, len); + if (GNUNET_OK != res) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s,s:o}", + "error", "malformed binary data in JSON", + "path", path); + return GNUNET_NO; + } + return GNUNET_YES; + } + break; + case JNAV_RET_DATA_VAR: + { + void **where = va_arg (argp, void **); + size_t *len = va_arg (argp, size_t *); + const char *str; + + va_end(argp); + if (GNUNET_YES == ignore) + return GNUNET_NO; + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + *len = (strlen (str) * 5) / 8; + if (where != NULL) + { + int res; + *where = GNUNET_malloc (*len); + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + *where, *len); + if (GNUNET_OK != res) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:o}", + "error", "malformed binary data in JSON", + "path", path); + return GNUNET_NO; + } + } + return GNUNET_OK; + } + break; + case JNAV_RET_TYPED_JSON: + { + int typ = va_arg (argp, int); + const json_t **r_json = va_arg (argp, const json_t **); + + va_end(argp); + if (GNUNET_YES == ignore) + return GNUNET_NO; + if (typ != -1 && json_typeof (root) != typ) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:i, s:i s:o}", + "error", "wrong JSON field type", + "type_expected", typ, + "type_actual", json_typeof (root), + "path", path); + return GNUNET_NO; + } + *r_json = root; + return GNUNET_OK; + } + break; + default: + GNUNET_assert (0); + } + } + GNUNET_assert (0); +} + + + diff --git a/src/util/misc.supp b/src/util/misc.supp new file mode 100644 index 000000000..afcac6128 --- /dev/null +++ b/src/util/misc.supp @@ -0,0 +1,28 @@ +{ + <gnunet_gcrypt_init> + Memcheck:Leak + match-leak-kinds:reachable + ... + fun:GNUNET_CRYPTO_random_init + fun:call_init.part.0 + ... +} + +{ + <mpi_ec_new> + Memcheck:Leak + match-leak-kinds:reachable + ... + fun:point_from_keyparam + fun:_gcry_mpi_ec_new + ... +} + +{ + <gnunet_log_setup> + Memcheck:Leak + match-leak-kinds:reachable + ... + fun:GNUNET_log_setup + ... +}
\ No newline at end of file diff --git a/src/util/rsa.c b/src/util/rsa.c new file mode 100644 index 000000000..cde56be9e --- /dev/null +++ b/src/util/rsa.c @@ -0,0 +1,925 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file util/rsa.c + * @brief RSA key management utilities. Most of the code here is taken from + * gnunet-0.9.5a + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * + * Authors of the gnunet code: + * Christian Grothoff + * Krista Bennett + * Gerd Knorr <kraxel@bytesex.org> + * Ioana Patrascu + * Tzvetan Horozov + */ + +#include "platform.h" +#include "gcrypt.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_rsa.h" + + + +#define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__) + +#define LOG_STRERROR(kind,syscall) GNUNET_log_from_strerror (kind, "util", syscall) + +#define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util", syscall, filename) + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' with the message given + * by gcry_strerror(rc). + */ +#define LOG_GCRY(level, cmd, rc) do { LOG(level, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, gcry_strerror(rc)); } while(0) + +/** + * Shorthand to cleanup non null mpi data types + */ +#define mpi_release_non_null(mpi) \ + if (NULL != mpi) gcry_mpi_release (mpi); + +/** + * The private information of an RSA key pair. + * NOTE: this must match the definition in crypto_ksk.c and gnunet-rsa.c! + */ +struct TALER_RSA_PrivateKey +{ + /** + * Libgcrypt S-expression for the ECC key. + */ + gcry_sexp_t sexp; +}; + + +/** + * Extract values from an S-expression. + * + * @param array where to store the result(s) + * @param sexp S-expression to parse + * @param topname top-level name in the S-expression that is of interest + * @param elems names of the elements to extract + * @return 0 on success + */ +static int +key_from_sexp (gcry_mpi_t * array, gcry_sexp_t sexp, const char *topname, + const char *elems) +{ + gcry_sexp_t list; + gcry_sexp_t l2; + const char *s; + unsigned int i; + unsigned int idx; + + if (! (list = gcry_sexp_find_token (sexp, topname, 0))) + return 1; + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + if (! list) + return 2; + idx = 0; + for (s = elems; *s; s++, idx++) + { + if (! (l2 = gcry_sexp_find_token (list, s, 1))) + { + for (i = 0; i < idx; i++) + { + gcry_free (array[i]); + array[i] = NULL; + } + gcry_sexp_release (list); + return 3; /* required parameter not found */ + } + array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + if (! array[idx]) + { + for (i = 0; i < idx; i++) + { + gcry_free (array[i]); + array[i] = NULL; + } + gcry_sexp_release (list); + return 4; /* required parameter is invalid */ + } + } + gcry_sexp_release (list); + return 0; +} + +/** + * If target != size, move target bytes to the + * end of the size-sized buffer and zero out the + * first target-size bytes. + * + * @param buf original buffer + * @param size number of bytes in the buffer + * @param target target size of the buffer + */ +static void +adjust (unsigned char *buf, size_t size, size_t target) +{ + if (size < target) + { + memmove (&buf[target - size], buf, size); + memset (buf, 0, target - size); + } +} + + +/** + * Create a new private key. Caller must free return value. + * + * @return fresh private key + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_key_create () +{ + struct TALER_RSA_PrivateKey *ret; + gcry_sexp_t s_key; + gcry_sexp_t s_keyparam; + + GNUNET_assert (0 == + gcry_sexp_build (&s_keyparam, NULL, + "(genkey(rsa(nbits %d)(rsa-use-e 3:257)))", + 2048)); + GNUNET_assert (0 == gcry_pk_genkey (&s_key, s_keyparam)); + gcry_sexp_release (s_keyparam); +#if EXTRA_CHECKS + GNUNET_assert (0 == gcry_pk_testkey (s_key)); +#endif + ret = GNUNET_malloc (sizeof (struct TALER_RSA_PrivateKey)); + ret->sexp = s_key; + return ret; +} + + +/** + * Free memory occupied by the private key. + * + * @param key pointer to the memory to free + */ +void +TALER_RSA_key_free (struct TALER_RSA_PrivateKey *key) +{ + gcry_sexp_release (key->sexp); + GNUNET_free (key); +} + + +/** + * Encode the private key in a format suitable for + * storing it into a file. + * @return encoding of the private key + */ +struct TALER_RSA_PrivateKeyBinaryEncoded * +TALER_RSA_encode_key (const struct TALER_RSA_PrivateKey *hostkey) +{ + struct TALER_RSA_PrivateKeyBinaryEncoded *retval; + gcry_mpi_t pkv[6]; + void *pbu[6]; + size_t sizes[6]; + int rc; + int i; + int size; + +#if EXTRA_CHECKS + if (gcry_pk_testkey (hostkey->sexp)) + { + GNUNET_break (0); + return NULL; + } +#endif + + memset (pkv, 0, sizeof (gcry_mpi_t) * 6); + rc = key_from_sexp (pkv, hostkey->sexp, "private-key", "nedpqu"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "rsa", "nedpqu"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "private-key", "nedpq"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "rsa", "nedpq"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "private-key", "ned"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "rsa", "ned"); + GNUNET_assert (0 == rc); + size = sizeof (struct TALER_RSA_PrivateKeyBinaryEncoded); + for (i = 0; i < 6; i++) + { + if (NULL != pkv[i]) + { + GNUNET_assert (0 == + gcry_mpi_aprint (GCRYMPI_FMT_USG, + (unsigned char **) &pbu[i], &sizes[i], + pkv[i])); + size += sizes[i]; + } + else + { + pbu[i] = NULL; + sizes[i] = 0; + } + } + GNUNET_assert (size < 65536); + retval = GNUNET_malloc (size); + retval->len = htons (size); + i = 0; + retval->sizen = htons (sizes[0]); + memcpy (&((char *) (&retval[1]))[i], pbu[0], sizes[0]); + i += sizes[0]; + retval->sizee = htons (sizes[1]); + memcpy (&((char *) (&retval[1]))[i], pbu[1], sizes[1]); + i += sizes[1]; + retval->sized = htons (sizes[2]); + memcpy (&((char *) (&retval[1]))[i], pbu[2], sizes[2]); + i += sizes[2]; + /* swap p and q! */ + retval->sizep = htons (sizes[4]); + memcpy (&((char *) (&retval[1]))[i], pbu[4], sizes[4]); + i += sizes[4]; + retval->sizeq = htons (sizes[3]); + memcpy (&((char *) (&retval[1]))[i], pbu[3], sizes[3]); + i += sizes[3]; + retval->sizedmp1 = htons (0); + retval->sizedmq1 = htons (0); + memcpy (&((char *) (&retval[1]))[i], pbu[5], sizes[5]); + for (i = 0; i < 6; i++) + { + if (pkv[i] != NULL) + gcry_mpi_release (pkv[i]); + if (pbu[i] != NULL) + free (pbu[i]); + } + return retval; +} + + +/** + * Extract the public key of the given private key. + * + * @param priv the private key + * @param pub where to write the public key + */ +void +TALER_RSA_key_get_public (const struct TALER_RSA_PrivateKey *priv, + struct TALER_RSA_PublicKeyBinaryEncoded *pub) +{ + gcry_mpi_t skey[2]; + size_t size; + int rc; + + rc = key_from_sexp (skey, priv->sexp, "public-key", "ne"); + if (0 != rc) + rc = key_from_sexp (skey, priv->sexp, "private-key", "ne"); + if (0 != rc) + rc = key_from_sexp (skey, priv->sexp, "rsa", "ne"); + GNUNET_assert (0 == rc); + pub->len = + htons (sizeof (struct TALER_RSA_PublicKeyBinaryEncoded) - + sizeof (pub->padding)); + pub->sizen = htons (TALER_RSA_DATA_ENCODING_LENGTH); + pub->padding = 0; + size = TALER_RSA_DATA_ENCODING_LENGTH; + GNUNET_assert (0 == + gcry_mpi_print (GCRYMPI_FMT_USG, &pub->key[0], size, &size, + skey[0])); + adjust (&pub->key[0], size, TALER_RSA_DATA_ENCODING_LENGTH); + size = TALER_RSA_KEY_LENGTH - TALER_RSA_DATA_ENCODING_LENGTH; + GNUNET_assert (0 == + gcry_mpi_print (GCRYMPI_FMT_USG, + &pub->key + [TALER_RSA_DATA_ENCODING_LENGTH], size, + &size, skey[1])); + adjust (&pub->key[TALER_RSA_DATA_ENCODING_LENGTH], size, + TALER_RSA_KEY_LENGTH - + TALER_RSA_DATA_ENCODING_LENGTH); + gcry_mpi_release (skey[0]); + gcry_mpi_release (skey[1]); +} + + +/** + * Decode the private key from the data-format back + * to the "normal", internal format. + * + * @param buf the buffer where the private key data is stored + * @param len the length of the data in 'buffer' + * @return NULL on error + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_decode_key (const char *buf, uint16_t len) +{ + struct TALER_RSA_PrivateKey *ret; + const struct TALER_RSA_PrivateKeyBinaryEncoded *encoding = + (const struct TALER_RSA_PrivateKeyBinaryEncoded *) buf; + gcry_sexp_t res; + gcry_mpi_t n; + gcry_mpi_t e; + gcry_mpi_t d; + gcry_mpi_t p; + gcry_mpi_t q; + gcry_mpi_t u; + int rc; + size_t size; + size_t pos; + uint16_t enc_len; + size_t erroff; + + enc_len = ntohs (encoding->len); + if (len != enc_len) + return NULL; + + pos = 0; + size = ntohs (encoding->sizen); + rc = gcry_mpi_scan (&n, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizen); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + return NULL; + } + size = ntohs (encoding->sizee); + rc = gcry_mpi_scan (&e, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizee); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + return NULL; + } + size = ntohs (encoding->sized); + rc = gcry_mpi_scan (&d, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sized); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + return NULL; + } + /* swap p and q! */ + size = ntohs (encoding->sizep); + if (size > 0) + { + rc = gcry_mpi_scan (&q, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizep); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + return NULL; + } + } + else + q = NULL; + size = ntohs (encoding->sizeq); + if (size > 0) + { + rc = gcry_mpi_scan (&p, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizeq); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + if (NULL != q) + gcry_mpi_release (q); + return NULL; + } + } + else + p = NULL; + pos += ntohs (encoding->sizedmp1); + pos += ntohs (encoding->sizedmq1); + size = + ntohs (encoding->len) - sizeof (struct TALER_RSA_PrivateKeyBinaryEncoded) - pos; + if (size > 0) + { + rc = gcry_mpi_scan (&u, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + if (NULL != p) + gcry_mpi_release (p); + if (NULL != q) + gcry_mpi_release (q); + return NULL; + } + } + else + u = NULL; + + if ((NULL != p) && (NULL != q) && (NULL != u)) + { + rc = gcry_sexp_build (&res, &erroff, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", + n, e, d, p, q, u); + } + else + { + if ((NULL != p) && (NULL != q)) + { + rc = gcry_sexp_build (&res, &erroff, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)))", + n, e, d, p, q); + } + else + { + rc = gcry_sexp_build (&res, &erroff, + "(private-key(rsa(n %m)(e %m)(d %m)))", n, e, d); + } + } + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + if (NULL != p) + gcry_mpi_release (p); + if (NULL != q) + gcry_mpi_release (q); + if (NULL != u) + gcry_mpi_release (u); + + if (0 != rc) + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc); + if (0 != (rc = gcry_pk_testkey (res))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_pk_testkey", rc); + return NULL; + } + ret = GNUNET_malloc (sizeof (struct TALER_RSA_PrivateKey)); + ret->sexp = res; + return ret; +} + + +/** + * Convert a public key to a string. + * + * @param pub key to convert + * @return string representing 'pub' + */ +char * +TALER_RSA_public_key_to_string (const struct TALER_RSA_PublicKeyBinaryEncoded *pub) +{ + char *pubkeybuf; + size_t keylen = (sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)) * 8; + char *end; + + if (keylen % 5 > 0) + keylen += 5 - keylen % 5; + keylen /= 5; + pubkeybuf = GNUNET_malloc (keylen + 1); + end = GNUNET_STRINGS_data_to_string ((unsigned char *) pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded), + pubkeybuf, + keylen); + if (NULL == end) + { + GNUNET_free (pubkeybuf); + return NULL; + } + *end = '\0'; + return pubkeybuf; +} + + +/** + * Convert a string representing a public key to a public key. + * + * @param enc encoded public key + * @param enclen number of bytes in enc (without 0-terminator) + * @param pub where to store the public key + * @return GNUNET_OK on success + */ +int +TALER_RSA_public_key_from_string (const char *enc, + size_t enclen, + struct TALER_RSA_PublicKeyBinaryEncoded *pub) +{ + size_t keylen = (sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)) * 8; + + if (keylen % 5 > 0) + keylen += 5 - keylen % 5; + keylen /= 5; + if (enclen != keylen) + return GNUNET_SYSERR; + + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, enclen, + (unsigned char*) pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))) + return GNUNET_SYSERR; + if ( (ntohs (pub->len) != sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)) || + (ntohs (pub->padding) != 0) || + (ntohs (pub->sizen) != TALER_RSA_DATA_ENCODING_LENGTH) ) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Convert the data specified in the given purpose argument to an + * S-expression suitable for signature operations. + * + * @param ptr pointer to the data to convert + * @param size the size of the data + * @return converted s-expression + */ +static gcry_sexp_t +data_to_sexp (const void *ptr, size_t size) +{ + gcry_mpi_t value; + gcry_sexp_t data; + + value = NULL; + data = NULL; + GNUNET_assert (0 == gcry_mpi_scan (&value, GCRYMPI_FMT_USG, ptr, size, NULL)); + GNUNET_assert (0 == gcry_sexp_build (&data, NULL, "(data (flags raw) (value %M))", value)); + gcry_mpi_release (value); + return data; +} + + +/** + * Sign the given hash block. + * + * @param key private key to use for the signing + * @param hash the block containing the hash of the message to sign + * @param hash_size the size of the hash block + * @param sig where to write the signature + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +int +TALER_RSA_sign (const struct TALER_RSA_PrivateKey *key, + const void *hash, + size_t hash_size, + struct TALER_RSA_Signature *sig) +{ + gcry_sexp_t result; + gcry_sexp_t data; + size_t ssize; + gcry_mpi_t rval; + + data = data_to_sexp (hash, hash_size); + GNUNET_assert (0 == gcry_pk_sign (&result, data, key->sexp)); + gcry_sexp_release (data); + GNUNET_assert (0 == key_from_sexp (&rval, result, "rsa", "s")); + gcry_sexp_release (result); + ssize = sizeof (struct TALER_RSA_Signature); + GNUNET_assert (0 == + gcry_mpi_print (GCRYMPI_FMT_USG, (unsigned char *) sig, ssize, + &ssize, rval)); + gcry_mpi_release (rval); + adjust (sig->sig, ssize, sizeof (struct TALER_RSA_Signature)); + return GNUNET_OK; +} + + +/** + * Convert the given public key from the network format to the + * S-expression that can be used by libgcrypt. + * + * @param publicKey public key to decode + * @return NULL on error + */ +static gcry_sexp_t +decode_public_key (const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey) +{ + gcry_sexp_t result; + gcry_mpi_t n; + gcry_mpi_t e; + size_t size; + size_t erroff; + int rc; + + if ((ntohs (publicKey->sizen) != TALER_RSA_DATA_ENCODING_LENGTH) || + (ntohs (publicKey->len) != + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded) - + sizeof (publicKey->padding))) + { + GNUNET_break (0); + return NULL; + } + size = TALER_RSA_DATA_ENCODING_LENGTH; + if (0 != (rc = gcry_mpi_scan (&n, GCRYMPI_FMT_USG, &publicKey->key[0], size, &size))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + return NULL; + } + size = TALER_RSA_KEY_LENGTH - TALER_RSA_DATA_ENCODING_LENGTH; + if (0 != (rc = gcry_mpi_scan (&e, GCRYMPI_FMT_USG, + &publicKey->key[TALER_RSA_DATA_ENCODING_LENGTH], + size, &size))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + return NULL; + } + rc = gcry_sexp_build (&result, &erroff, "(public-key(rsa(n %m)(e %m)))", n, + e); + gcry_mpi_release (n); + gcry_mpi_release (e); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc); /* erroff gives more info */ + return NULL; + } + return result; +} + + +/** + * Verify signature with the given hash. + * + * @param hash the hash code to verify against the signature + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_hash_verify (const struct GNUNET_HashCode *hash, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey) +{ + gcry_sexp_t data; + gcry_sexp_t sigdata; + size_t size; + gcry_mpi_t val; + gcry_sexp_t psexp; + size_t erroff; + int rc; + + size = sizeof (struct TALER_RSA_Signature); + GNUNET_assert (0 == + gcry_mpi_scan (&val, GCRYMPI_FMT_USG, + (const unsigned char *) sig, size, &size)); + GNUNET_assert (0 == + gcry_sexp_build (&sigdata, &erroff, "(sig-val(rsa(s %m)))", + val)); + gcry_mpi_release (val); + data = data_to_sexp (hash, sizeof (struct GNUNET_HashCode)); + if (! (psexp = decode_public_key (publicKey))) + { + gcry_sexp_release (data); + gcry_sexp_release (sigdata); + return GNUNET_SYSERR; + } + rc = gcry_pk_verify (sigdata, data, psexp); + gcry_sexp_release (psexp); + gcry_sexp_release (data); + gcry_sexp_release (sigdata); + if (rc) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("RSA signature verification failed at %s:%d: %s\n"), __FILE__, + __LINE__, gcry_strerror (rc)); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify signature on the given message + * + * @param msg the message + * @param size the size of the message + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_verify (const void *msg, size_t size, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey) +{ + struct GNUNET_HashCode hash; + + GNUNET_CRYPTO_hash (msg, size, &hash); + return TALER_RSA_hash_verify (&hash, sig, publicKey); +} + +/** + * The blinding key is equal in length to the RSA modulus + */ +#define TALER_RSA_BLINDING_KEY_LEN TALER_RSA_DATA_ENCODING_LENGTH + +struct TALER_RSA_BlindingKey +{ + /** + * The blinding factor + */ + gcry_mpi_t r; +}; + +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_create () +{ + struct TALER_RSA_BlindingKey *blind; + + blind = GNUNET_new (struct TALER_RSA_BlindingKey); + blind->r = gcry_mpi_new (TALER_RSA_BLINDING_KEY_LEN * 8); + gcry_mpi_randomize (blind->r, TALER_RSA_BLINDING_KEY_LEN * 8, GCRY_STRONG_RANDOM); + return blind; +} + + +void +TALER_RSA_blinding_key_destroy (struct TALER_RSA_BlindingKey *bkey) +{ + gcry_mpi_release (bkey->r); + GNUNET_free (bkey); +} + + +struct TALER_RSA_BlindedSignaturePurpose * +TALER_RSA_message_blind (const void *msg, size_t size, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey) +{ + struct TALER_RSA_BlindedSignaturePurpose *bsp; + struct GNUNET_HashCode hash; + gcry_sexp_t psexp; + gcry_mpi_t data; + gcry_mpi_t skey[2]; + gcry_mpi_t r_e; + gcry_mpi_t data_r_e; + size_t rsize; + gcry_error_t rc; + int ret; + + bsp = NULL; + psexp = NULL; + data = NULL; + skey[0] = skey[1] = NULL; + r_e = NULL; + data_r_e = NULL; + rsize = 0; + rc = 0; + ret = 0; + if (! (psexp = decode_public_key (pkey))) + return NULL; + ret = key_from_sexp (skey, psexp, "public-key", "ne"); + if (0 != ret) + ret = key_from_sexp (skey, psexp, "rsa", "ne"); + gcry_sexp_release (psexp); + psexp = NULL; + GNUNET_assert (0 == ret); + GNUNET_CRYPTO_hash (msg, size, &hash); + if (0 != (rc=gcry_mpi_scan (&data, GCRYMPI_FMT_USG, + (const unsigned char *) msg, size, &rsize))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_WARNING, "gcry_mpi_scan", rc); + goto cleanup; + } + r_e = gcry_mpi_new (0); + gcry_mpi_powm (r_e, bkey->r, + skey[1], /* e */ + skey[0]); /* n */ + + data_r_e = gcry_mpi_new (0); + gcry_mpi_mulm (data_r_e, data, r_e, skey[0]); + + bsp = GNUNET_new (struct TALER_RSA_BlindedSignaturePurpose); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, + (unsigned char *) bsp, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &rsize, + data_r_e); + GNUNET_assert (0 == rc); + adjust ((unsigned char *) bsp, rsize, + sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + + cleanup: + if (NULL != psexp) gcry_sexp_release (psexp); + mpi_release_non_null (skey[0]); + mpi_release_non_null (skey[1]); + mpi_release_non_null (data); + mpi_release_non_null (r_e); + mpi_release_non_null (data_r_e); + return bsp; +} + + +int +TALER_RSA_unblind (struct TALER_RSA_Signature *sig, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey) +{ + gcry_sexp_t psexp; + gcry_mpi_t skey; + gcry_mpi_t sigval; + gcry_mpi_t r_inv; + gcry_mpi_t ubsig; + size_t rsize; + gcry_error_t rc; + int ret; + + psexp = NULL; + skey = NULL; + sigval = NULL; + r_inv = NULL; + ubsig = NULL; + rsize = 0; + rc = 0; + ret = GNUNET_SYSERR; + if (! (psexp = decode_public_key (pkey))) + return GNUNET_SYSERR; + ret = key_from_sexp (&skey, psexp, "public-key", "n"); + if (0 != ret) + ret = key_from_sexp (&skey, psexp, "rsa", "n"); + gcry_sexp_release (psexp); + psexp = NULL; + if (0 != (rc = gcry_mpi_scan (&sigval, GCRYMPI_FMT_USG, + (const unsigned char *) sig, + sizeof (struct TALER_RSA_Signature), + &rsize))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + goto cleanup; + } + r_inv = gcry_mpi_new (0); + GNUNET_assert (1 == gcry_mpi_invm (r_inv, bkey->r, skey)); /* n: skey */ + ubsig = gcry_mpi_new (0); + gcry_mpi_mulm (ubsig, sigval, r_inv, skey); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, + (unsigned char *) sig, + sizeof (struct TALER_RSA_Signature), + &rsize, + ubsig); + GNUNET_assert (0 == rc); + adjust ((unsigned char *) sig, rsize, sizeof (struct TALER_RSA_Signature)); + ret = GNUNET_OK; + + cleanup: + if (NULL != psexp) gcry_sexp_release (psexp); + mpi_release_non_null (skey); + mpi_release_non_null (sigval); + mpi_release_non_null (r_inv); + mpi_release_non_null (ubsig); + return ret; +} + + +/** + * Encode a blinding key + * + * @param bkey the blinding key to encode + * @param bkey_enc where to store the encoded binary key + * @return #GNUNET_OK upon successful encoding; #GNUNET_SYSERR upon failure + */ +int +TALER_RSA_blinding_key_encode (struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc) +{ + GNUNET_abort (); /* FIXME: not implemented */ +} + + +/** + * Decode a blinding key from its encoded form + * + * @param bkey_enc the encoded blinding key + * @return the decoded blinding key; NULL upon error + */ +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_decode (struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc) +{ + GNUNET_abort (); /* FIXME: not implemented */ +} + +/* end of util/rsa.c */ diff --git a/src/util/test_hash_context.c b/src/util/test_hash_context.c new file mode 100644 index 000000000..e5110f212 --- /dev/null +++ b/src/util/test_hash_context.c @@ -0,0 +1,48 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file util/test_hash_context.c + * @brief test case for incremental hashing + * @author Florian Dold + */ + +#include "platform.h" +#include "taler_util.h" +#include <gcrypt.h> + +#define LEN 1234 + +int main() +{ + char data[1234]; + struct GNUNET_HashCode hc1; + struct GNUNET_HashCode hc2; + struct TALER_HashContext hctx; + + memset (data, 42, LEN); + + TALER_hash_context_start (&hctx); + TALER_hash_context_read (&hctx, data, LEN); + TALER_hash_context_finish (&hctx, &hc1); + + GNUNET_CRYPTO_hash (data, LEN, &hc2); + + if (0 == memcmp (&hc1, &hc2, sizeof (struct GNUNET_HashCode))) + return 0; + return 1; +} + diff --git a/src/util/test_rsa.c b/src/util/test_rsa.c new file mode 100644 index 000000000..ac3ae2cd4 --- /dev/null +++ b/src/util/test_rsa.c @@ -0,0 +1,112 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file util/test_rsa.c + * @brief testcase for utility functions for RSA cryptography + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "taler_rsa.h" +#include <gnunet/gnunet_util_lib.h> + +#define TEST_PURPOSE UINT32_MAX + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +int +main (int argc, char *argv[]) +{ +#define RND_BLK_SIZE 4096 + unsigned char rnd_blk[RND_BLK_SIZE]; + struct TALER_RSA_PrivateKey *priv; + struct TALER_RSA_PrivateKeyBinaryEncoded *priv_enc; + struct TALER_RSA_PublicKeyBinaryEncoded pubkey; + struct TALER_RSA_BlindingKey *bkey; + struct TALER_RSA_BlindedSignaturePurpose *bsp; + struct TALER_RSA_Signature sig; + struct GNUNET_HashCode hash; + int ret; + + priv = NULL; + bsp = NULL; + bkey = NULL; + ret = 1; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, rnd_blk, + RND_BLK_SIZE); + GNUNET_CRYPTO_hash (rnd_blk, RND_BLK_SIZE, &hash); + priv = TALER_RSA_key_create (); + GNUNET_assert (NULL != priv); + EXITIF (GNUNET_OK != TALER_RSA_sign (priv, + &hash, sizeof (hash), + &sig)); + TALER_RSA_key_get_public (priv, &pubkey); + EXITIF (NULL == (priv_enc = TALER_RSA_encode_key (priv))); + TALER_RSA_key_free (priv); + priv = NULL; + EXITIF (NULL == (priv = TALER_RSA_decode_key ((const char *) priv_enc, + ntohs (priv_enc->len)))); + GNUNET_free (priv_enc); + priv_enc = NULL; + EXITIF (GNUNET_OK != TALER_RSA_hash_verify (&hash, + &sig, + &pubkey)); + EXITIF (GNUNET_OK != TALER_RSA_verify (rnd_blk, + RND_BLK_SIZE, + &sig, + &pubkey)); + + /* test blind signing */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, rnd_blk, + RND_BLK_SIZE); + GNUNET_CRYPTO_hash (rnd_blk, RND_BLK_SIZE, &hash); + (void) memset (&sig, 0, sizeof (struct TALER_RSA_Signature)); + EXITIF (NULL == (bkey = TALER_RSA_blinding_key_create ())); + EXITIF (NULL == (bsp = + TALER_RSA_message_blind (&hash, sizeof (hash), + bkey, &pubkey))); + EXITIF (GNUNET_OK != TALER_RSA_sign (priv, + bsp, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &sig)); + EXITIF (GNUNET_OK != TALER_RSA_unblind (&sig, + bkey, + &pubkey)); + EXITIF (GNUNET_OK != TALER_RSA_hash_verify (&hash, + &sig, + &pubkey)); + ret = 0; /* all OK */ + + EXITIF_exit: + if (NULL != priv) + { + TALER_RSA_key_free (priv); + priv = NULL; + } + if (NULL != priv_enc) + { + GNUNET_free (priv_enc); + priv_enc = NULL; + } + if (NULL != bkey) + TALER_RSA_blinding_key_destroy (bkey); + GNUNET_free_non_null (bsp); + return ret; +} diff --git a/src/util/util.c b/src/util/util.c new file mode 100644 index 000000000..3677bcbde --- /dev/null +++ b/src/util/util.c @@ -0,0 +1,528 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file util.c + * @brief Common utility functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include "platform.h" +#include "taler_util.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_util_lib.h> +#include <gcrypt.h> + +#define CURVE "Ed25519" + +#define AMOUNT_FRAC_BASE 1000000 +#define AMOUNT_FRAC_LEN 6 + + + +static void +fatal_error_handler (void *cls, int wtf, const char *msg) +{ + LOG_ERROR("Fatal error in Gcrypt: %s\n", msg); + abort(); +} + + +/** + * Initialize Gcrypt library. + */ +void +TALER_gcrypt_init() +{ + gcry_set_fatalerror_handler (&fatal_error_handler, NULL); + TALER_assert_as(gcry_check_version(NEED_LIBGCRYPT_VERSION), + "libgcrypt version mismatch"); + /* Disable secure memory. */ + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); +} + + +/** + * Generate a ECC private key. + * + * @return the s-expression representing the generated ECC private key; NULL + * upon error + */ +gcry_sexp_t +TALER_genkey () +{ + gcry_sexp_t priv_sexp; + gcry_sexp_t s_keyparam; + int rc; + + if (0 != (rc = gcry_sexp_build (&s_keyparam, NULL, + "(genkey(ecc(curve \"" CURVE "\")" + "(flags eddsa)))"))) + { + LOG_GCRY_ERROR ("gcry_sexp_build", rc); + return NULL; + } + if (0 != (rc = gcry_pk_genkey (&priv_sexp, s_keyparam))) + { + LOG_GCRY_ERROR ("gcry_pk_genkey", rc); + gcry_sexp_release (s_keyparam); + return NULL; + } + gcry_sexp_release (s_keyparam); + if (0 != (rc = gcry_pk_testkey (priv_sexp))) + { + LOG_GCRY_ERROR("gcry_pk_testkey", rc); + gcry_sexp_release (priv_sexp); + return NULL; + } + return priv_sexp; +} + + +/** + * Parse money amount description, in the format "A:B.C". + * + * @param str amount description + * @param denom amount to write the result to + * @return GNUNET_OK if the string is a valid amount specification, + * GNUNET_SYSERR if it is invalid. + */ +int +TALER_string_to_amount (const char *str, struct TALER_Amount *denom) +{ + unsigned int i; // pos in str + int n; // number tmp + unsigned int c; // currency pos + uint32_t b; // base for suffix + + memset (denom, 0, sizeof (struct TALER_Amount)); + + i = n = c = 0; + + while (isspace(str[i])) + i++; + + if (0 == str[i]) + { + printf("null before currency\n"); + return GNUNET_SYSERR; + } + + while (str[i] != ':') + { + if (0 == str[i]) + { + printf("null before colon"); + return GNUNET_SYSERR; + } + if (c > 3) + { + printf("currency too long\n"); + return GNUNET_SYSERR; + } + denom->currency[c] = str[i]; + c++; + i++; + } + + // skip colon + i++; + + if (0 == str[i]) + { + printf("null before value\n"); + return GNUNET_SYSERR; + } + + while (str[i] != '.') + { + if (0 == str[i]) + { + return GNUNET_OK; + } + n = str[i] - '0'; + if (n < 0 || n > 9) + { + printf("invalid character '%c' before comma at %u\n", (char) n, i); + return GNUNET_SYSERR; + } + denom->value = (denom->value * 10) + n; + i++; + } + + // skip the dot + i++; + + if (0 == str[i]) + { + printf("null after dot"); + return GNUNET_SYSERR; + } + + b = 100000; + + while (0 != str[i]) + { + n = str[i] - '0'; + if (b == 0 || n < 0 || n > 9) + { + printf("error after comma"); + return GNUNET_SYSERR; + } + denom->fraction += n * b; + b /= 10; + i++; + } + + return GNUNET_OK; +} + + +/** + * FIXME + */ +struct TALER_AmountNBO +TALER_amount_hton (struct TALER_Amount d) +{ + struct TALER_AmountNBO dn; + dn.value = htonl (d.value); + dn.fraction = htonl (d.fraction); + memcpy (dn.currency, d.currency, TALER_CURRENCY_LEN); + + return dn; +} + + +/** + * FIXME + */ +struct TALER_Amount +TALER_amount_ntoh (struct TALER_AmountNBO dn) +{ + struct TALER_Amount d; + d.value = ntohl (dn.value); + d.fraction = ntohl (dn.fraction); + memcpy (d.currency, dn.currency, sizeof(dn.currency)); + + return d; +} + + +/** + * Compare the value/fraction of two amounts. Does not compare the currency, + * i.e. comparing amounts with the same value and fraction but different + * currency would return 0. + * + * @param a1 first amount + * @param a2 second amount + * @return result of the comparison + */ +int +TALER_amount_cmp (struct TALER_Amount a1, struct TALER_Amount a2) +{ + a1 = TALER_amount_normalize (a1); + a2 = TALER_amount_normalize (a2); + if (a1.value == a2.value) + { + if (a1.fraction < a2.fraction) + return -1; + if (a1.fraction > a2.fraction) + return 1; + return 0; + } + if (a1.value < a2.value) + return -1; + return 1; +} + + +/** + * Perform saturating subtraction of amounts. + * + * @param a1 amount to subtract from + * @param a2 amount to subtract + * @return (a1-a2) or 0 if a2>=a1 + */ +struct TALER_Amount +TALER_amount_subtract (struct TALER_Amount a1, struct TALER_Amount a2) +{ + a1 = TALER_amount_normalize (a1); + a2 = TALER_amount_normalize (a2); + + if (a1.value < a2.value) + { + a1.value = 0; + a1.fraction = 0; + return a1; + } + + if (a1.fraction < a2.fraction) + { + if (0 == a1.value) + { + a1.fraction = 0; + return a1; + } + a1.fraction += AMOUNT_FRAC_BASE; + a1.value -= 1; + } + + a1.fraction -= a2.fraction; + a1.value -= a2.value; + + return a1; +} + + +/** + * Perform saturating addition of amounts. + * + * @param a1 first amount to add + * @param a2 second amount to add + * @return sum of a1 and a2 + */ +struct TALER_Amount +TALER_amount_add (struct TALER_Amount a1, struct TALER_Amount a2) +{ + a1 = TALER_amount_normalize (a1); + a2 = TALER_amount_normalize (a2); + + a1.value += a2.value; + a1.fraction += a2.fraction; + + if (0 == a1.currency[0]) + { + memcpy (a2.currency, a1.currency, TALER_CURRENCY_LEN); + } + + if (0 == a2.currency[0]) + { + memcpy (a1.currency, a2.currency, TALER_CURRENCY_LEN); + } + + if (0 != a1.currency[0] && 0 != memcmp (a1.currency, a2.currency, TALER_CURRENCY_LEN)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "adding mismatching currencies\n"); + } + + if (a1.value < a2.value) + { + a1.value = UINT32_MAX; + a2.value = UINT32_MAX; + return a1; + } + + return TALER_amount_normalize (a1); +} + + +/** + * Normalize the given amount. + * + * @param amout amount to normalize + * @return normalized amount + */ +struct TALER_Amount +TALER_amount_normalize (struct TALER_Amount amount) +{ + while (amount.value != UINT32_MAX && amount.fraction >= AMOUNT_FRAC_BASE) + { + amount.fraction -= AMOUNT_FRAC_BASE; + amount.value += 1; + } + return amount; +} + + +/** + * Convert amount to string. + * + * @param amount amount to convert to string + * @return freshly allocated string representation + */ +char * +TALER_amount_to_string (struct TALER_Amount amount) +{ + char tail[AMOUNT_FRAC_LEN + 1] = { 0 }; + char curr[TALER_CURRENCY_LEN + 1] = { 0 }; + char *result = NULL; + int len; + + memcpy (curr, amount.currency, TALER_CURRENCY_LEN); + + amount = TALER_amount_normalize (amount); + if (0 != amount.fraction) + { + unsigned int i; + uint32_t n = amount.fraction; + for (i = 0; (i < AMOUNT_FRAC_LEN) && (n != 0); i++) + { + tail[i] = '0' + (n / (AMOUNT_FRAC_BASE / 10)); + n = (n * 10) % (AMOUNT_FRAC_BASE); + } + tail[i] = 0; + len = GNUNET_asprintf (&result, "%s:%lu.%s", curr, (unsigned long) amount.value, tail); + } + else + { + len = GNUNET_asprintf (&result, "%s:%lu", curr, (unsigned long) amount.value); + } + GNUNET_assert (len > 0); + return result; +} + + + +/** + * Return the base32crockford encoding of the given buffer. + * + * The returned string will be freshly allocated, and must be free'd + * with GNUNET_free. + * + * @param buffer with data + * @param size size of the buffer + * @return freshly allocated, null-terminated string + */ +char * +TALER_data_to_string_alloc (const void *buf, size_t size) +{ + char *str_buf; + size_t len = size * 8; + char *end; + + if (len % 5 > 0) + len += 5 - len % 5; + len /= 5; + str_buf = GNUNET_malloc (len + 1); + end = GNUNET_STRINGS_data_to_string (buf, size, str_buf, len); + if (NULL == end) + { + GNUNET_free (str_buf); + return NULL; + } + *end = '\0'; + return str_buf; +} + + +/** + * Get encoded binary data from a configuration. + * + * @return GNUNET_OK on success + * GNUNET_NO is the value does not exist + * GNUNET_SYSERR on encoding error + */ +int +TALER_configuration_get_data (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, const char *option, + void *buf, size_t buf_size) +{ + char *enc; + int res; + size_t data_size; + if (GNUNET_OK != (res = GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &enc))) + return res; + data_size = (strlen (enc) * 5) / 8; + if (data_size != buf_size) + { + GNUNET_free (enc); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, strlen (enc), + buf, buf_size)) + { + GNUNET_free (enc); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +derive_refresh_key (const struct GNUNET_HashCode *secret, + struct GNUNET_CRYPTO_SymmetricInitializationVector *iv, + struct GNUNET_CRYPTO_SymmetricSessionKey *skey) +{ + static const char ctx_key[] = "taler-key-skey"; + static const char ctx_iv[] = "taler-key-iv"; + + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (skey, sizeof (struct GNUNET_CRYPTO_SymmetricSessionKey), + ctx_key, strlen (ctx_key), + secret, sizeof (struct GNUNET_HashCode), + NULL, 0)); + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (iv, sizeof (struct GNUNET_CRYPTO_SymmetricInitializationVector), + ctx_iv, strlen (ctx_iv), + secret, sizeof (struct GNUNET_HashCode), + NULL, 0)); +} + + +int +TALER_refresh_decrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result) +{ + struct GNUNET_CRYPTO_SymmetricInitializationVector iv; + struct GNUNET_CRYPTO_SymmetricSessionKey skey; + + derive_refresh_key (secret, &iv, &skey); + + return GNUNET_CRYPTO_symmetric_decrypt (input, input_size, &skey, &iv, result); +} + + +int +TALER_refresh_encrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result) +{ + struct GNUNET_CRYPTO_SymmetricInitializationVector iv; + struct GNUNET_CRYPTO_SymmetricSessionKey skey; + + derive_refresh_key (secret, &iv, &skey); + + return GNUNET_CRYPTO_symmetric_encrypt (input, input_size, &skey, &iv, result); +} + + +void +TALER_hash_context_start (struct TALER_HashContext *hc) +{ + GNUNET_assert (0 == gcry_md_open (&hc->hd, GCRY_MD_SHA512, 0)); +} + + +void +TALER_hash_context_read (struct TALER_HashContext *hc, void *buf, size_t size) +{ + gcry_md_write (hc->hd, buf, size); +} + + +void +TALER_hash_context_finish (struct TALER_HashContext *hc, + struct GNUNET_HashCode *r_hash) +{ + void *res = gcry_md_read (hc->hd, 0); + GNUNET_assert (NULL != res); + if (NULL != r_hash) + memcpy (r_hash, res, sizeof (struct GNUNET_HashCode)); + gcry_md_close (hc->hd); +} + + +/* end of util.c */ |