commit 6a8748b05e11219408f142a558a93565fe49dcd4 Author: Craig Everett Date: Thu Sep 26 21:54:04 2024 +0900 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20177b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.eunit +deps +*.o +*.beam +*.plt +*.swp +erl_crash.dump +ebin/*.beam +doc/*.html +doc/*.css +doc/edoc-info +doc/erlang.png +rel/example_project +.concrete/DEV_MODE +.rebar diff --git a/Emakefile b/Emakefile new file mode 100644 index 0000000..68c7b67 --- /dev/null +++ b/Emakefile @@ -0,0 +1 @@ +{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + 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. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + 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 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. Use with the GNU Affero General Public License. + + 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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 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 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 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. + + + Copyright (C) + + This program 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 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + 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 GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ebin/clutch.app b/ebin/clutch.app new file mode 100644 index 0000000..6ff1d3c --- /dev/null +++ b/ebin/clutch.app @@ -0,0 +1,8 @@ +{application,clutch, + [{description,"A desktop client for the Gajumaru network of blockchain networks"}, + {registered,[]}, + {included_applications,[]}, + {applications,[stdlib,kernel]}, + {vsn,"0.1.0"}, + {modules,[clutch,gmc_con,gmc_gui,gmc_sup]}, + {mod,{clutch,[]}}]}. diff --git a/include/gmc.hrl b/include/gmc.hrl new file mode 100644 index 0000000..ad11244 --- /dev/null +++ b/include/gmc.hrl @@ -0,0 +1,19 @@ +-record(ak, + {name = "" :: string(), + id = <<>> :: clutch:id(), + balance = 0 :: non_neg_integer(), + history = [] :: [clutch:tx()], + checked = never :: never | clutch:ts()}). + + +-record(tx, + {id = none :: none | clutch:id(), + amount = 0 :: non_neg_integer(), + type = spend :: spend | atom()}). + + +-record(key, + {name = "" :: string(), + id = <<>> :: clutch:id(), + pair = #{} :: #{public := binary(), secret := binary()}, + type = {eddsa, 256} :: {Cipher :: atom, Bitsize :: pos_integer()}}). diff --git a/priv/words4096.txt b/priv/words4096.txt new file mode 100644 index 0000000..69b9ad6 --- /dev/null +++ b/priv/words4096.txt @@ -0,0 +1,4096 @@ +aardvark +abacus +abalone +abandon +abbey +abdomen +abduct +abhor +abide +ability +able +abnormal +aboard +abolish +abort +above +abrasive +abridged +abroad +abrupt +abscond +absence +absinthe +absorb +abstract +absurd +abundant +abuser +abyss +acacia +academic +accent +accident +acclaim +account +accredit +accuse +acetone +achieve +acid +acne +acolyte +acoustic +acquire +acreage +acrobat +acrylic +activity +actor +actress +actual +acuity +acumen +acute +adage +adamant +adapt +addendum +addict +address +adequate +adhesive +adjacent +adjoin +adjust +admiral +admonish +adobe +adoption +adorn +adrenal +adult +advance +advert +advisor +advocate +aerobic +affair +affirm +afflict +afford +affront +afraid +africa +again +aged +agency +agile +agitate +agnostic +agony +agrarian +agree +ahead +ailment +aimless +airbrush +airdrop +airfare +airline +airmail +airport +airship +airtight +aisle +ajar +alarm +albacore +albino +album +alchemy +alcove +alert +alfalfa +algae +algebra +alias +alibi +alien +alimony +alive +alkaline +alley +allied +allocate +allure +almanac +almond +alone +aloof +alpaca +alpha +alpine +alto +altruism +aluminum +amateur +amaze +amber +ambition +ambrosia +ambush +amend +amethyst +amicable +amiss +ammonia +amnesia +amoeba +amorous +amount +amperage +amplify +amputate +amulet +amuse +anaconda +anagram +analysis +anarchy +anatomy +ancestor +anchor +ancient +android +anecdote +anemic +aneurism +angel +angler +angry +anguish +animal +ankle +annex +announce +annual +anoint +anomaly +anorexia +another +answer +antacid +antenna +anthem +antique +antonym +anvil +anxiety +aorta +apart +apathy +aperture +apex +aphid +apogee +apology +apostle +apparel +appear +apple +appoint +approve +apricot +apron +aptitude +aquatic +arachnid +arcade +archery +arctic +ardent +arduous +arena +argue +argyle +arise +armada +armchair +armor +armpit +army +aroma +arouse +arrange +arrest +arrive +arrow +arsenal +arson +artefact +artist +artwork +asbestos +ascend +ascot +ashamed +ashtray +ashy +asia +askew +aspect +asphalt +aspirin +assassin +asset +assist +assorted +assume +asteroid +asthma +astound +astride +astute +asylum +atheism +athlete +atlas +atoll +atom +atrium +atrocity +attack +attend +attic +attorney +attract +auburn +auction +audacity +audit +augment +august +aura +aurora +austere +author +autopsy +autumn +avail +avarice +avatar +avenger +average +aviation +avocado +avoid +await +awake +award +awesome +awkward +awning +awry +axe +axiom +axis +azalea +azimuth +azure +baboon +babysit +bachelor +backhand +bacon +bacteria +badge +baffle +bagel +baggage +bagpipe +bailiff +bakery +balance +balcony +balder +ballroom +balmy +baloney +bamboo +banana +bandage +bane +bangle +banister +banjo +banknote +banner +banquet +banshee +baptize +barbecue +barefoot +bargain +baritone +bark +barley +barman +barnyard +baroness +barrel +basalt +baseball +bashful +basic +basket +bassinet +baste +bathe +battle +bay +bayonet +bazooka +beaker +beam +beanbag +beard +beastly +beaten +beauty +because +beckon +become +bedbug +bedroom +bedsore +beefy +beehive +beeline +beeswax +beetle +befall +before +befuddle +beggar +begin +begrudge +beguile +behavior +behead +behind +behold +beige +believe +bellhop +belong +bemuse +bench +benefit +benign +bequeath +berate +beret +berserk +beseech +beside +bespoke +best +beta +betray +between +beverage +beware +bewilder +beyond +biased +bible +bicep +bicker +bicycle +bidding +bifocal +biggest +bigmouth +bigotry +bigwig +bike +bikini +billfold +binary +binder +bingo +binomial +biology +bionic +biopsy +bipedal +birch +birdcage +birthday +biscuit +bisect +bisque +bistro +bitter +bizarre +blackout +blade +blamed +blanket +blast +blatant +blazer +bleak +bleed +blemish +bless +blimp +blind +blister +blitz +blizzard +bloated +blob +blockade +blogger +blonde +blooper +blossom +blouse +blowgun +blubber +bludgeon +bluejay +bluffer +blunder +blur +blustery +boastful +boater +bobcat +bobsled +bobtail +bodega +bodice +bodywork +bogey +bohemian +boil +boldface +bolt +bombard +bonanza +bond +boneless +bonfire +bonnet +bonsai +bonus +boogie +book +boost +bootleg +borax +bordered +boredom +borrow +bosom +boss +bosun +botanist +botched +bother +bottom +botulism +boulder +boundary +bouquet +bourbon +boutique +bovine +bowler +boxer +boycott +boyhood +bracelet +brag +braille +bramble +branch +brass +bratty +brave +brawler +brazen +breathe +breeze +brethren +brevity +brewery +briar +bribery +brick +bright +brim +brine +brisket +brittle +broach +broccoli +broiler +broken +bronze +broom +browse +bruise +brunt +brute +bubble +buckle +buddy +budget +buffet +builder +bulb +bulge +bulimia +bulky +bulletin +bummed +bumpy +bundle +bungalow +bunion +bunker +buoyant +burden +bureau +burger +burial +burlap +burnout +burp +burrito +burst +bushel +business +bustle +busy +butane +butcher +butler +button +buxom +buyer +buyout +buzz +bylaw +bypass +cabaret +cabbage +cabin +cactus +cadaver +cadet +caffeine +caftan +cage +cairn +cajole +calamity +calcium +calendar +calico +callous +calm +calorie +calypso +camera +campus +canal +cancel +candy +canine +cannibal +canoe +canteen +canvas +canyon +capacity +capital +capsule +capture +caramel +carbon +carcass +card +careful +cargo +caribou +carnival +carousel +carry +carsick +cartoon +carve +cascade +cashew +casino +cassette +castle +casual +catalog +catcher +category +catnap +catwalk +cauldron +causeway +caution +cavalry +caveman +cavity +ceiling +celery +celibate +cellmate +cement +censored +center +ceramic +ceremony +certain +cesar +cesium +cesspool +chaff +chagrin +chair +chalice +champion +change +chaotic +chapter +charity +chase +chat +cheap +checkers +cheddar +cheese +chemical +cherry +chestnut +chevron +chew +chicken +chief +chiffon +child +chimney +china +chipmunk +chirp +chisel +chive +chlorine +choice +choke +cholera +chomp +choppy +chorus +chowder +chronic +chubby +chuckle +chug +chummy +chunk +churn +chutney +cicada +cider +cigar +cilantro +cinema +cinnamon +cipher +circle +cistern +citadel +citizen +citrus +city +civil +claim +clammy +clang +clarify +class +clatter +clavicle +clay +cleanup +cleft +clemency +clench +clerk +clever +client +cliff +climate +clinic +clipped +cloaked +clock +clog +cloister +closet +clothing +cloudy +clove +clown +clubfoot +clueless +clump +clunky +cluster +clutch +coach +coastal +coated +cobalt +cobbler +cobra +cobweb +coccyx +cocktail +coconut +code +coerce +coffee +cognac +coherent +cohort +coiled +coin +colander +colder +coleslaw +coliseum +collect +color +column +comatose +combine +comedy +comfort +comic +common +company +comrade +concert +conduct +confirm +congress +conical +conjoin +connect +conquer +consume +control +convince +cookbook +cool +copper +copy +corduroy +corner +coronary +corporal +correct +corset +cortex +cosmetic +cosplay +costume +cotton +cougar +counter +coupon +courier +cousin +cover +cowardly +cowboy +cowlick +coyote +crabby +crackle +cradle +craft +cram +crane +crater +craving +crawl +crayon +crazy +creamy +credit +creep +cremate +crescent +crevice +cricket +criminal +cringe +crisis +critical +croak +crochet +crooked +crop +croquet +crossbow +crouch +crowd +crucial +cruel +cruiser +crumble +crunch +crush +crux +cryptic +crystal +cube +cuckoo +cucumber +cuddle +cudgel +cuff +cuisine +culinary +culprit +cultural +culvert +cumin +cumulus +cunning +cupboard +cupcake +cupid +curator +curb +curfew +curled +currency +cursive +curtsy +curvy +cushion +cuss +custody +cutback +cutest +cuticle +cutlery +cutout +cycle +cylinder +cynic +cypress +cyst +dabble +daffodil +dainty +daiquiri +daisy +damage +damsel +dance +dandruff +danger +dapper +darkness +darling +dart +dash +database +dateline +daughter +daunting +dawdle +daybreak +daydream +daylight +dazed +dazzle +deadline +dealer +dean +deathbed +debate +debrief +debtor +debut +decade +deceased +decision +deck +declare +decorate +decrease +dedicate +deduct +deed +deepest +deface +defense +define +deflate +deformed +defraud +deft +defuse +degree +deity +dejected +delay +delegate +deliver +delta +delusion +delve +demand +demeanor +demise +democrat +demure +denial +denounce +density +dentist +deny +depart +depend +depict +deploy +deposit +depress +depth +deputy +derail +derby +derelict +derive +describe +deserter +desire +desktop +desolate +despair +destroy +detach +detect +detour +devalue +develop +device +devote +dewdrop +diabetic +diagnose +dialogue +diamond +diaper +diary +diatribe +dicey +dictate +diesel +diet +differ +digest +digital +dignity +digress +dilemma +diligent +dilute +diminish +dimmer +dimpled +dingy +dinner +dinosaur +diorama +diploma +direct +dirty +disabled +disburse +disco +disdain +disease +disguise +dishevel +dismal +dispense +disrupt +dissuade +distance +dive +divide +divorce +divulge +dizzy +docility +dockyard +doctor +document +dodge +dodo +dogged +doghouse +dogmatic +doldrums +doll +dolphin +domain +domestic +dominant +donate +donkey +doomsday +door +dorky +dorm +dorsal +dosage +dossier +dotted +doubt +doughnut +downtown +dowry +dozen +draftee +dragon +drainage +dramatic +drapery +drastic +draw +dream +dredge +dress +dribble +dried +drift +drink +driveway +drizzle +drool +droplet +drought +drove +drowsy +drudgery +drug +druid +drummer +drywall +dubious +duckling +dugout +duke +dumbbell +dumpster +dungeon +duo +duplex +duration +dust +dutchess +dutiful +duty +duvet +dwarf +dwell +dwindle +dynamic +dyslexia +eager +eagle +eardrum +earl +earmark +earner +earphone +earring +earshot +earth +earwig +easel +eastward +easy +eatery +ebb +ebony +echo +eclectic +eclipse +ecology +economy +ecstasy +edge +edible +edifice +editor +educate +eel +eery +effigy +effort +eggnog +eggplant +eggshell +ego +elapse +elastic +elated +elbow +elder +election +elegance +element +elephant +elevator +eligible +elite +elixir +ellipsis +elm +elongate +elope +eloquent +elude +elusive +emaciate +email +emanate +embark +embezzle +emblem +embody +embrace +emerald +emigrant +eminent +emission +emoji +emotion +empathy +emperor +emphasis +employer +empower +empty +emu +emulate +enamel +enchant +enclose +encoder +encrypt +encumber +endeavor +endless +endorse +endure +enemy +energize +engage +engine +engraver +engulf +enhance +enigma +enjoy +enlist +enmity +enormity +enraged +enrich +enroll +ensemble +ensnare +entangle +enthrone +entire +entrance +entwine +envelope +envision +envy +enzyme +epic +epidemic +epigram +epilepsy +episode +epitaph +epoch +epoxy +equation +equinox +eraser +erect +erode +errand +error +erupt +escape +escort +escrow +esoteric +espresso +essay +essence +estate +esteem +estimate +estrange +estuary +eternal +ethereal +ethical +ethnic +eulogy +euphoric +eureka +euro +evade +evaluate +evasion +event +evict +evidence +evil +evoke +evolve +exact +exalted +example +excavate +excerpt +exchange +excite +exclude +excrete +excuse +execute +exempt +exercise +exhaust +exhibit +exhume +exile +exist +exodus +exorcist +exotic +expand +expert +expire +explain +expose +express +extend +extinct +extort +extra +eyeball +eyeglass +eyelash +fabled +fabric +fabulous +facade +facelift +facility +fact +faculty +faded +failure +fainter +fairy +faith +fake +falcon +fallout +false +famished +famous +fanatic +fanboy +fancy +fanfare +fang +fantasy +farewell +farmer +farther +fashion +fasten +fatal +fathom +fatigue +fatty +faucet +fault +favorite +fax +fealty +fearless +feast +feature +federal +fedora +fee +feeble +feedback +feeler +feign +feisty +feline +felon +feminine +femur +fence +feral +fern +ferocity +ferret +fertile +fervent +festival +fetch +fetid +feud +fever +fiasco +fiber +fiction +fiddler +fidelity +fidget +fiendish +fiery +fiesta +figure +filament +filch +filet +filigree +filling +filter +finance +fine +finger +finish +firewood +firm +first +fiscal +fishery +fissure +fitful +fixate +fizz +fjord +flabby +flag +flail +flaky +flame +flannel +flapjack +flash +flatten +flaunt +flavor +flawless +fleece +fleshy +flexible +flick +flight +flimsy +fling +flip +flirt +float +floor +floppy +floral +floss +flotsam +flourish +flower +fluent +fluff +fluid +flummox +flunk +fluoride +flurry +flusher +flute +flux +flypaper +flywheel +foam +focus +fodder +fog +fold +foliage +folklore +follow +fondue +font +foolish +football +forager +forbid +force +forecast +forfeit +forget +forklift +forlorn +formal +forsake +fortune +forum +forward +fossil +fought +foul +founder +foxhound +foxtrot +foxy +foyer +fraction +fragment +frailty +frantic +fraught +freaky +freckled +freeway +freight +frenzy +frequent +freshman +fret +fridge +friend +frighten +fringed +frisky +fritter +frizzy +frock +frolic +frontier +frost +froth +frown +frozen +fructose +frugal +fruit +fugitive +fulcrum +fulfill +fullback +fumigate +fund +funeral +fungus +funny +furious +furl +furnace +furrow +furthest +fuselage +fusion +fussy +futile +futon +future +fuzzy +gadget +galaxy +gallery +gambler +game +gangster +gap +garage +garden +gargle +garish +garlic +garment +garnet +garrison +garter +gaseous +gaslight +gasoline +gastric +gatepost +gather +gaudy +gauge +gauntlet +gavel +gawk +gazette +gearbox +gecko +geeky +geezer +gelatin +gemstone +general +genius +genre +gentle +genuine +geology +geometry +gerbil +germ +gesture +getaway +geyser +ghastly +ghetto +ghost +ghoul +giddy +gifted +gigantic +giggle +gilded +gimmick +giraffe +girdle +girlish +girth +gist +gizmo +gizzard +glacier +glad +glamour +glance +glare +glassy +glaucoma +glazed +gleam +gleeful +glen +glib +glide +glimpse +glint +glitter +globe +gloom +glory +glossary +gloved +glow +glucose +glue +gluttony +glycerin +glyph +gnarled +gnash +gnaw +gnome +goalie +goatee +goblet +goddess +goldfish +golfer +gondola +good +gooey +goose +gopher +gorge +gorilla +gosling +gospel +gossip +gothic +gourmet +govern +gown +grabber +gracious +graduate +graffiti +grainy +grammar +grant +grape +grasp +grateful +gravity +gray +greasy +green +gremlin +grew +gridlock +grief +grill +grimace +grin +gristle +gritty +grizzly +grocery +groggy +grommet +groove +gross +grotto +grout +grovel +grownup +grub +gruesome +gruff +grumpy +grungy +gryphon +guard +guava +guess +guidance +guilty +guitar +gullible +gulp +gumbo +gumdrop +gumption +gunsmith +gurney +guru +gusty +gut +guttural +gym +gymnast +gypsy +gyration +gyro +habit +hacksaw +haggler +haiku +haircut +halberd +half +halibut +hallway +halogen +halter +hamlet +hammer +hamster +handrail +hangover +happy +harass +harbor +hardwood +harmonic +harness +harpoon +harsh +harvest +hashtag +hassle +hatchet +hateful +hatred +hauler +haunch +havoc +haystack +haywire +hazard +hazelnut +hazmat +headset +health +hearsay +heat +heavy +heckler +hectic +hedgehog +hedonism +heedful +hegemony +height +heinous +heirloom +heliport +hello +helmet +helpful +hemlock +hen +henchman +henna +herald +herbal +heresy +heritage +hernia +heron +herring +hesitate +hexagon +hiatus +hibiscus +hiccup +hickory +hidden +hideaway +highway +hijacker +hiker +hilarity +hill +hinge +hint +hip +hippo +history +hither +hoagie +hoarder +hoax +hobby +hobo +hockey +hoedown +hoggish +hoist +holiday +holler +hologram +holster +holy +homage +home +homicide +homonym +honeydew +honk +honor +hoodie +hookworm +hooligan +hoop +hooray +hopeful +horizon +hormone +hornet +horror +horseman +hospital +hostess +hotel +hourly +housing +howdy +howitzer +hub +hubcap +hubris +huge +hula +human +humble +humdrum +humidity +hummus +humpback +hungry +hunter +hurdle +hurry +hurt +husband +hush +husky +hustler +hyacinth +hybrid +hydrate +hyena +hygiene +hyper +hyphen +hypnosis +hysteria +iceberg +icicle +icon +icy +idea +identify +ideology +idiot +idler +idolize +idyllic +igloo +ignite +ignore +iguana +illicit +illusion +imagery +imbibe +imbue +imitate +immense +immolate +immune +impasse +impede +implode +impostor +impress +impunity +inbox +incense +inch +incision +include +income +incubate +index +indicate +industry +inept +inertia +infant +inferno +infinity +inflict +info +infrared +ingest +ingot +inhale +inherit +inhibit +initial +inject +injure +inkwell +inlet +inmate +innocent +innuendo +input +inquiry +insane +insert +insignia +insomnia +inspect +instruct +insult +intact +interest +intimate +intruder +invasion +investor +invite +invoke +iodine +ionize +iris +irksome +ironwork +irritate +island +isolate +isotope +issue +itchy +item +iterate +ivory +jabber +jackal +jade +jagged +jaguar +jailer +jamboree +janitor +jargon +jasmine +jaundice +javelin +jaw +jawbone +jaywalk +jazz +jealousy +jeans +jeopardy +jerky +jersey +jester +jet +jettison +jewelry +jiffy +jigsaw +jinx +jittery +jock +jogger +joint +joke +jolly +jostle +journey +joust +joy +joyride +joystick +jubilant +judge +judicial +judo +jug +juggler +juicy +jujitsu +jukebox +jump +junction +junior +junk +juror +jury +justify +juvenile +kabob +kale +kangaroo +karaoke +karma +kayak +kazoo +keen +keepsake +keg +kennel +keratin +kerchief +kerosene +kestrel +ketchup +keyboard +keyhole +keynote +keystone +keyword +khaki +kick +kidder +kidney +killjoy +kilogram +kilt +kimono +kind +kinetic +kinfolk +kingdom +kinsman +kiosk +kiss +kitchen +kite +kitten +kiwi +klutzy +knapsack +knee +knife +knitted +knockout +know +knuckle +koala +kumquat +lab +label +labor +lacerate +lackey +lacquer +lacrosse +lactose +ladder +ladybug +laggard +lagoon +lair +lament +laminate +lamp +lancer +landlord +language +lanky +lantern +lanyard +laptop +larceny +large +larvae +larynx +lasagna +lasso +latex +latitude +latrine +lattice +laugh +laundry +laureate +lava +lavender +lavish +lawful +lawn +lawyer +laxative +layaway +layout +lazy +leader +leaflet +league +leaky +leap +learn +leash +leathery +lecture +ledger +leeway +lefty +legacy +legend +legible +legume +legwork +leisure +lemming +lemonade +lend +length +leniency +lentil +leopard +leprosy +lesion +lesser +lethargy +letter +level +levitate +levy +lexicon +liaison +liberty +library +license +lichen +lieu +lifespan +lift +ligament +lighter +likable +lilac +limbo +lime +limit +linchpin +linen +linoleum +linseed +lion +lipid +lipstick +liquid +lisp +listen +literary +lithium +litigate +little +livery +lizard +llama +loaded +loaf +loaner +loathe +lobbyist +lobotomy +lobster +location +lockjaw +locust +logic +logo +loiterer +lollipop +lonesome +long +lookout +loopy +looter +lopsided +lordship +loser +lotion +lottery +lotus +loud +lounge +lousy +lovely +lowland +loyalty +lozenge +lucid +luck +luggage +lukewarm +lullaby +lumber +luminous +lunar +lunch +lure +luxury +lychee +lymph +lyric +macaroni +machine +macro +mad +maestro +magazine +magenta +maggot +magic +magma +magnet +mahogany +maiden +mailman +maimed +maintain +majestic +majority +makeup +malaria +malice +mallard +malted +malware +mammal +manager +mandolin +maneuver +mango +manhole +manicure +mankind +manly +manpower +mansion +mantra +manual +maple +marathon +marble +march +mare +margin +marine +market +marmot +maroon +marriage +marshal +martini +marvel +mascara +mashup +masked +mason +massive +master +matador +matchbox +material +math +matrix +mattress +maturity +maverick +maximum +mayhem +mayor +meadow +meander +measure +meatball +mechanic +medalist +medical +medley +meeting +megabyte +melanoma +mellow +melody +melt +member +meme +memory +menace +mental +menu +meow +merchant +merge +merit +mermaid +mesh +message +metallic +meteor +method +metric +miasma +microbe +midair +midday +midnight +midpoint +midriff +midst +midterm +midwife +midyear +mighty +migraine +mild +mileage +military +milkman +million +mimic +mimosa +minced +mindful +mineral +minister +minnow +minstrel +minty +minute +miracle +mirror +mischief +misery +misfit +mishap +missile +mistake +mitosis +mitt +mixture +mnemonic +mobile +moccasin +mocha +mockery +modern +modify +module +mogul +moisture +molasses +moldy +molecule +mollusk +molten +moment +momma +monarch +money +mongoose +monitor +monocle +monster +month +monument +moocher +moon +mop +moped +moral +morgue +morning +morocco +morphine +morsel +mortgage +mosaic +mosquito +mossy +motherly +motivate +motley +motor +motto +mountain +mourn +mouse +mouthful +move +mucky +mucus +muffin +mugger +mulch +mullet +multiply +mummify +mundane +murmur +muscle +museum +mushroom +musical +musket +mustang +mutant +mute +mutilate +mutter +mutual +myopia +myriad +mystery +mythical +nacho +nail +naive +naked +namesake +nanny +napkin +narcotic +narrator +narwhal +nasal +nasty +national +nature +naughty +nausea +nautical +navigate +nearest +nebula +necklace +nectar +needle +negative +neglect +neighbor +nemesis +neon +neoprene +nephew +nepotism +nerd +nerve +nestle +network +neuron +neutral +newborn +newlywed +newscast +newton +next +nexus +niche +nickel +nicotine +niece +nifty +nightcap +nihilist +nimble +ninja +nippy +nirvana +nitpick +nitrogen +nitwit +noble +nobody +nocturne +noise +nomad +nominee +nonsense +noodle +normalcy +north +nosedive +nostril +nosy +notary +notch +notepad +notice +noun +nourish +novel +noxious +nozzle +nuance +nuclear +nugget +nuisance +nullify +numb +numeral +nunnery +nurse +nurture +nutmeg +nutrient +nutshell +nutty +nylon +oak +oasis +oatmeal +obedient +obelisk +obese +obey +obituary +object +oblige +oblong +oboe +obscure +observe +obsidian +obsolete +obstacle +obtain +obtuse +obvious +occasion +occupant +ocean +ocelot +octave +octopus +ocular +oddity +odometer +odyssey +offer +offhand +office +offload +offshore +often +ogre +oilskin +ointment +okay +oligarch +omelet +omit +omnivore +oncoming +onion +online +onset +onward +onyx +ooze +opaque +opera +opinion +oppose +oppress +option +opulent +oracle +orange +orbit +orchard +order +ordinary +oregano +organize +original +ornament +orphan +orthodox +osmosis +ostrich +otter +ottoman +ounce +outage +outburst +outcome +outdoor +outfit +outgrow +outhouse +outlaw +output +outrun +outside +outtake +outward +oval +oven +overall +owl +owner +oxford +oxygen +oxymoron +oyster +ozone +pacific +package +paddle +padlock +pageant +pagoda +painful +pajamas +palpable +palsy +paltry +pamphlet +panacea +pancake +panda +panel +panic +panorama +panther +papaya +paper +paprika +papyrus +parade +parcel +pardon +parental +pariah +parka +parlor +parody +parrot +parsley +particle +passport +pasta +patchy +patent +pathway +patio +patrol +pattern +pave +pavilion +pawnshop +payday +payload +peaceful +peanut +pearly +peasant +pebble +pecan +pectoral +peculiar +pedantic +peddler +pedestal +pedicure +peerless +pelican +pellet +penalty +pencil +pendant +penguin +penny +pensive +pentagon +pepper +percent +perfect +period +perjury +perk +permit +perplex +person +perturb +peruse +perverse +pesky +petal +petition +petrify +petunia +pewter +phalanx +phantom +pharmacy +phlegm +phobia +phoenix +phonetic +photo +phrase +physical +piano +pickup +picnic +picture +pierce +piety +pigeon +piglet +pigment +pile +pilfer +pilgrim +pillow +pinball +pincer +pinhole +pink +pinnacle +pinpoint +pinto +pioneer +pious +pirate +piston +pitch +pitfall +pitiful +pitted +pivot +pixel +pixie +pizza +placard +plan +plaque +plasma +platform +playlist +plaza +plead +pledge +plenty +plethora +pliable +plot +plumage +plunge +plural +plushy +plywood +poacher +pockmark +podcast +podiatry +poem +poignant +pointy +poison +poker +polarize +polished +polka +pollen +polo +polygon +pomade +pompom +poncho +pontoon +ponytail +pooch +popcorn +popular +porous +porridge +portion +positive +possible +post +potato +potency +pothole +potluck +poultry +poverty +powder +powerful +pox +practice +prairie +praline +prance +prawn +prayer +preach +precise +predator +prefer +preheat +prelude +premium +prepare +prequel +pressure +pretty +prevent +price +priest +primary +print +priority +prisoner +privacy +problem +process +produce +profile +program +prohibit +project +prologue +promise +pronoun +proof +property +prorate +prospect +protest +proud +provide +prowl +proxy +prudish +prune +psalm +pseudo +psychic +puberty +public +pucker +pudding +pudgy +puffy +pugilist +pulley +pulpit +pulse +puma +pummel +pumpkin +puncture +pundit +pungent +punish +puny +pupil +puppy +purchase +puree +purity +purple +pursuit +purveyor +pushup +putrid +putt +puzzle +pyramid +pyre +python +quadrant +quagmire +quail +quake +quality +quantity +quarter +quasar +queasy +queen +quell +quench +query +quest +quick +quiet +quilted +quirky +quitter +quiver +quiz +quote +rabbit +rabies +raccoon +race +radar +radio +radon +rafter +raggedy +ragtag +raider +railroad +rainbow +raisin +rally +rampage +ramrod +rancher +random +ransack +rapid +rarity +rascal +raspy +rat +ration +raunchy +ravage +raven +ravioli +razor +reaction +ready +realize +reaper +rebel +rebound +rebuttal +receiver +recharge +recipe +reckless +recliner +recorder +recruit +rectify +recycle +redeemer +redhead +redneck +reduce +redwood +reef +referee +refinery +reflect +reform +refrain +refugee +regalia +regency +reggae +region +regret +regular +rehab +rehearse +reject +rejoice +relaxed +relevant +relic +reload +remark +remedy +reminder +remnant +removal +render +renegade +renovate +rent +repair +repeater +replica +report +reprisal +reptile +repute +requiem +rescue +resemble +resident +resonate +response +restroom +result +retailer +retina +retrieve +reunion +reveler +revive +revolt +reward +rhapsody +rhetoric +rhino +rhodium +rhombus +rhubarb +rhythm +rib +ribbon +rich +rickshaw +ricochet +riddle +ride +ridicule +riff +rifle +rightful +rigid +ringtone +rinse +riot +ripple +risk +ritual +ritzy +rival +river +roadwork +roar +roast +robbery +robe +robin +robot +robust +rocky +rodeo +roguish +romantic +romp +roofing +rookie +roommate +rosemary +roster +rotate +rotten +rouge +roulette +round +routine +rowboat +royal +rubber +rubric +rucksack +rudder +rueful +ruffian +rugby +rugged +ruin +ruler +ruminate +rummage +rumor +rumple +runner +runoff +runway +rupture +rural +rusted +ruthless +saber +sabotage +sadistic +sadness +safari +safe +saffron +saga +said +sailor +saint +salary +salesman +saliva +salon +salsa +salt +salute +salvage +sampler +samurai +sanctum +sandwich +sanguine +sanitize +sapling +sapphire +sarcasm +sardine +sassy +satchel +satisfy +saturate +sauce +sauna +savanna +savior +savory +savvy +sawdust +sawhorse +sawmill +scab +scaffold +scale +scamper +scandal +scapula +scared +scatter +scavenge +scenery +scepter +scheme +schism +schnapps +scholar +science +scimitar +scissor +scoff +scold +scooter +scope +scorpion +scotch +scout +scowl +scramble +screen +script +scroll +scrub +scuba +scuffed +sculpt +scumbag +scurry +scuttle +scythe +seabird +seafood +sealant +seamless +seaplane +search +season +seaweed +secluded +second +secret +section +security +sedan +sediment +seek +seepage +seesaw +seethe +segment +seismic +seizure +seldom +select +self +sellout +seltzer +semantic +semester +seminar +senator +senior +sensory +sentence +sepia +sepsis +sequence +serenade +serfdom +sergeant +serious +serpent +serrated +serum +service +sesame +session +setback +settle +setup +severity +sewage +sewing +sextant +shabby +shackle +shadow +shaft +shaggy +shaky +shallow +shame +sharp +shawl +sheathe +sheet +shelter +shepherd +sheriff +shield +shifty +shimmy +shinbone +shipyard +shiver +shock +shoelace +shop +short +shotgun +shoulder +shove +showoff +shrapnel +shred +shrine +shroud +shrug +shudder +shuffle +shutdown +sibling +sickle +sidewalk +siege +sierra +signal +silent +silicone +silk +silly +silver +similar +simple +simulate +since +sinew +single +sinkhole +sinus +siphon +siren +sirloin +sister +sitcom +size +sizzle +skate +skeleton +skeptic +sketch +skewer +skier +skillet +skimmed +skin +skirt +skittish +skull +skunk +skydive +skyline +skyward +slacker +slalom +slammer +slant +slather +sleaze +sled +sleepy +slender +sleuth +slice +slim +slinky +slipper +slobber +slogan +sloped +sloth +slouch +sluggish +slurp +slush +smart +smash +smear +smell +smile +smith +smoke +smolder +smooth +smother +smudge +smug +snack +snag +snake +snapshot +snarl +snazzy +sneaker +sneeze +snicker +snide +sniff +snip +snobbish +snooze +snore +snot +snowball +snuggle +soaked +soap +sob +soccer +society +socket +soda +sodium +soft +soggy +solar +soldier +solemn +solid +soloist +solstice +solution +solve +sombrero +somebody +sonata +songbird +sonic +sooner +soothe +soprano +sorbet +sorcerer +sorority +sortie +soulmate +source +south +souvenir +soybean +space +spandex +spark +spasm +spatula +spawn +speak +special +speech +spend +spew +sphere +sphinx +spicy +spider +spiffy +spigot +spill +spine +spirit +spit +splash +spleen +splint +splotchy +splurge +spoil +sponsor +spoon +sporty +spotter +spouse +spray +spreader +sprinkle +sprout +spruce +spud +spunky +spurn +spy +spyglass +square +squeeze +squirrel +sriracha +stable +staccato +stadium +stage +stairway +stalker +stamp +standard +stapler +starve +station +staunch +stay +steady +steer +stellar +stencil +stereo +steward +stick +stifle +stigma +stilt +stimulus +stingray +stipend +stir +stockade +stoic +stolen +stomach +stone +stool +stopper +storm +stow +strategy +street +strike +strong +struggle +stub +stucco +student +stuff +stumble +stunt +stupor +sturgeon +stutter +stylus +stymie +suave +subdue +subject +sublime +submit +subplot +subsidy +subtitle +suburbia +subvert +subway +success +sudden +sudsy +suffer +sugar +suggest +suitable +sulfur +sullen +sultan +summer +sumo +sunburn +sundial +sunken +sunlight +sunroof +sunset +superior +support +supreme +surface +surgery +surmount +surname +surprise +surround +survive +sushi +suspect +sustain +swaddle +swagger +swampy +swan +swarm +swath +sweater +sweeper +swerve +swift +swimmer +swindler +swipe +switch +swivel +swollen +swoop +sworn +sycamore +syllabus +symbolic +symmetry +sympathy +synapse +sync +syndrome +synergy +synopsis +syntax +syringe +syrup +system +tablet +taboo +tacit +tackle +taco +tactile +tadpole +taffy +tag +tailpipe +takeout +talent +talisman +tall +tamper +tandem +tangy +tankard +tanned +tantrum +tapestry +tapioca +tardy +target +tariff +tarmac +tarnish +tarp +tarrier +tartar +task +tassel +tasteful +tattoo +taunt +tavern +taxation +taxi +teacher +teal +teamwork +teapot +teardrop +teaser +techno +tedium +teenager +teeth +telecast +teller +temple +tenant +tendency +tennis +tenor +tension +tentacle +tenure +tepid +tequila +terminal +terrain +terse +tertiary +testify +tetanus +tether +texture +thank +thatch +thaw +theater +theft +theme +theory +therapy +thesis +thick +thigh +thimble +thinner +thirst +thistle +thorn +thought +thrall +threat +thrive +throaty +thrum +thud +thumb +thunder +thwart +thyroid +tiara +tibia +ticket +tidal +tidbit +tidy +tiger +tight +timber +timeline +timid +tinfoil +tinker +tinsel +tinted +tipsy +tiptoe +tirade +tired +titanium +title +toad +toaster +tobacco +toboggan +today +toddler +toenail +tofu +together +toggle +toilet +token +tolerate +tomato +tomb +tomcat +tomorrow +tonality +toned +tongs +tonight +tonnage +tonsil +toolbox +tooth +topaz +topology +topple +topsoil +torch +torment +tornado +torpedo +torque +torrid +torso +torture +torus +total +tote +toucan +tourist +toward +tower +township +toxic +track +trader +traffic +tragic +train +trample +transfer +trapeze +trash +trauma +traveler +treaty +trek +tremble +trend +trespass +trial +tribe +tricycle +trident +trigger +trilogy +trinket +trip +triumph +trivia +trod +troll +trooper +tropic +trouble +truant +trucker +trudge +truffle +trumpet +trunk +trust +truth +try +tsunami +tuba +tubby +tubular +tuft +tugboat +tuition +tulip +tumbler +tummy +tumult +tundra +tuner +tungsten +tunic +tunnel +turbine +turf +turkey +turmoil +turnip +turret +turtle +tussle +tutor +tutu +tuxedo +tweak +tweet +twerp +twice +twiddle +twilight +twin +twirl +twist +tycoon +type +typhoon +typical +tyrant +ubiquity +ukulele +ulcer +ulterior +ultimate +ultra +umbrella +umpire +uncle +uncouth +under +undo +undulate +unicycle +uniform +unique +unisex +unite +universe +unkempt +until +unwieldy +upbeat +upchuck +upcoming +updraft +upfront +upgrade +upheaval +uphill +upkeep +uplift +upload +upper +upright +uproar +upscale +upset +upstream +uptake +uptown +upward +uranium +urban +urchin +urge +useful +useless +username +usher +usual +usurper +utensil +utility +utmost +utopia +uvula +vacant +vaccine +vacuum +vagabond +vagrant +vague +valet +valid +valuable +valve +vampire +vandal +vanguard +vanish +vanquish +vapor +variety +varsity +vascular +vassal +vast +vector +veer +vegan +veggie +vehement +vehicle +velocity +velvet +vendor +veneer +vengeful +venison +venom +venture +venue +veranda +verb +verdict +verify +vermin +version +vertebra +vessel +vestige +veteran +veto +viable +viaduct +vibrant +vicinity +victim +video +view +vigilant +vigorous +village +vinegar +vintage +vinyl +violence +virtual +virus +visa +visceral +visitor +visor +visual +vital +vitriol +vivid +vocal +voice +volatile +volcano +volition +volley +voltage +volume +voodoo +voter +voucher +vowel +voyage +vulture +wacky +wafer +waft +waggle +wagon +waitress +waiver +walkway +wallet +walnut +walrus +waltz +wanderer +wardrobe +warhorse +warlord +warmth +warped +warrior +warthog +washroom +wasp +wasted +watch +waterbed +wavy +waxed +wayward +weaken +wealthy +weapon +weary +weather +weaver +webbed +webcam +website +wedding +weedy +weekend +weep +weird +welcome +welfare +western +wetland +whack +whaler +wharf +wheeze +whelp +whiff +whim +whinny +whiplash +whirl +whisper +white +wicked +widow +wife +wig +wildfire +willowy +wimpy +windpipe +winery +wingtip +winter +wiper +wireless +wiry +wisdom +wish +wistful +withdraw +witness +witty +wizardry +wobble +woe +wolf +woman +wombat +wonder +woodwork +woolen +woozy +work +world +wormhole +worry +worship +worthy +wounded +wraith +wrangle +wrapper +wreath +wreckage +wrench +wrestle +wretched +wriggle +wrinkle +wrist +written +wrong +xenon +yacht +yam +yank +yarn +year +yelp +yeoman +yes +yeti +yield +yodel +yoga +yogurt +yolk +young +yuletide +yuppie +zany +zealot +zebra +zenith +zeppelin +zero +zesty +zinc +zipper +zodiac +zombie +zoom +zucchini +zygote diff --git a/src/clutch.erl b/src/clutch.erl new file mode 100644 index 0000000..fed401f --- /dev/null +++ b/src/clutch.erl @@ -0,0 +1,68 @@ +%%% @doc +%%% Clutch +%%% @end + +-module(clutch). +-vsn("0.1.0"). +-behavior(application). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-export([ts/0]). +-export([start/2, stop/1]). + +-export_type([id/0, key/0, ak/0, tx/0, ts/0]). + +-include("$zx_include/zx_logger.hrl"). +-include("gmc.hrl"). + + +-type id() :: binary(). +-type key() :: #key{}. +-type ak() :: #ak{}. +-type tx() :: #tx{}. +-type ts() :: integer(). + + +ts() -> + erlang:system_time(nanosecond). + + +-spec start(normal, Args :: term()) -> {ok, pid()}. +%% @private +%% Called by OTP to kick things off. This is for the use of the "application" part of +%% OTP, not to be called by user code. +%% +%% NOTE: +%% The commented out second argument would come from ebin/clutch.app's 'mod' +%% section, which is difficult to define dynamically so is not used by default +%% here (if you need this, you already know how to change it). +%% +%% Optional runtime arguments passed in at start time can be obtained by calling +%% zx_daemon:argv/0 anywhere in the body of the program. +%% +%% See: http://erlang.org/doc/apps/kernel/application.html + +start(normal, _Args) -> + ok = + case net_kernel:stop() of + ok -> + log(info, "SAFE: This node is running standalone."); + {error, not_found} -> + log(warning, "SAFETY: Distribution has been disabled on this node."); + {error, not_allowed} -> + ok = tell(error, "DANGER! This node is in distributed mode!"), + init:stop(1) + end, + ok = application:ensure_started(zxwidgets), + gmc_sup:start_link(). + + +-spec stop(term()) -> ok. +%% @private +%% Similar to start/2 above, this is to be called by the "application" part of OTP, +%% not client code. Causes a (hopefully graceful) shutdown of the application. + +stop(_State) -> + ok. diff --git a/src/gmc_con.erl b/src/gmc_con.erl new file mode 100644 index 0000000..d9ff7ad --- /dev/null +++ b/src/gmc_con.erl @@ -0,0 +1,470 @@ +%%% @doc +%%% Clutch Controller +%%% +%%% This process is a in charge of maintaining the program's state. +%%% @end + +-module(gmc_con). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(gen_server). +-export([make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1, + login/1, password/2]). % add_key/1, drop_key/1]). +-export([encrypt/2, decrypt/2]). +-export([start_link/0, stop/0, save/1]). +-export([init/1, terminate/2, code_change/3, + handle_call/3, handle_cast/2, handle_info/2]). +-include("$zx_include/zx_logger.hrl"). + +-include("gmc.hrl"). + + +%%% Type and Record Definitions + + +-record(s, + {window = none :: none | wx:wx_object(), + accounts = [] :: [clutch:ak()], + pass = none :: none | binary(), + keys = [] :: [clutch:key()], + chains = [] :: [chain()], + prefs = #{} :: #{atom() := term()}}). + +-record(chain, + {id = "" :: string(), + nodes = [] :: [address()], + mdws = [] :: [address()]}). + +-type state() :: #s{}. +-type chain() :: tuple(). +-type address() :: {inet:ip_address(), inet:port_number()}. + + + +%% Interface + +-spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok + when Type :: {eddsa, ed25519}, + Size :: 256, + Name :: string(), + Seed :: string(), + Encoding :: utf8 | base64 | base58, + Transform :: raw | {Algo, Yugeness}, + Algo :: sha3 | sha2 | x_or | pbkdf2, + Yugeness :: rand | non_neg_integer(). +%% @doc +%% Generate a new key. +%% The magic first two args are here because `ak_*' basically has no option other than +%% to mean a 256 bit key based on Curve 25519. When more key varieties are added to the +%% system this will change quite a lot. + +make_key({eddsa, ed25519}, 256, Name, Seed, Encoding, Transform) -> + gen_server:cast(?MODULE, {make_key, Name, Seed, Encoding, Transform}). + + +-spec recover_key(Mnemonic) -> ok + when Mnemonic :: string(). + +recover_key(Mnemonic) -> + gen_server:cast(?MODULE, {recover_key, Mnemonic}). + + +-spec mnemonic(ID) -> {ok, Mnemonic} | error + when ID :: clutch:id(), + Mnemonic :: string(). + +mnemonic(ID) -> + gen_server:call(?MODULE, {mnemonic, ID}). + + +-spec rename_key(ID, NewName) -> ok + when ID :: clutch:id(), + NewName :: string(). + +rename_key(ID, NewName) -> + gen_server:cast(?MODULE, {rename_key, ID, NewName}). + + +-spec drop_key(ID) -> ok + when ID :: clutch:id(). + +drop_key(ID) -> + gen_server:cast(?MODULE, {drop_key, ID}). + + +-spec stop() -> ok. + +stop() -> + gen_server:cast(?MODULE, stop). + + +-spec login(Phrase) -> ok + when Phrase :: string(). + +login(Phrase) -> + gen_server:cast(?MODULE, {login, Phrase}). + + +-spec password(Old, New) -> ok + when Old :: none | string(), + New :: none | string(). + +password(Old, New) -> + gen_server:cast(?MODULE, {password, Old, New}). + + +-spec save(Prefs) -> ok | {error, Reason} + when Prefs :: #{atom() := term()}, + Reason :: file:posix(). + +save(Prefs) -> + gen_server:call(?MODULE, {save, Prefs}). + + + +%%% Startup Functions + + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). +%% @private +%% Called by gmc_sup. + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). + + +-spec init(none) -> {ok, state()}. + +init(none) -> + ok = log(info, "Starting"), + Prefs = read_prefs(), + Window = gmc_gui:start_link(Prefs), + ok = log(info, "Window: ~p", [Window]), + ok = gmc_gui:ask_password(), + State = #s{window = Window}, + ArgV = zx_daemon:argv(), + ok = gmc_gui:show(ArgV), + {ok, State}. + + +read_prefs() -> + case file:consult(prefs_path()) of + {ok, Prefs} -> + proplists:to_map(Prefs); + _ -> + #{selected => 0, + lang => en_us, + geometry => none} + end. + + + +%%% gen_server Message Handling Callbacks + + +-spec handle_call(Message, From, State) -> Result + when Message :: term(), + From :: {pid(), reference()}, + State :: state(), + Result :: {reply, Response, NewState} + | {noreply, State}, + Response :: ok + | {error, {listening, inet:port_number()}}, + NewState :: state(). +%% @private +%% The gen_server:handle_call/3 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3 + +handle_call({save, Prefs}, _, State) -> + Response = do_save(State#s{prefs = Prefs}), + {reply, Response, State}; +handle_call({mnemonic, ID}, _, State) -> + Response = do_mnemonic(ID, State), + {reply, Response, State}; +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +-spec handle_cast(Message, State) -> {noreply, NewState} + when Message :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The gen_server:handle_cast/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 + +handle_cast({make_key, Name, Seed, Encoding, Transform}, State) -> + NewState = do_make_key(Name, Seed, Encoding, Transform, State), + {noreply, NewState}; +handle_cast({recover_key, Mnemonic}, State) -> + NewState = do_recover_key(Mnemonic, State), + {noreply, NewState}; +handle_cast({rename_key, ID, NewName}, State) -> + NewState = do_rename_key(ID, NewName, State), + {noreply, NewState}; +handle_cast({drop_key, ID}, State) -> + NewState = do_drop_key(ID, State), + {noreply, NewState}; +handle_cast({login, Phrase}, State) -> + NewState = do_login(Phrase, State), + {noreply, NewState}; +handle_cast({password, Old, New}, State) -> + NewState = do_password(Old, New, State), + {noreply, NewState}; +handle_cast(stop, State) -> + ok = log(info, "Received a 'stop' message."), + {stop, normal, State}; +handle_cast(Unexpected, State) -> + ok = tell(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +-spec handle_info(Message, State) -> {noreply, NewState} + when Message :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The gen_server:handle_info/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + + +%% @private +%% gen_server callback to handle state transformations necessary for hot +%% code updates. This template performs no transformation. + +code_change(_, State, _) -> + {ok, State}. + + +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + zx:stop(). + + + +%%% + +do_make_key(Name, <<>>, _, Transform, State) -> + Bin = crypto:strong_rand_bytes(32), + do_make_key2(Name, Bin, Transform, State); +do_make_key(Name, Seed, utf8, Transform, State) -> + Bin = unicode:characters_to_binary(Seed), + do_make_key2(Name, Bin, Transform, State); +do_make_key(Name, Seed, base64, Transform, State) -> + case base64_decode(Seed) of + {ok, Bin} -> + do_make_key2(Name, Bin, Transform, State); + {error, Reason} -> + ok = gmc_gui:notify({error, {base64, Reason}}), + State + end; +do_make_key(Name, Seed, base58, Transform, State) -> + case base58:check_base58(Seed) of + true -> + Bin = base58:base58_to_binary(Seed), + do_make_key2(Name, Bin, Transform, State); + false -> + ok = gmc_gui:notify({error, {base58, badarg}}), + State + end. + +do_make_key2(Name, Bin, Transform, State = #s{accounts = Accounts, keys = Keys}) -> + T = transform(Transform), + Seed = T(Bin), + Key = #key{name = KeyName, id = ID} = gmc_key_master:make_key(Name, Seed), + Account = #ak{name = KeyName, id = ID}, + NewKeys = [Key | Keys], + NewAccounts = [Account | Accounts], + ok = gmc_gui:show(NewAccounts), + State#s{accounts = NewAccounts, keys = NewKeys}. + + +base64_decode(String) -> + try + {ok, base64:decode(String)} + catch + E:R -> {E, R} + end. + + +transform({sha3, 256}) -> + fun(D) -> crypto:hash(sha3_256, D) end; +transform({sha2, 256}) -> + fun(D) -> crypto:hash(sha256, D) end; +transform({x_or, 256}) -> + fun t_xor/1; +transform(raw) -> + fun(D) -> D end. + + +t_xor(Bin) -> t_xor(Bin, <<0:256>>). + +t_xor(<>, A) -> + t_xor(T, crypto:exor(H, A)); +t_xor(<<>>, A) -> + A; +t_xor(B, A) -> + H = <<0:(256 - bit_size(B)), B/binary>>, + crypto:exor(H, A). + + +do_recover_key(Mnemonic, State = #s{keys = Keys, accounts = Accounts}) -> + case gmc_key_master:decode(Mnemonic) of + {ok, Seed} -> + do_recover_key2(Seed, State); + Error -> + ok = gmc_gui:notify(Error), + State + end. + +do_recover_key2(Seed, State = #s{keys = Keys, accounts = Accounts}) -> + Recovered = #key{id = ID, name = Name} = gmc_key_master:make_key("", Seed), + case lists:keymember(ID, #key.id, Keys) of + false -> + NewKeys = [Recovered | Keys], + Account = #ak{name = Name, id = ID}, + NewAccounts = [Account | Accounts], + ok = gmc_gui:show(NewAccounts), + State#s{accounts = NewAccounts, keys = NewKeys}; + true -> + State + end. + + +do_mnemonic(ID, #s{keys = Keys}) -> + case lists:keyfind(ID, #key.id, Keys) of + #key{pair = #{secret := <>}} -> + Mnemonic = gmc_key_master:encode(K), + {ok, Mnemonic}; + false -> + {error, bad_key} + end. + + +do_rename_key(ID, NewName, State = #s{accounts = Accounts, keys = Keys}) -> + A = lists:keyfind(ID, #ak.id, Accounts), + K = lists:keyfind(ID, #key.id, Keys), + NewAccounts = lists:keystore(ID, #ak.id, Accounts, A#ak{name = NewName}), + NewKeys = lists:keystore(ID, #key.id, Keys, K#key{name = NewName}), + ok = gmc_gui:show(NewAccounts), + State#s{accounts = NewAccounts, keys = NewKeys}. + + +do_drop_key(ID, State = #s{accounts = Accounts, keys = Keys}) -> + NewAccounts = lists:keydelete(ID, #ak.id, Accounts), + NewKeys = lists:keydelete(ID, #key.id, Keys), + ok = gmc_gui:show(NewAccounts), + State#s{accounts = NewAccounts, keys = NewKeys}. + + +encrypt(Pass, Binary) -> + Flags = [{encrypt, true}, {padding, pkcs_padding}], + crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). + + +decrypt(Pass, Binary) -> + Flags = [{encrypt, false}, {padding, pkcs_padding}], + crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). + + +pass(Phrase) -> + crypto:hash(sha3_256, Phrase). + + +do_login(none, State) -> + case read(none) of + {ok, Recovered = #s{accounts = Accounts}} -> + ok = gmc_gui:show(Accounts), + Recovered; + error -> + State + end; +do_login(Phrase, State = #s{pass = none}) -> + Pass = pass(Phrase), + try + case read(Pass) of + {ok, Recovered = #s{accounts = Accounts}} -> + ok = gmc_gui:show(Accounts), + Recovered; + error -> + State#s{pass = Pass} + end + catch + E:R -> + ok = tell("Problem with loading state: {~p, ~p}", [E, R]), + ok = gmc_gui:ask_password(), + State + end. + + +do_password(none, none, State) -> + State; +do_password(none, New, State = #s{pass = none}) -> + Pass = pass(New), + State#s{pass = Pass}; +do_password(Old, none, State = #s{pass = Pass}) -> + case pass(Old) =:= Pass of + true -> State#s{pass = none}; + false -> State + end; +do_password(Old, New, State = #s{pass = Pass}) -> + case pass(Old) =:= Pass of + true -> State#s{pass = pass(New)}; + false -> State + end. + + +do_save(State = #s{prefs = Prefs}) -> + ok = persist(Prefs), + do_save2(State). + + +do_save2(State = #s{pass = none}) -> + Path = save_path(), + ok = filelib:ensure_dir(Path), + file:write_file(Path, term_to_binary(State)); +do_save2(State = #s{pass = Pass}) -> + Path = save_path(), + ok = filelib:ensure_dir(Path), + Cipher = encrypt(Pass, term_to_binary(State)), + file:write_file(Path, Cipher). + + +read(none) -> + case file:read_file(save_path()) of + {ok, Bin} -> {ok, binary_to_term(Bin)}; + {error, enoent} -> error + end; +read(Pass) -> + case file:read_file(save_path()) of + {ok, Cipher} -> {ok, binary_to_term(decrypt(Pass, Cipher))}; + {error, enoent} -> error + end. + + +save_path() -> + filename:join(zx_lib:path(var, "otpr", "clutch"), "opaque.data"). + + +persist(Prefs) -> + Path = prefs_path(), + ok = filelib:ensure_dir(Path), + zx_lib:write_terms(Path, proplists:from_map(Prefs)). + + +prefs_path() -> + filename:join(zx_lib:path(etc, "otpr", "clutch"), "prefs.eterms"). diff --git a/src/gmc_gui.erl b/src/gmc_gui.erl new file mode 100644 index 0000000..d6a84db --- /dev/null +++ b/src/gmc_gui.erl @@ -0,0 +1,623 @@ +%%% @doc +%%% Clutch GUI +%%% +%%% This process is responsible for creating the main GUI frame displayed to the user. +%%% +%%% Reference: http://erlang.org/doc/man/wx_object.html +%%% @end + +-module(gmc_gui). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(wx_object). +-include_lib("wx/include/wx.hrl"). +-export([show/1, notify/1, ask_password/0]). +-export([start_link/1]). +-export([init/1, terminate/2, code_change/3, + handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). +-include("$zx_include/zx_logger.hrl"). +-include("gmc.hrl"). + + +-record(w, + {name = none :: atom(), + id = 0 :: integer(), + wx = none :: wx:wx_object()}). + +-record(s, + {wx = none :: none | wx:wx_object(), + frame = none :: none | wx:wx_object(), + lang = en :: en | jp, + j = none :: none | fun(), + prefs = #{} :: #{atom() := term()}, + accounts = [] :: [clutch:ak()], + picker = none :: none | wx:wx_object(), + id = {#w{}, #w{}} :: labeled(), + balance = {#w{}, #w{}} :: labeled(), + buttons = [] :: [widget()]}). + + +-type state() :: term(). +-type widget() :: #w{}. +-type labeled() :: {Label :: #w{}, Control :: #w{}}. + + + +%%% Interface functions + +show(Accounts) -> + wx_object:cast(?MODULE, {show, Accounts}). + + +notify(Message) -> + wx_object:cast(?MODULE, {notify, Message}). + + +ask_password() -> + wx_object:cast(?MODULE, password). + + + +%%% Startup Functions + +start_link(Accounts) -> + wx_object:start_link({local, ?MODULE}, ?MODULE, Accounts, []). + + +init(Prefs) -> + ok = log(info, "GUI starting..."), + Lang = maps:get(lang, Prefs, en_us), + Trans = gmc_jt:read_translations(?MODULE), + J = gmc_jt:j(Lang, Trans), + + Wx = wx:new(), + Frame = wxFrame:new(Wx, ?wxID_ANY, J("Gajumaru Clutch")), + MainSz = wxBoxSizer:new(?wxVERTICAL), + Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]), + + ID_L = wxStaticText:new(Frame, ?wxID_ANY, J("Account ID: ")), + ID_T = wxStaticText:new(Frame, ?wxID_ANY, ""), + ID_W = + {#w{id = wxStaticText:getId(ID_L), wx = ID_L}, + #w{id = wxStaticText:getId(ID_T), wx = ID_T}}, + ID_Sz = wxBoxSizer:new(?wxHORIZONTAL), + _ = wxSizer:add(ID_Sz, ID_L, zxw:flags(base)), + _ = wxSizer:add(ID_Sz, ID_T, zxw:flags(wide)), + + BalanceL = wxStaticText:new(Frame, ?wxID_ANY, "木"), + BalanceT = wxStaticText:new(Frame, ?wxID_ANY, price_to_string(0)), + Balance = + {#w{id = wxStaticText:getId(BalanceL), wx = BalanceL}, + #w{id = wxStaticText:getId(BalanceT), wx = BalanceT}}, + BalanceSz = wxBoxSizer:new(?wxHORIZONTAL), + _ = wxSizer:add(BalanceSz, BalanceL, zxw:flags(base)), + _ = wxSizer:add(BalanceSz, BalanceT, zxw:flags(wide)), + + ButtonTemplates = + [{make_key, J("Create")}, + {recover, J("Recover")}, + {mnemonic, J("Mnemonic")}, + {rename, J("Rename")}, + {drop_key, J("Delete")}, + {send, J("Send Money")}, + {recv, J("Receive Money")}, + {grids, J("GRIDS URL")}, + {refresh, J("Refresh")}], + + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + #w{name = Name, id = wxButton:getId(B), wx = B} + end, + + Buttons = lists:map(MakeButton, ButtonTemplates), + + AccountSz = wxBoxSizer:new(?wxHORIZONTAL), + ActionsSz = wxBoxSizer:new(?wxHORIZONTAL), + HistorySz = wxBoxSizer:new(?wxVERTICAL), + + #w{wx = MakeBn} = lists:keyfind(make_key, #w.name, Buttons), + #w{wx = RecoBn} = lists:keyfind(recover, #w.name, Buttons), + #w{wx = MnemBn} = lists:keyfind(mnemonic, #w.name, Buttons), + #w{wx = Rename} = lists:keyfind(rename, #w.name, Buttons), + #w{wx = DropBn} = lists:keyfind(drop_key, #w.name, Buttons), + _ = wxSizer:add(AccountSz, MakeBn, zxw:flags(wide)), + _ = wxSizer:add(AccountSz, RecoBn, zxw:flags(wide)), + _ = wxSizer:add(AccountSz, MnemBn, zxw:flags(wide)), + _ = wxSizer:add(AccountSz, Rename, zxw:flags(wide)), + _ = wxSizer:add(AccountSz, DropBn, zxw:flags(wide)), + + #w{wx = SendBn} = lists:keyfind(send, #w.name, Buttons), + #w{wx = RecvBn} = lists:keyfind(recv, #w.name, Buttons), + #w{wx = GridsBn} = lists:keyfind(grids, #w.name, Buttons), + _ = wxSizer:add(ActionsSz, SendBn, zxw:flags(wide)), + _ = wxSizer:add(ActionsSz, RecvBn, zxw:flags(wide)), + _ = wxSizer:add(ActionsSz, GridsBn, zxw:flags(wide)), + + #w{wx = Refresh} = lists:keyfind(refresh, #w.name, Buttons), + _ = wxSizer:add(HistorySz, Refresh, zxw:flags(base)), + + + _ = wxSizer:add(MainSz, AccountSz, zxw:flags(base)), + _ = wxSizer:add(MainSz, Picker, zxw:flags(wide)), + _ = wxSizer:add(MainSz, ID_Sz, zxw:flags(base)), + _ = wxSizer:add(MainSz, BalanceSz, zxw:flags(base)), + _ = wxSizer:add(MainSz, ActionsSz, zxw:flags(base)), + _ = wxSizer:add(MainSz, HistorySz, [{proportion, 3}, {flag, ?wxEXPAND}]), + ok = wxFrame:setSizer(Frame, MainSz), + ok = wxSizer:layout(MainSz), + + ok = + case safe_size(Prefs) of + {pref, max} -> + wxTopLevelWindow:maximize(Frame); + {pref, WSize} -> + wxFrame:setSize(Frame, WSize); + {center, WSize} -> + ok = wxFrame:setSize(Frame, WSize), + wxFrame:center(Frame) + end, + + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + true = wxFrame:show(Frame), + ok = wxListBox:connect(Picker, command_listbox_selected), + State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, + accounts = [], + picker = Picker, + id = ID_W, + balance = Balance, + buttons = Buttons}, + {Frame, State}. + + +safe_size(Prefs) -> + Display = wxDisplay:new(), + GSize = wxDisplay:getGeometry(Display), + CSize = wxDisplay:getClientArea(Display), + PPI = + try + wxDisplay:getPPI(Display) + catch + Class:Exception -> {Class, Exception} + end, + ok = log(info, "Geometry: ~p", [GSize]), + ok = log(info, "ClientArea: ~p", [CSize]), + ok = log(info, "PPI: ~p", [PPI]), + Geometry = + case maps:find(geometry, Prefs) of + {ok, none} -> + {X, Y, _, _} = GSize, + {center, {X, Y, 420, 520}}; + {ok, G} -> + {pref, G}; + error -> + {X, Y, _, _} = GSize, + {center, {X, Y, 420, 520}} + end, + ok = wxDisplay:destroy(Display), + Geometry. + + +-spec handle_call(Message, From, State) -> Result + when Message :: term(), + From :: {pid(), reference()}, + State :: state(), + Result :: {reply, Response, NewState} + | {noreply, State}, + Response :: ok + | {error, {listening, inet:port_number()}}, + NewState :: state(). + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +-spec handle_cast(Message, State) -> {noreply, NewState} + when Message :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The gen_server:handle_cast/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 + +handle_cast({show, Accounts}, State) -> + NewState = do_show(Accounts, State), + {noreply, NewState}; +handle_cast(password, State) -> + ok = do_ask_password(State), + {noreply, State}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +-spec handle_info(Message, State) -> {noreply, NewState} + when Message :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The gen_server:handle_info/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + +-spec handle_event(Event, State) -> {noreply, NewState} + when Event :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The wx_object:handle_event/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, + id = ID}, + State = #s{buttons = Buttons}) -> + NewState = + case lists:keyfind(ID, #w.id, Buttons) of + #w{name = make_key} -> make_key(State); + #w{name = recover} -> recover_key(State); + #w{name = mnemonic} -> show_mnemonic(State); + #w{name = rename} -> rename_key(State); + #w{name = drop_key} -> drop_key(State); + #w{name = grids} -> grids_dialogue(State); + #w{name = Name} -> handle_button(Name, State); + false -> State + end, + {noreply, NewState}; +handle_event(#wx{event = #wxCommand{type = command_listbox_selected, + commandInt = Selected}}, + State) -> + NewState = do_selection(Selected, State), + {noreply, NewState}; +handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs}) -> + Geometry = + case wxTopLevelWindow:isMaximized(Frame) of + true -> + max; + false -> + {X, Y} = wxWindow:getPosition(Frame), + {W, H} = wxWindow:getSize(Frame), + {X, Y, W, H} + end, + NewPrefs = maps:put(geometry, Geometry, Prefs), + ok = gmc_con:save(NewPrefs), + ok = gmc_con:stop(), + ok = wxWindow:destroy(Frame), + {noreply, State}; +handle_event(Event, State) -> + ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + wx:destroy(). + + +%%% Doers + +make_key(State = #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Key")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + + NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name (Optional)")}]), + NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), + + SeedSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Seed (Optional)")}]), + SeedTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxDEFAULT bor ?wxTE_MULTILINE}]), + ok = wxTextCtrl:setValue(SeedTx, base64:encode(crypto:strong_rand_bytes(64))), + + OptionsSz = wxStaticBoxSizer:new(?wxHORIZONTAL, Dialog, [{label, J("Options")}]), + Encodings = + ["Base64", + "UTF-8", + "Base58"], + EncodingOptions = wxChoice:new(Dialog, ?wxID_ANY, [{choices, Encodings}]), + ok = wxChoice:setSelection(EncodingOptions, 0), + Transforms = + ["SHA-3 (256)", + "SHA-2 (256)", + "XOR", + "Direct Input (no transform)"], + TransformOptions = wxChoice:new(Dialog, ?wxID_ANY, [{choices, Transforms}]), + ok = wxChoice:setSelection(TransformOptions, 0), + + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + + _ = wxStaticBoxSizer:add(NameSz, NameTx, zxw:flags(base)), + _ = wxStaticBoxSizer:add(SeedSz, SeedTx, zxw:flags(wide)), + _ = wxStaticBoxSizer:add(OptionsSz, EncodingOptions, zxw:flags(wide)), + _ = wxStaticBoxSizer:add(OptionsSz, TransformOptions, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, NameSz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, SeedSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, OptionsSz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:setSize(Dialog, {400, 300}), + ok = wxFrame:center(Dialog), + + ok = wxStyledTextCtrl:setFocus(NameTx), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Type = {eddsa, ed25519}, + Size = 256, + Name = wxTextCtrl:getValue(NameTx), + Seed = wxTextCtrl:getValue(SeedTx), + Encoding = + case wxChoice:getSelection(EncodingOptions) of + 0 -> base64; + 1 -> utf8; + 2 -> base58 + end, + Transform = + case wxChoice:getSelection(TransformOptions) of + 0 -> {sha3, 256}; + 1 -> {sha2, 256}; + 2 -> {x_or, 256}; + 3 -> raw + end, + gmc_con:make_key(Type, Size, Name, Seed, Encoding, Transform); + ?wxID_CANCEL -> + ok + end, + ok = wxDialog:destroy(Dialog), + State. + + +recover_key(State = #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Mnemonic")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + MnemSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Recovery Phrase")}]), + MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]), + _ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(MnemTx), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Mnemonic = wxTextCtrl:getValue(MnemTx), + gmc_con:recover_key(Mnemonic); + ?wxID_CANCEL -> + ok + end, + ok = wxDialog:destroy(Dialog), + State. + + +show_mnemonic(State = #s{accounts = []}) -> + State; +show_mnemonic(State = #s{picker = Picker}) -> + case wxListBox:getSelection(Picker) of + -1 -> State; + Selected -> show_mnemonic(Selected + 1, State) + end. + +show_mnemonic(Selected, State = #s{frame = Frame, j = J, accounts = Accounts}) -> + #ak{id = ID} = lists:nth(Selected, Accounts), + {ok, Mnemonic} = gmc_con:mnemonic(ID), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Mnemonic")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + MnemSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Recovery Phrase")}]), + Options = [{value, Mnemonic}, {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}], + MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, Options), + _ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(MnemTx), + ?wxID_OK = wxDialog:showModal(Dialog), + ok = wxDialog:destroy(Dialog), + State. + + +rename_key(State = #s{accounts = []}) -> + State; +rename_key(State = #s{picker = Picker}) -> + case wxListBox:getSelection(Picker) of + -1 -> State; + Selected -> rename_key(Selected + 1, State) + end. + +rename_key(Selected, State = #s{frame = Frame, j = J, accounts = Accounts}) -> + #ak{id = ID, name = Name} = lists:nth(Selected, Accounts), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Key")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name (Optional)")}]), + NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), + ok = wxTextCtrl:setValue(NameTx, Name), + _ = wxStaticBoxSizer:add(NameSz, NameTx, zxw:flags(base)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, NameSz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(NameTx), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + NewName = wxTextCtrl:getValue(NameTx), + gmc_con:rename_key(ID, NewName); + ?wxID_CANCEL -> + ok + end, + ok = wxDialog:destroy(Dialog), + State. + + +drop_key(State = #s{accounts = []}) -> + State; +drop_key(State = #s{picker = Picker}) -> + case wxListBox:getSelection(Picker) of + -1 -> State; + Selected -> drop_key(Selected + 1, State) + end. + +drop_key(Selected, State = #s{frame = Frame, j = J, accounts = Accounts}) -> + #ak{id = ID, name = Name} = lists:nth(Selected, Accounts), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Key")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Message = ["REALLY delete key: ", Name, " ?"], + MessageT = wxStaticText:new(Dialog, ?wxID_ANY, Message), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, MessageT, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> gmc_con:drop_key(ID); + ?wxID_CANCEL -> ok + end, + ok = wxDialog:destroy(Dialog), + State. + + +grids_dialogue(State = #s{frame = Frame, j = J}) -> + tell("Handle GRIDS URL"), +% ok = +% case zxw:modal_text_input(Frame, J("GRIDS"), J("ZA GRIDS"), [J("URL")]) of +% {ok, [URL]} -> tell("Received URL: ~p", [URL]); +% cancel -> tell("User cancelled GRIDS action.") +% end, + State. + + +handle_button(Name, State) -> + ok = tell("Button Click: ~p", [Name]), + State. + + + +do_selection(Selected, + State = #s{prefs = Prefs, accounts = Accounts, + balance = {_, #w{wx = B}}, id = {_, #w{wx = I}}}) -> + #ak{id = ID, balance = Pucks} = lists:nth(Selected + 1, Accounts), + ok = wxStaticText:setLabel(I, ID), + ok = wxStaticText:setLabel(B, price_to_string(Pucks)), + NewPrefs = maps:put(selected, Selected, Prefs), + State#s{prefs = NewPrefs}. + + +do_show(Accounts, State = #s{prefs = Prefs, picker = Picker}) -> + AKs = [Name || #ak{name = Name} <- Accounts], + ok = wxListBox:set(Picker, AKs), + case Accounts of + [] -> + State; + [_] -> + Selected = 0, + ok = wxListBox:setSelection(Picker, Selected), + do_selection(Selected, State#s{accounts = Accounts}); + _ -> + Selected = maps:get(selected, Prefs), + ok = wxListBox:setSelection(Picker, Selected), + do_selection(Selected, State#s{accounts = Accounts}) + end. + + +do_ask_password(#s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Label = "Password (leave blank for no password)", + PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]), + PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY), + _ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, PassSz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(PassTx), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Phrase = + case wxTextCtrl:getValue(PassTx) of + "" -> none; + P -> P + end, + gmc_con:login(Phrase); + ?wxID_CANCEL -> + gmc_con:login(none) + end, + wxDialog:destroy(Dialog). + + + +%%% Helpers + + +price_to_string(Pucks) -> + Gaju = 1000000000000000000, + H = integer_to_list(Pucks div Gaju), + R = Pucks rem Gaju, + case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of + [] -> H; + T -> string:join([H, T], ".") + end. + +string_to_price(String) -> + case string:split(String, ".") of + [H] -> join_price(H, "0"); + [H, T] -> join_price(H, T); + _ -> {error, bad_price} + end. + +join_price(H, T) -> + try + Parts = [H, string:pad(T, 18, trailing, $0)], + Price = list_to_integer(unicode:characters_to_list(Parts)), + case Price < 0 of + false -> {ok, Price}; + true -> {error, negative_price} + end + catch + error:R -> {error, R} + end. diff --git a/src/gmc_jt.erl b/src/gmc_jt.erl new file mode 100644 index 0000000..0e838b6 --- /dev/null +++ b/src/gmc_jt.erl @@ -0,0 +1,39 @@ +%%% A rework of the JumpText idea. To be adapted into actual JumpText for v2.0. +%%% To add translations to a module you add a file named ?MODULE.trans to priv/i18n/. +%%% The file contains an Erlang terms file of the form: +%%% `{LangCode :: atom(), Translations :: #{Phrase :: string() := Translation :: string()}}' +%%% The entire file's contents are returned by `read_translations/1'. +%%% The function `j/2' extracts the dictionary requested and returns a translation +%%% function that closes over the desired phrase dictionary. +%%% The customary way to insert translations into a program is to assign the translation +%%% closure the name `J' and keep it in the local process state. When the user selects +%%% another language, create a new `J' with the desired language. +%%% +%%% `oneshot_j/2' is a convenience function for those who don't intend to keep the +%%% library of translations in memory (calling `oneshot_j/2' will always perform a disk +%%% read, where `j/2' could be called any number of times without a disk read if the +%%% translation library is retained). + +-module(gmc_jt). +-export([read_translations/1, j/2, oneshot_j/2]). + + +read_translations(Module) -> + File = atom_to_list(Module) ++ ".trans", + Path = filename:join([zx:get_home(), "priv", "i18n", File]), + case file:consult(Path) of + {ok, Translations} -> Translations; + {error, enoent} -> [] + end. + + +j(Lang, Translations) -> + case proplists:get_value(Lang, Translations, none) of + none -> fun(Phrase) -> Phrase end; + Dict -> fun(Phrase) -> maps:get(Phrase, Dict, Phrase) end + end. + + +oneshot_j(Module, Lang) -> + Translations = read_translations(Module), + j(Lang, Translations). diff --git a/src/gmc_key_master.erl b/src/gmc_key_master.erl new file mode 100644 index 0000000..b9c10e8 --- /dev/null +++ b/src/gmc_key_master.erl @@ -0,0 +1,204 @@ +%%% @doc +%%% Key functions go here. +%%% +%%% The main reason this is a module of its own is that in the original architecture +%%% it was a process rather than just a library of functions. Now that it exists, though, +%%% there is little motivation to cram everything here into the controller process's +%%% code. +%%% @end + +-module(gmc_key_master). + + +-export([make_key/2, encode/1, decode/1]). +-export([lcg/1]). +%-export([start_link/1]). +%-export([init/1, terminate/2, code_change/3, +% handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). +%-include("$zx_include/zx_logger.hrl"). +-include("gmc.hrl"). + + +%-record(s, +% {wx = none :: none | wx:wx_object(), +% frame = none :: none | wx:wx_object(), +% lang = en :: en | jp, +% j = none :: none | fun(), +% prefs = #{} :: #{atom() := term()}, +% accounts = [] :: [clutch:ak()], +% picker = none :: none | wx:wx_object(), +% balance = {#w{}, #w{}} :: labeled(), +% buttons = [] :: [widget()]}). + + + +%%% Startup Functions + +%start_link(Accounts) -> +% wx_object:start_link({local, ?MODULE}, ?MODULE, Accounts, []). + + +%init({Accounts, Prefs}) -> +% ok = log(info, "GUI starting..."), +% Lang = maps:get(lang, Prefs, en_us), +% Trans = gmc_jt:read_translations(?MODULE), +% J = gmc_jt:j(Lang, Trans), +% +% Selected = maps:get(selected, Prefs, 1), +% AKs = [ID || #ak{id = ID} <- Accounts], +% +% Wx = wx:new(), +% Frame = wxFrame:new(Wx, ?wxID_ANY, J("Gajumaru Clutch")), +% MainSz = wxBoxSizer:new(?wxVERTICAL), + + + +%%% + + +make_key("", <<>>) -> + Pair = #{public := Public} = ecu_eddsa:sign_keypair(), + ID = aeser_api_encoder:encode(account_pubkey, Public), + Name = binary_to_list(ID), + #key{name = Name, id = ID, pair = Pair}; +make_key("", Seed) -> + Pair = #{public := Public} = ecu_eddsa:sign_seed_keypair(Seed), + ID = aeser_api_encoder:encode(account_pubkey, Public), + Name = binary_to_list(ID), + #key{name = Name, id = ID, pair = Pair}; +make_key(Name, <<>>) -> + Pair = #{public := Public} = ecu_eddsa:sign_keypair(), + ID = aeser_api_encoder:encode(account_pubkey, Public), + #key{name = Name, id = ID, pair = Pair}; +make_key(Name, Seed) -> + Pair = #{public := Public} = ecu_eddsa:sign_seed_keypair(Seed), + ID = aeser_api_encoder:encode(account_pubkey, Public), + #key{name = Name, id = ID, pair = Pair}. + + +-spec encode(Secret) -> Phrase + when Secret :: binary(), + Phrase :: string(). +%% @doc +%% The encoding and decoding procesures are written to be able to handle any +%% width of bitstring or binary and a variable size dictionary. The magic numbers +%% 32, 4096 and 12 have been dropped in because currently these are known, but that +%% will change in the future if the key size or type changes. + +encode(Bin) -> + <> = Bin, + DictSize = 4096, + Words = read_words(), +% Width = chunksize(DictSize - 1, 2), + Width = 12, + Chunks = chunksize(Number, DictSize), + Binary = <>, + encode(Width, Binary, Words). + +encode(Width, Bits, Words) -> + CheckSum = checksum(Width, Bits), + encode(Width, <>, Words, []). + +encode(_, <<>>, _, Acc) -> + unicode:characters_to_list(lists:join(" ", lists:reverse(Acc))); +encode(Width, Bits, Words, Acc) -> + <> = Bits, + Word = lists:nth(I + 1, Words), + encode(Width, Rest, Words, [Word | Acc]). + + +-spec decode(Phrase) -> {ok, Secret} | {error, Reason} + when Phrase :: string(), + Secret :: binary(), + Reason :: bad_phrase | bad_word. +%% @doc +%% Reverses the encoded secret string back into its binary representation. + +decode(Encoded) -> + DictSize = 4096, + Words = read_words(), + Width = chunksize(DictSize - 1, 2), + decode(Width, Words, Encoded). + +decode(Width, Words, Encoded) when is_list(Encoded) -> + decode(Width, Words, list_to_binary(Encoded)); +decode(Width, Words, Encoded) -> + Split = string:lexemes(Encoded, " "), + decode(Width, Words, Split, <<>>). + +decode(Width, Words, [Word | Rest], Acc) -> + case find(Word, Words) of + {ok, N} -> decode(Width, Words, Rest, <>); + Error -> Error + end; +decode(Width, _, [], Acc) -> + sumcheck(Width, Acc). + + +chunksize(N, C) -> + chunksize(N, C, 0). + +chunksize(0, _, A) -> A; +chunksize(N, C, A) -> chunksize(N div C, C, A + 1). + + +read_words() -> + Path = filename:join([zx:get_home(), "priv", "words4096.txt"]), + {ok, Bin} = file:read_file(Path), + string:lexemes(Bin, "\n"). + + +find(Word, Words) -> + find(Word, Words, 0). + +find(Word, [Word | _], N) -> {ok, N}; +find(Word, [_ | Rest], N) -> find(Word, Rest, N + 1); +find(Word, [], _) -> {error, {bad_word, Word}}. + + +checksum(Width, Bits) -> + checksum(Width, Bits, 0). + +checksum(_, <<>>, Sum) -> + Sum; +checksum(Width, Bits, Sum) -> + <> = Bits, + checksum(Width, Rest, N bxor Sum). + + +sumcheck(Width, Bits) -> + <> = Bits, + case checksum(Width, Binary) =:= CheckSum of + true -> + <> = Binary, + {ok, <>}; + false -> + {error, bad_phrase} + end. + + + +-spec lcg(integer()) -> integer(). +%% A simple PRNG that fits into 32 bits and is easy to implement anywhere (Kotlin). +%% Specifically, it is a "linear congruential generator" of the Lehmer variety. +%% The constants used are based on recommendations from Park, Miller and Stockmeyer: +%% https://www.firstpr.com.au/dsp/rand31/p105-crawford.pdf#page=4 +%% +%% The input value should be between 1 and 2^31-1. +%% +%% The purpose of this PRNG is for password-based dictionary shuffling. + +lcg(N) -> + M = 16#7FFFFFFF, + A = 48271, + Q = 44488, % M div A + R = 3399, % M rem A + Div = N div Q, + Rem = N rem Q, + S = Rem * A, + T = Div * R, + Result = S - T, + case Result < 0 of + false -> Result; + true -> Result + M + end. diff --git a/src/gmc_sup.erl b/src/gmc_sup.erl new file mode 100644 index 0000000..fa72a85 --- /dev/null +++ b/src/gmc_sup.erl @@ -0,0 +1,46 @@ +%%% @doc +%%% Clutch Top-level Supervisor +%%% +%%% The very top level supervisor in the system. It only has one service branch: the +%%% "con" (program controller). The con is the +%%% only be one part of a larger system. Were this a game system, for example, the +%%% item data management service would be a peer, as would a login credential provision +%%% service, game world event handling, and so on. +%%% +%%% See: http://erlang.org/doc/design_principles/applications.html +%%% See: http://zxq9.com/archives/1311 +%%% @end + +-module(gmc_sup). +-vsn("0.1.0"). +-behaviour(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-export([start_link/0]). +-export([init/1]). + + +-spec start_link() -> {ok, pid()}. +%% @private +%% This supervisor's own start function. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + + +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init([]) -> + RestartStrategy = {one_for_one, 0, 60}, + Clients = {gmc_con, + {gmc_con, start_link, []}, + permanent, + 5000, + worker, + [gmc_con]}, + Children = [Clients], + {ok, {RestartStrategy, Children}}. diff --git a/zomp.meta b/zomp.meta new file mode 100644 index 0000000..845be50 --- /dev/null +++ b/zomp.meta @@ -0,0 +1,22 @@ +{name,"Clutch"}. +{type,gui}. +{modules,[]}. +{prefix,"gmc"}. +{desc,"A desktop client for the Gajumaru network of blockchain networks"}. +{author,"Craig Everett"}. +{package_id,{"otpr","clutch",{0,1,0}}}. +{deps,[{"otpr","erl_base58",{0,1,0}}, + {"otpr","aeserialization",{0,1,0}}, + {"otpr","eblake2",{1,0,0}}, + {"otpr","ec_utils",{1,0,0}}, + {"otpr","zxwidgets",{1,0,1}}]}. +{key_name,none}. +{a_email,"craigeverett@qpq.swiss"}. +{c_email,"craigeverett@qpq.swiss"}. +{copyright,"Craig Everett"}. +{file_exts,[]}. +{license,"GPL-3.0-or-later"}. +{repo_url,"https://gitlab.com/ioecs/clutch"}. +{tags,["gaju","gm","gajumaru","wallet","blockchain","cryptocurrency","crypto", + "puck"]}. +{ws_url,"https://gajumaru.io"}.