From 0781198909a8abb49fa11eafcd1dd08d7416a6e6 Mon Sep 17 00:00:00 2001 From: aiooss-anssi Date: Thu, 9 Nov 2023 16:20:37 +0100 Subject: [PATCH] Initial public code release --- .github/demo.webp | Bin 0 -> 50282 bytes .github/workflows/linter.yml | 25 + .gitignore | 12 + COPYING | 674 ++ README.md | 100 + docker-compose.yml | 32 + example.env | 62 + grafana/Dockerfile | 10 + grafana/dashboards/home.json | 401 ++ grafana/provisioning/dashboards/default.yml | 8 + grafana/provisioning/datasources/sql.yml | 8 + suricata/.dockerignore | 1 + suricata/Dockerfile | 6 + suricata/custom.rules | 70 + suricata/entrypoint.sh | 8 + suricata/lua/sha2.lua | 5675 +++++++++++++++++ suricata/lua/tcpstore.lua | 62 + suricata/lua/udpstore.lua | 71 + suricata/output/.gitkeep | 0 suricata/suricata.yaml | 2168 +++++++ webapp/.dockerignore | 5 + webapp/Dockerfile | 9 + webapp/database.py | 314 + webapp/main.py | 286 + webapp/static/assets/css/bootstrap.min.css | 6 + webapp/static/assets/css/style.css | 61 + webapp/static/assets/js/api.js | 64 + .../static/assets/js/bootstrap.bundle.min.js | 7 + webapp/static/assets/js/flowdisplay.js | 277 + webapp/static/assets/js/flowlist.js | 390 ++ webapp/static/favicon.svg | 10 + webapp/static/filestore | 1 + webapp/static/input_pcaps | 1 + webapp/static/tcpstore | 1 + webapp/static/udpstore | 1 + webapp/templates/http-replay.py.jinja2 | 53 + webapp/templates/index.html | 156 + webapp/templates/tcp-replay.py.jinja2 | 49 + 38 files changed, 11084 insertions(+) create mode 100644 .github/demo.webp create mode 100644 .github/workflows/linter.yml create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 example.env create mode 100644 grafana/Dockerfile create mode 100644 grafana/dashboards/home.json create mode 100644 grafana/provisioning/dashboards/default.yml create mode 100644 grafana/provisioning/datasources/sql.yml create mode 100644 suricata/.dockerignore create mode 100644 suricata/Dockerfile create mode 100644 suricata/custom.rules create mode 100755 suricata/entrypoint.sh create mode 100644 suricata/lua/sha2.lua create mode 100644 suricata/lua/tcpstore.lua create mode 100644 suricata/lua/udpstore.lua create mode 100644 suricata/output/.gitkeep create mode 100644 suricata/suricata.yaml create mode 100644 webapp/.dockerignore create mode 100644 webapp/Dockerfile create mode 100644 webapp/database.py create mode 100644 webapp/main.py create mode 100644 webapp/static/assets/css/bootstrap.min.css create mode 100644 webapp/static/assets/css/style.css create mode 100644 webapp/static/assets/js/api.js create mode 100644 webapp/static/assets/js/bootstrap.bundle.min.js create mode 100644 webapp/static/assets/js/flowdisplay.js create mode 100644 webapp/static/assets/js/flowlist.js create mode 100644 webapp/static/favicon.svg create mode 120000 webapp/static/filestore create mode 120000 webapp/static/input_pcaps create mode 120000 webapp/static/tcpstore create mode 120000 webapp/static/udpstore create mode 100644 webapp/templates/http-replay.py.jinja2 create mode 100644 webapp/templates/index.html create mode 100644 webapp/templates/tcp-replay.py.jinja2 diff --git a/.github/demo.webp b/.github/demo.webp new file mode 100644 index 0000000000000000000000000000000000000000..5662c7ff540fbc876e8f17675e03fa5a31deae4a GIT binary patch literal 50282 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 literal 0 HcmV?d00001 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..313d533 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,25 @@ +name: Lint code + +on: [ push, pull_request ] + +jobs: + python-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 + with: + src: "./webapp" + - uses: psf/black@stable + with: + src: "./webapp" + + javascript-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v1 + with: + node-version: '*' + - run: npm install standard + - run: npx standard ./webapp/static/assets diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dc95d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +input_pcaps/* +suricata/output/filestore +suricata/output/tcpstore +suricata/output/udpstore +suricata/output/*.json +suricata/output/*.log +webapp/database/* +.env + +# Python +__pycache__ +.ruff_cache diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/COPYING @@ -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/README.md b/README.md new file mode 100644 index 0000000..947922c --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Shovel + +Shovel is a web application that offers a graphical user interface to explore +[Suricata EVE outputs](https://docs.suricata.io/en/suricata-7.0.1/output/eve/eve-json-output.html). +Its primary focus is to help [Capture-the-Flag players](https://en.wikipedia.org/wiki/Capture_the_flag_(cybersecurity)) +analyse network traffic dumps during stressful and time-limited attack-defense games such as +[FAUSTCTF](https://faustctf.net/) or [ECSC](https://ecsc.eu/). +Shovel is developed in the context of +[ECSC Team France](https://ctftime.org/team/159269/) training. + +![Shovel during ENOWARS7](./.github/demo.webp) + +You might also want to have a look at these other awesome traffic analyser tools: + + - https://github.com/secgroup/flower (first commit in 2018) + - https://github.com/eciavatta/caronte (first commit in 2020) + - https://github.com/OpenAttackDefenseTools/tulip (fork from flower in May 2022) + +Compared to these traffic analyser tools, Shovel relies on Suricata while making +some opinionated choices for the frontend. This has a few nice implications: + + - dissection of all application protocols already supported by Suricata (TCP and UDP), + - use a single SQLite database, + - on disk TCP/UDP/HTTP payload deduplication, + - filters based on libmagic, e.g. quickly filter flows containing PDF documents or PNG images, + - no heavy build tools needed, Shovel is easy to tweak. + +Moreover, Shovel is batteries-included with Grafana visualizations and some Suricata alert rules. + +## Setup + +### 0. Before the Capture-the-Flag event begins + +Copy `example.env` to `.env` and tweak the configuration parameters. +Also add the flag format in `suricata/custom.rules` if needed. + +If you are playing a CTF using an IPv6 network, you might want to [enable IPv6 support in Docker deamon](https://docs.docker.com/config/daemon/ipv6/) before the CTF starts. + +### 1. Network capture setup + +You should place network captures in `input_pcaps/` folder. +Capture files should be splitted into chunks to be progressively imported. +If the CTF event does not already provide PCAP files, then you can adapt one +of the following commands for a GNU/Linux system: +```bash +ssh root@10.20.9.6 tcpdump -i wg-faustctf -n -w - 'tcp port not 22' | tcpdump -n -r - -G 30 -w input_pcaps/trace-%Y-%m-%d_%H-%M-%S.pcap +``` +For a Microsoft Windows system, you may adapt the following command (3389 is RDP): +```powershell +.\tshark.exe -b duration:60 -w \\share\captures\trace -f "tcp port not 3389" +``` + +### 2. Launch Suricata and webapp via Docker (option A) + +Start Suricata, the web application and Grafana using `docker compose up -d --build`. + +Please note that restarting Suricata will cause all network capture files to be loaded again from zero. + +### 2. Launch Suricata and webapp traditionally (option B) + +You may launch Suricata then the web application using the following: +```bash +# Start Suricata +export $(grep -vE "^(#.*|\s*)$" .env) +./suricata/entrypoint.sh +``` + +```bash +# Start web app +export $(grep -vE "^(#.*|\s*)$" .env) +(cd webapp && uvicorn --host 0.0.0.0 main:app) +``` + +Please note that restarting Suricata will cause all network capture files to be loaded again from zero. + +## Frequently Asked Questions + +### Is Suricata `flow_id` really unique? + +`flow_id` is derived from timestamp (ms scale) and current flow parameters (such +as source and destination ports and addresses). See source code: +. + +### How do I reload rules without rebuilding the database? + +You can edit suricata rules in `suricata/custom.rules`, then reload the rules +using: +```bash +kill -USR2 $(pidof suricata) +``` + +## Licensing + +Copyright (C) 2023 ANSSI + +Shovel 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, version 3. + +Shovel 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 Shovel. If not, see . diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e6c927c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: "3" + +services: + suricata: + build: ./suricata + image: shovel-suricata + volumes: + - "./input_pcaps:/input_pcaps:ro" + - "./suricata/custom.rules:/suricata/custom.rules:ro" + - "./suricata/output:/suricata/output:rw" + env_file: + - .env + + webapp: + build: ./webapp + image: shovel-webapp + volumes: + - "./input_pcaps:/webapp/static/input_pcaps:ro" + - "./suricata/output:/suricata/output:ro" + - "./webapp/database:/webapp/database:rw" + ports: + - 8000:8000 + env_file: + - .env + + grafana: + build: ./grafana + image: shovel-grafana + volumes: + - "./webapp/database:/webapp/database:ro" + ports: + - 3000:3000 diff --git a/example.env b/example.env new file mode 100644 index 0000000..1ba3807 --- /dev/null +++ b/example.env @@ -0,0 +1,62 @@ +# Examples from FAUSTCTF (2023-09-23) +CTF_START_DATE=2023-09-23T15:00+02:00 +CTF_TICK_LENGTH=180 +CTF_HOME_NET=[fd66:666::0/32] +CTF_SERVICES=chatapp,image_galoisry,jokes,rsamail,buerographie_app,tic_tac_toe,office_supplies,auction_service +CTF_SERVICE_AUCTION_SERVICE=[fd66:666:798::2]:12345,[fd66:666:798::2]:12346 +CTF_SERVICE_BUEROGRAPHIE_APP=[fd66:666:798::2]:13731 +CTF_SERVICE_CHATAPP=[fd66:666:798::2]:3000 +CTF_SERVICE_IMAGE_GALOISRY=[fd66:666:798::2]:5005 +CTF_SERVICE_JOKES=[fd66:666:798::2]:5000 +CTF_SERVICE_OFFICE_SUPPLIES=[fd66:666:798::2]:1337 +CTF_SERVICE_RSAMAIL=[fd66:666:798::2]:5555 +CTF_SERVICE_TIC_TAC_TOE=[fd66:666:798::2]:3333 + +# Examples from ENOWARS7 (2023-07-22) +#CTF_START_DATE=2023-07-22T15:00+02:00 +#CTF_TICK_LENGTH=60 +#CTF_HOME_NET=[10.1.42.1/25] +#CTF_SERVICES=asocialnetwork,bollwerk,granulizer,oldschool,phreaking,steinsgate,yvm +#CTF_SERVICE_ASOCIALNETWORK=10.1.42.1:3000 +#CTF_SERVICE_BOLLWERK=10.1.42.1:6009 +#CTF_SERVICE_GRANULIZER=10.1.42.1:2345 +#CTF_SERVICE_OLDSCHOOL=10.1.42.1:9080 +#CTF_SERVICE_PHREAKING=10.1.42.1:3399,10.1.42.1:6060,10.1.42.1:9930,10.1.42.1:6061,10.1.42.1:9931,10.1.42.1:6062,10.1.42.1:9932,10.1.42.1:6063,10.1.42.1:9933,10.1.42.1:6064,10.1.42.1:9934,10.1.42.1:6065,10.1.42.1:9935,10.1.42.1:6066,10.1.42.1:9936,10.1.42.1:6067,10.1.42.1:9937,10.1.42.1:6068,10.1.42.1:9938,10.1.42.1:6069,10.1.42.1:9939 +#CTF_SERVICE_STEINSGATE=10.1.42.1:4433,10.1.42.1:4420 +#CTF_SERVICE_YVM=10.1.42.1:3165 + +# Examples from ICC 2023 training (2023-07-09) +#CTF_START_DATE=2023-07-09T11:00+02:00 +#CTF_TICK_LENGTH=120 +#CTF_HOME_NET=[10.20.9.0/24] +#CTF_SERVICES=flag_prescription,navashield,win_dc1,win_srv1 +#CTF_SERVICE_FLAG_PRESCRIPTION=10.20.9.6:5001 +#CTF_SERVICE_NAVASHIELD=10.20.9.6:8001,10.20.9.6:5000 +#CTF_SERVICE_WIN_DC1=10.20.9.4:80,10.20.9.4:445,10.20.9.4:135 +#CTF_SERVICE_WIN_SRV1=10.20.9.5:80,10.20.9.5:31337,10.20.9.5:135 + +# Examples from ECSC 2022 (2022-09-15) +#CTF_START_DATE=2023-01-30T13:00+02:00 +#CTF_TICK_LENGTH=120 +#CTF_HOME_NET=[10.20.9.0/24] +#CTF_SERVICES=dewaste,cantina,hps,aquaeductus,blinkygram,winds_of_the_past,techbay +#CTF_SERVICE_DEWASTE=10.10.10.1:10010 +#CTF_SERVICE_CANTINA=10.10.10.1:10020,10.10.10.1:10021,10.10.10.1:10024 +#CTF_SERVICE_HPS=10.10.10.1:10030 +#CTF_SERVICE_AQUAEDUCTUS=10.10.10.1:10041 +#CTF_SERVICE_BLINKYGRAM=10.10.10.1:10050 +#CTF_SERVICE_WINDS_OF_THE_PAST=10.10.10.1:10060 +#CTF_SERVICE_TECHBAY=10.10.10.1:10070 + +# Examples from FAUST CTF 2022 (2022-07-09) +#CTF_START_DATE=2023-01-30T13:00+02:00 +#CTF_TICK_LENGTH=180 +#CTF_HOME_NET=[fd66:666::0/32] +#CTF_SERVICES=compiler60,docsnotebook,digital_seconds_ago,fittyfit,fluxmail,notes_from_the_future,admincrashboard +#CTF_SERVICE_COMPILER60=[fd66:666:534::2]:6061,[fd66:666:534::2]:6062 +#CTF_SERVICE_DOCSNOTEBOOK=[fd66:666:534::2]:9000 +#CTF_SERVICE_DIGITAL_SECONDS_AGO=[fd66:666:534::2]:13731 +#CTF_SERVICE_FITTYFIT=[fd66:666:534::2]:5001 +#CTF_SERVICE_FLUXMAIL=[fd66:666:534::2]:4242 +#CTF_SERVICE_NOTES_FROM_THE_FUTURE=[fd66:666:534::2]:1338 +#CTF_SERVICE_ADMINCRASHBOARD=[fd66:666:534::2]:5000,[fd66:666:534::2]:5002 diff --git a/grafana/Dockerfile b/grafana/Dockerfile new file mode 100644 index 0000000..daa8733 --- /dev/null +++ b/grafana/Dockerfile @@ -0,0 +1,10 @@ +FROM grafana/grafana-oss:10.1.2 +ENV GF_ANALYTICS_CHECK_FOR_PLUGIN_UPDATES=false +ENV GF_ANALYTICS_CHECK_FOR_UPDATES=false +ENV GF_ANALYTICS_REPORTING_ENABLED=false +ENV GF_AUTH_ANONYMOUS_ENABLED=true +ENV GF_AUTH_ANONYMOUS_HIDE_VERSION=true +ENV GF_INSTALL_PLUGINS=frser-sqlite-datasource +ENV GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/var/lib/grafana/dashboards/home.json +COPY ./provisioning /etc/grafana/provisioning +COPY ./dashboards /var/lib/grafana/dashboards diff --git a/grafana/dashboards/home.json b/grafana/dashboards/home.json new file mode 100644 index 0000000..478d167 --- /dev/null +++ b/grafana/dashboards/home.json @@ -0,0 +1,401 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "purple", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 40, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "queryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nGROUP BY tick", + "queryType": "table", + "rawQueryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nGROUP BY tick", + "refId": "A", + "timeColumns": [] + } + ], + "title": "Flows per tick", + "type": "trend" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "red", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 40, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "queryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_out_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY tick", + "queryType": "table", + "rawQueryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_out_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY tick", + "refId": "A", + "timeColumns": [] + } + ], + "title": "FLAG OUT per tick", + "transformations": [], + "type": "trend" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 40, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "queryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_in_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG IN\")\nGROUP BY tick", + "queryType": "table", + "rawQueryText": "SELECT ($__unixEpochGroupSeconds(ts_start / 1000, 180) - (SELECT ts_start / 1000 FROM ctf_config))/180 as tick, COUNT(*) as flag_in_count FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG IN\")\nGROUP BY tick", + "refId": "A", + "timeColumns": [] + } + ], + "title": "FLAG IN per tick", + "transformations": [], + "type": "trend" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "fillOpacity": 100, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 0 + }, + "displayName": "${__field.labels.dest_ipport}", + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 5, + "options": { + "colWidth": 1, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "rowHeight": 0.8, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2D2EEF3E092AF52B" + }, + "queryText": "SELECT $__unixEpochGroupSeconds(ts_start / 1000, 180) as time, COUNT(*) as flag_out_count, dest_ipport FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY time, dest_ipport", + "queryType": "time series", + "rawQueryText": "SELECT $__unixEpochGroupSeconds(ts_start / 1000, 180) as time, COUNT(*) as flag_out_count, dest_ipport FROM flow\nWHERE ts_start >= $__from and ts_start < $__to\nAND id IN (SELECT flow_id FROM alert WHERE tag = \"FLAG OUT\")\nGROUP BY time, dest_ipport", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "FLAG OUT per service per tick", + "type": "status-history" + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "1970-01-01T12:00:00.000Z", + "to": "2100-01-01T12:00:00.000Z" + }, + "timepicker": {}, + "timezone": "", + "title": "Home", + "uid": "WdNSDiRIz", + "version": 9, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/provisioning/dashboards/default.yml b/grafana/provisioning/dashboards/default.yml new file mode 100644 index 0000000..733035e --- /dev/null +++ b/grafana/provisioning/dashboards/default.yml @@ -0,0 +1,8 @@ +apiVersion: 1 + +providers: + - name: dashboards + type: file + allowUiUpdates: true + options: + path: /var/lib/grafana/dashboards diff --git a/grafana/provisioning/datasources/sql.yml b/grafana/provisioning/datasources/sql.yml new file mode 100644 index 0000000..9afcf9b --- /dev/null +++ b/grafana/provisioning/datasources/sql.yml @@ -0,0 +1,8 @@ +apiVersion: 1 + +datasources: + - name: SQLite + type: frser-sqlite-datasource + jsonData: + path: /webapp/database/database.db + isDefault: true diff --git a/suricata/.dockerignore b/suricata/.dockerignore new file mode 100644 index 0000000..53752db --- /dev/null +++ b/suricata/.dockerignore @@ -0,0 +1 @@ +output diff --git a/suricata/Dockerfile b/suricata/Dockerfile new file mode 100644 index 0000000..5b99ba4 --- /dev/null +++ b/suricata/Dockerfile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-3.0-only + +FROM alpine:20230901 +RUN apk add --no-cache suricata +COPY . /suricata +CMD ["/suricata/entrypoint.sh"] diff --git a/suricata/custom.rules b/suricata/custom.rules new file mode 100644 index 0000000..5f535bc --- /dev/null +++ b/suricata/custom.rules @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# Suricata rules for Attack-Defense CTF games +# Please remember to increment `sid` when adding new rules as its value must be unique. + +# Flags +# As PCRE is slow, please use a content filter before. +# Please test your regex at https://regex101.com/ using "PCRE2" mode. +alert tcp $HOME_NET any -> any any (msg: "A ECSC flag was sent to client"; flow:to_client; content: "ECSC_"; pcre: "/ECSC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG OUT, color danger; sid: 1;) +alert tcp $HOME_NET any -> any any (msg: "A ECSC flag was sent to client (base64)"; flow:to_client; content: "RUNTQ1"; pcre: "/RUNTQ1[A-Za-z0-9\/+]{44}==/"; metadata: tag FLAG OUT B64, color danger; sid: 2;) +alert tcp $HOME_NET any -> any any (msg: "A ENOWARS flag was sent to client"; flow:to_client; content: "ENO"; pcre: "/ENO[A-Za-z0-9+\/=]{48}/"; metadata: tag FLAG OUT, color danger; sid: 3;) +alert tcp $HOME_NET any -> any any (msg: "A ENOWARS flag was sent to client (base64)"; flow:to_client; content: "RU5P"; pcre: "/RU5P[A-Za-z0-9\/+]{64}/"; metadata: tag FLAG OUT B64, color danger; sid: 4;) +alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client"; flow:to_client; content: "FAUST_"; metadata: tag FLAG OUT, color danger; sid: 5;) +alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client (base64)"; flow:to_client; content: "RkFVU1Rf"; metadata: tag FLAG OUT B64, color danger; sid: 6;) +alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client (base64, 1-byte shifted)"; flow:to_client; content: "ZBVVNUX"; metadata: tag FLAG OUT B64, color danger; sid: 7;) +alert tcp $HOME_NET any -> any any (msg: "A FAUSTCTF flag was sent to client (base64, 2-byte shifted)"; flow:to_client; content: "GQVVTVF"; metadata: tag FLAG OUT B64, color danger; sid: 8;) +alert tcp $HOME_NET any -> any any (msg: "A ICC flag was sent to client"; flow:to_client; content: "ICC_"; pcre: "/ICC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG OUT, color danger; sid: 9;) +alert tcp $HOME_NET any -> any any (msg: "A ICC flag was sent to client (base64)"; flow:to_client; content: "SUNDX"; pcre: "/SUNDX[A-Za-z0-9\/+]{43}/"; metadata: tag FLAG OUT, color danger; sid: 10;) +alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "ECSC_"; pcre: "/ECSC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG IN, color success; sid: 51;) +alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "ENO"; pcre: "/ENO[A-Za-z0-9+\/=]{48}/"; metadata: tag FLAG IN, color success; sid: 52;) +alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "FAUST_"; pcre: "/FAUST_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG IN, color success; sid: 53;) +alert tcp any any -> $HOME_NET any (msg: "A flag was placed in our services (probably by the CTF admins)"; flow:to_server; content: "ICC_"; pcre: "/ICC_[A-Za-z0-9\/+]{32}/"; metadata: tag FLAG IN, color success; sid: 54;) + +# Tag file formats using libmagic +alert tcp any any -> any any (msg: "tag"; filemagic: "assembler source"; metadata: tag ASM, color primary; sid: 101;) +alert tcp any any -> any any (msg: "tag"; filemagic: "GIF image"; metadata: tag GIF, color primary; sid: 102;) +alert tcp any any -> any any (msg: "tag"; filemagic: "HTML document"; metadata: tag HTML, color primary; sid: 113;) +alert tcp any any -> any any (msg: "tag"; filemagic: "JPEG image"; metadata: tag JPG, color primary; sid: 104;) +alert tcp any any -> any any (msg: "tag"; filemagic: "JSON "; metadata: tag JSON, color primary; sid: 105;) +alert tcp any any -> any any (msg: "tag"; filemagic: "PDF document"; metadata: tag PDF, color primary; sid: 106;) +alert tcp any any -> any any (msg: "tag"; filemagic: "PNG image"; metadata: tag PNG, color primary; sid: 107;) +alert tcp any any -> any any (msg: "tag"; filemagic: "SVG Scalable Vector Graphics image"; metadata: tag SVG, color primary; sid: 108;) +alert tcp any any -> any any (msg: "tag"; filemagic: "VGM Video Game Music"; metadata: tag VGM, color primary; sid: 109;) +alert tcp any any -> any any (msg: "tag"; filemagic: "Web Open Font"; metadata: tag WOFF, color primary; sid: 110;) +alert tcp any any -> any any (msg: "tag"; filemagic: "Zip archive"; metadata: tag ZIP, color primary; sid: 111;) + +# Tag HTTP methods and status +alert tcp any any -> any any (msg: "tag"; http.method; content: "GET"; metadata: tag GET, color info; sid: 201;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "POST"; metadata: tag POST, color info; sid: 202;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "PUT"; metadata: tag PUT, color info; sid: 203;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "HEAD"; metadata: tag HEAD, color info; sid: 204;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "DELETE"; metadata: tag DELETE, color info; sid: 205;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "TRACE"; metadata: tag TRACE, color info; sid: 206;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "OPTIONS"; metadata: tag OPTIONS, color info; sid: 207;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "CONNECT"; metadata: tag CONNECT, color info; sid: 208;) +alert tcp any any -> any any (msg: "tag"; http.method; content: "PATCH"; metadata: tag PATCH, color info; sid: 209;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "200"; metadata: tag 200, color info; sid: 210;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "201"; metadata: tag 201, color info; sid: 211;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "202"; metadata: tag 202, color info; sid: 212;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "204"; metadata: tag 204, color info; sid: 213;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "301"; metadata: tag 301, color info; sid: 214;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "302"; metadata: tag 302, color info; sid: 215;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "304"; metadata: tag 304, color info; sid: 216;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "400"; metadata: tag 400, color info; sid: 217;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "401"; metadata: tag 401, color info; sid: 218;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "403"; metadata: tag 403, color info; sid: 219;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "404"; metadata: tag 404, color info; sid: 220;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "405"; metadata: tag 405, color info; sid: 221;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "408"; metadata: tag 408, color info; sid: 222;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "500"; metadata: tag 500, color warning; sid: 223;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "501"; metadata: tag 501, color warning; sid: 224;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "502"; metadata: tag 502, color warning; sid: 225;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "503"; metadata: tag 503, color warning; sid: 226;) +alert tcp any any -> any any (msg: "tag"; http.stat_code; content: "504"; metadata: tag 504, color warning; sid: 227;) + +# Side-channel indicators +alert tcp any any -> $HOME_NET any (msg: "Found python-requests User-Agent"; flow:to_server; content: "python-requests/"; http_user_agent; metadata: tag UA PYREQ, color warning; sid: 301;) +alert tcp any any -> $HOME_NET any (msg: "Found python-httpx User-Agent"; flow:to_server; content: "python-httpx/"; http_user_agent; metadata: tag UA HTTPX, color warning; sid: 302;) +alert tcp any any -> $HOME_NET any (msg: "Found HeadlessChrome User-Agent"; flow:to_server; content: "HeadlessChrome/"; http_user_agent; metadata: tag UA HLCHROME, color warning; sid: 303;) +alert tcp any any -> $HOME_NET any (msg: "Found Firefox User-Agent"; flow:to_server; content: "Firefox/"; http_user_agent; metadata: tag UA FIREFOX, color warning; sid: 304;) diff --git a/suricata/entrypoint.sh b/suricata/entrypoint.sh new file mode 100755 index 0000000..f682ff7 --- /dev/null +++ b/suricata/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only + +echo "Cleaning previous Suricata output" +rm -f suricata/output/eve.json suricata/output/tcpstore.log suricata/output/udpstore.log + +echo "Starting Suricata with HOME_NET=$CTF_HOME_NET" +suricata -c suricata/suricata.yaml -S suricata/custom.rules -r input_pcaps -l suricata/output --set vars.address-groups.HOME_NET="$CTF_HOME_NET" --runmode=single --no-random --pcap-file-continuous diff --git a/suricata/lua/sha2.lua b/suricata/lua/sha2.lua new file mode 100644 index 0000000..201f52e --- /dev/null +++ b/suricata/lua/sha2.lua @@ -0,0 +1,5675 @@ +-------------------------------------------------------------------------------------------------------------------------- +-- sha2.lua +-------------------------------------------------------------------------------------------------------------------------- +-- VERSION: 12 (2022-02-23) +-- AUTHOR: Egor Skriptunoff +-- LICENSE: MIT (the same license as Lua itself) +-- URL: https://github.com/Egor-Skriptunoff/pure_lua_SHA +-- +-- DESCRIPTION: +-- This module contains functions to calculate SHA digest: +-- MD5, SHA-1, +-- SHA-224, SHA-256, SHA-512/224, SHA-512/256, SHA-384, SHA-512, +-- SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256, +-- HMAC, +-- BLAKE2b, BLAKE2s, BLAKE2bp, BLAKE2sp, BLAKE2Xb, BLAKE2Xs, +-- BLAKE3, BLAKE3_KDF +-- Written in pure Lua. +-- Compatible with: +-- Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4, Fengari, LuaJIT 2.0/2.1 (any CPU endianness). +-- Main feature of this module: it was heavily optimized for speed. +-- For every Lua version the module contains particular implementation branch to get benefits from version-specific features. +-- - branch for Lua 5.1 (emulating bitwise operators using look-up table) +-- - branch for Lua 5.2 (using bit32/bit library), suitable for both Lua 5.2 with native "bit32" and Lua 5.1 with external library "bit" +-- - branch for Lua 5.3/5.4 (using native 64-bit bitwise operators) +-- - branch for Lua 5.3/5.4 (using native 32-bit bitwise operators) for Lua built with LUA_INT_TYPE=LUA_INT_INT +-- - branch for LuaJIT without FFI library (useful in a sandboxed environment) +-- - branch for LuaJIT x86 without FFI library (LuaJIT x86 has oddity because of lack of CPU registers) +-- - branch for LuaJIT 2.0 with FFI library (bit.* functions work only with Lua numbers) +-- - branch for LuaJIT 2.1 with FFI library (bit.* functions can work with "int64_t" arguments) +-- +-- +-- USAGE: +-- Input data should be provided as a binary string: either as a whole string or as a sequence of substrings (chunk-by-chunk loading, total length < 9*10^15 bytes). +-- Result (SHA digest) is returned in hexadecimal representation as a string of lowercase hex digits. +-- Simplest usage example: +-- local sha = require("sha2") +-- local your_hash = sha.sha256("your string") +-- See file "sha2_test.lua" for more examples. +-- +-- +-- CHANGELOG: +-- version date description +-- ------- ---------- ----------- +-- 12 2022-02-23 Now works in Luau (but NOT optimized for speed) +-- 11 2022-01-09 BLAKE3 added +-- 10 2022-01-02 BLAKE2 functions added +-- 9 2020-05-10 Now works in OpenWrt's Lua (dialect of Lua 5.1 with "double" + "invisible int32") +-- 8 2019-09-03 SHA-3 functions added +-- 7 2019-03-17 Added functions to convert to/from base64 +-- 6 2018-11-12 HMAC added +-- 5 2018-11-10 SHA-1 added +-- 4 2018-11-03 MD5 added +-- 3 2018-11-02 Bug fixed: incorrect hashing of long (2 GByte) data streams on Lua 5.3/5.4 built with "int32" integers +-- 2 2018-10-07 Decreased module loading time in Lua 5.1 implementation branch (thanks to Peter Melnichenko for giving a hint) +-- 1 2018-10-06 First release (only SHA-2 functions) +----------------------------------------------------------------------------- + + +local print_debug_messages = false -- set to true to view some messages about your system's abilities and implementation branch chosen for your system + +local unpack, table_concat, byte, char, string_rep, sub, gsub, gmatch, string_format, floor, ceil, math_min, math_max, tonumber, type, math_huge = + table.unpack or unpack, table.concat, string.byte, string.char, string.rep, string.sub, string.gsub, string.gmatch, string.format, math.floor, math.ceil, math.min, math.max, tonumber, type, math.huge + + +-------------------------------------------------------------------------------- +-- EXAMINING YOUR SYSTEM +-------------------------------------------------------------------------------- + +local function get_precision(one) + -- "one" must be either float 1.0 or integer 1 + -- returns bits_precision, is_integer + -- This function works correctly with all floating point datatypes (including non-IEEE-754) + local k, n, m, prev_n = 0, one, one + while true do + k, prev_n, n, m = k + 1, n, n + n + 1, m + m + k % 2 + if k > 256 or n - (n - 1) ~= 1 or m - (m - 1) ~= 1 or n == m then + return k, false -- floating point datatype + elseif n == prev_n then + return k, true -- integer datatype + end + end +end + +-- Make sure Lua has "double" numbers +local x = 2/3 +local Lua_has_double = x * 5 > 3 and x * 4 < 3 and get_precision(1.0) >= 53 +assert(Lua_has_double, "at least 53-bit floating point numbers are required") + +-- Q: +-- SHA2 was designed for FPU-less machines. +-- So, why floating point numbers are needed for this module? +-- A: +-- 53-bit "double" numbers are useful to calculate "magic numbers" used in SHA. +-- I prefer to write 50 LOC "magic numbers calculator" instead of storing more than 200 constants explicitly in this source file. + +local int_prec, Lua_has_integers = get_precision(1) +local Lua_has_int64 = Lua_has_integers and int_prec == 64 +local Lua_has_int32 = Lua_has_integers and int_prec == 32 +assert(Lua_has_int64 or Lua_has_int32 or not Lua_has_integers, "Lua integers must be either 32-bit or 64-bit") + +-- Q: +-- Does it mean that almost all non-standard configurations are not supported? +-- A: +-- Yes. Sorry, too many problems to support all possible Lua numbers configurations. +-- Lua 5.1/5.2 with "int32" will not work. +-- Lua 5.1/5.2 with "int64" will not work. +-- Lua 5.1/5.2 with "int128" will not work. +-- Lua 5.1/5.2 with "float" will not work. +-- Lua 5.1/5.2 with "double" is OK. (default config for Lua 5.1, Lua 5.2, LuaJIT) +-- Lua 5.3/5.4 with "int32" + "float" will not work. +-- Lua 5.3/5.4 with "int64" + "float" will not work. +-- Lua 5.3/5.4 with "int128" + "float" will not work. +-- Lua 5.3/5.4 with "int32" + "double" is OK. (config used by Fengari) +-- Lua 5.3/5.4 with "int64" + "double" is OK. (default config for Lua 5.3, Lua 5.4) +-- Lua 5.3/5.4 with "int128" + "double" will not work. +-- Using floating point numbers better than "double" instead of "double" is OK (non-IEEE-754 floating point implementation are allowed). +-- Using "int128" instead of "int64" is not OK: "int128" would require different branch of implementation for optimized SHA512. + +-- Check for LuaJIT and 32-bit bitwise libraries +local is_LuaJIT = ({false, [1] = true})[1] and _VERSION ~= "Luau" and (type(jit) ~= "table" or jit.version_num >= 20000) -- LuaJIT 1.x.x and Luau are treated as vanilla Lua 5.1/5.2 +local is_LuaJIT_21 -- LuaJIT 2.1+ +local LuaJIT_arch +local ffi -- LuaJIT FFI library (as a table) +local b -- 32-bit bitwise library (as a table) +local library_name + +if is_LuaJIT then + -- Assuming "bit" library is always available on LuaJIT + b = require"bit" + library_name = "bit" + -- "ffi" is intentionally disabled on some systems for safety reason + local LuaJIT_has_FFI, result = pcall(require, "ffi") + if LuaJIT_has_FFI then + ffi = result + end + is_LuaJIT_21 = not not loadstring"b=0b0" + LuaJIT_arch = type(jit) == "table" and jit.arch or ffi and ffi.arch or nil +else + -- For vanilla Lua, "bit"/"bit32" libraries are searched in global namespace only. No attempt is made to load a library if it's not loaded yet. + for _, libname in ipairs(_VERSION == "Lua 5.2" and {"bit32", "bit"} or {"bit", "bit32"}) do + if type(_G[libname]) == "table" and _G[libname].bxor then + b = _G[libname] + library_name = libname + break + end + end +end + +-------------------------------------------------------------------------------- +-- You can disable here some of your system's abilities (for testing purposes) +-------------------------------------------------------------------------------- +-- is_LuaJIT = nil +-- is_LuaJIT_21 = nil +-- ffi = nil +-- Lua_has_int32 = nil +-- Lua_has_int64 = nil +-- b, library_name = nil +-------------------------------------------------------------------------------- + +if print_debug_messages then + -- Printing list of abilities of your system + print("Abilities:") + print(" Lua version: "..(is_LuaJIT and "LuaJIT "..(is_LuaJIT_21 and "2.1 " or "2.0 ")..(LuaJIT_arch or "")..(ffi and " with FFI" or " without FFI") or _VERSION)) + print(" Integer bitwise operators: "..(Lua_has_int64 and "int64" or Lua_has_int32 and "int32" or "no")) + print(" 32-bit bitwise library: "..(library_name or "not found")) +end + +-- Selecting the most suitable implementation for given set of abilities +local method, branch +if is_LuaJIT and ffi then + method = "Using 'ffi' library of LuaJIT" + branch = "FFI" +elseif is_LuaJIT then + method = "Using special code for sandboxed LuaJIT (no FFI)" + branch = "LJ" +elseif Lua_has_int64 then + method = "Using native int64 bitwise operators" + branch = "INT64" +elseif Lua_has_int32 then + method = "Using native int32 bitwise operators" + branch = "INT32" +elseif library_name then -- when bitwise library is available (Lua 5.2 with native library "bit32" or Lua 5.1 with external library "bit") + method = "Using '"..library_name.."' library" + branch = "LIB32" +else + method = "Emulating bitwise operators using look-up table" + branch = "EMUL" +end + +if print_debug_messages then + -- Printing the implementation selected to be used on your system + print("Implementation selected:") + print(" "..method) +end + + +-------------------------------------------------------------------------------- +-- BASIC 32-BIT BITWISE FUNCTIONS +-------------------------------------------------------------------------------- + +local AND, OR, XOR, SHL, SHR, ROL, ROR, NOT, NORM, HEX, XOR_BYTE +-- Only low 32 bits of function arguments matter, high bits are ignored +-- The result of all functions (except HEX) is an integer inside "correct range": +-- for "bit" library: (-2^31)..(2^31-1) +-- for "bit32" library: 0..(2^32-1) + +if branch == "FFI" or branch == "LJ" or branch == "LIB32" then + + -- Your system has 32-bit bitwise library (either "bit" or "bit32") + + AND = b.band -- 2 arguments + OR = b.bor -- 2 arguments + XOR = b.bxor -- 2..5 arguments + SHL = b.lshift -- second argument is integer 0..31 + SHR = b.rshift -- second argument is integer 0..31 + ROL = b.rol or b.lrotate -- second argument is integer 0..31 + ROR = b.ror or b.rrotate -- second argument is integer 0..31 + NOT = b.bnot -- only for LuaJIT + NORM = b.tobit -- only for LuaJIT + HEX = b.tohex -- returns string of 8 lowercase hexadecimal digits + assert(AND and OR and XOR and SHL and SHR and ROL and ROR and NOT, "Library '"..library_name.."' is incomplete") + XOR_BYTE = XOR -- XOR of two bytes (0..255) + +elseif branch == "EMUL" then + + -- Emulating 32-bit bitwise operations using 53-bit floating point arithmetic + + function SHL(x, n) + return (x * 2^n) % 2^32 + end + + function SHR(x, n) + x = x % 2^32 / 2^n + return x - x % 1 + end + + function ROL(x, n) + x = x % 2^32 * 2^n + local r = x % 2^32 + return r + (x - r) / 2^32 + end + + function ROR(x, n) + x = x % 2^32 / 2^n + local r = x % 1 + return r * 2^32 + (x - r) + end + + local AND_of_two_bytes = {[0] = 0} -- look-up table (256*256 entries) + local idx = 0 + for y = 0, 127 * 256, 256 do + for x = y, y + 127 do + x = AND_of_two_bytes[x] * 2 + AND_of_two_bytes[idx] = x + AND_of_two_bytes[idx + 1] = x + AND_of_two_bytes[idx + 256] = x + AND_of_two_bytes[idx + 257] = x + 1 + idx = idx + 2 + end + idx = idx + 256 + end + + local function and_or_xor(x, y, operation) + -- operation: nil = AND, 1 = OR, 2 = XOR + local x0 = x % 2^32 + local y0 = y % 2^32 + local rx = x0 % 256 + local ry = y0 % 256 + local res = AND_of_two_bytes[rx + ry * 256] + x = x0 - rx + y = (y0 - ry) / 256 + rx = x % 65536 + ry = y % 256 + res = res + AND_of_two_bytes[rx + ry] * 256 + x = (x - rx) / 256 + y = (y - ry) / 256 + rx = x % 65536 + y % 256 + res = res + AND_of_two_bytes[rx] * 65536 + res = res + AND_of_two_bytes[(x + y - rx) / 256] * 16777216 + if operation then + res = x0 + y0 - operation * res + end + return res + end + + function AND(x, y) + return and_or_xor(x, y) + end + + function OR(x, y) + return and_or_xor(x, y, 1) + end + + function XOR(x, y, z, t, u) -- 2..5 arguments + if z then + if t then + if u then + t = and_or_xor(t, u, 2) + end + z = and_or_xor(z, t, 2) + end + y = and_or_xor(y, z, 2) + end + return and_or_xor(x, y, 2) + end + + function XOR_BYTE(x, y) + return x + y - 2 * AND_of_two_bytes[x + y * 256] + end + +end + +HEX = HEX + or + pcall(string_format, "%x", 2^31) and + function (x) -- returns string of 8 lowercase hexadecimal digits + return string_format("%08x", x % 4294967296) + end + or + function (x) -- for OpenWrt's dialect of Lua + return string_format("%08x", (x + 2^31) % 2^32 - 2^31) + end + +local function XORA5(x, y) + return XOR(x, y or 0xA5A5A5A5) % 4294967296 +end + +local function create_array_of_lanes() + return {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +end + + +-------------------------------------------------------------------------------- +-- CREATING OPTIMIZED INNER LOOP +-------------------------------------------------------------------------------- + +-- Inner loop functions +local sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 + +-- Arrays of SHA-2 "magic numbers" (in "INT64" and "FFI" branches "*_lo" arrays contain 64-bit values) +local sha2_K_lo, sha2_K_hi, sha2_H_lo, sha2_H_hi, sha3_RC_lo, sha3_RC_hi = {}, {}, {}, {}, {}, {} +local sha2_H_ext256 = {[224] = {}, [256] = sha2_H_hi} +local sha2_H_ext512_lo, sha2_H_ext512_hi = {[384] = {}, [512] = sha2_H_lo}, {[384] = {}, [512] = sha2_H_hi} +local md5_K, md5_sha1_H = {}, {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0} +local md5_next_shift = {0, 0, 0, 0, 0, 0, 0, 0, 28, 25, 26, 27, 0, 0, 10, 9, 11, 12, 0, 15, 16, 17, 18, 0, 20, 22, 23, 21} +local HEX64, lanes_index_base -- defined only for branches that internally use 64-bit integers: "INT64" and "FFI" +local common_W = {} -- temporary table shared between all calculations (to avoid creating new temporary table every time) +local common_W_blake2b, common_W_blake2s, v_for_blake2s_feed_64 = common_W, common_W, {} +local K_lo_modulo, hi_factor, hi_factor_keccak = 4294967296, 0, 0 +local sigma = { + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + { 15, 11, 5, 9, 10, 16, 14, 7, 2, 13, 1, 3, 12, 8, 6, 4 }, + { 12, 9, 13, 1, 6, 3, 16, 14, 11, 15, 4, 7, 8, 2, 10, 5 }, + { 8, 10, 4, 2, 14, 13, 12, 15, 3, 7, 6, 11, 5, 1, 16, 9 }, + { 10, 1, 6, 8, 3, 5, 11, 16, 15, 2, 12, 13, 7, 9, 4, 14 }, + { 3, 13, 7, 11, 1, 12, 9, 4, 5, 14, 8, 6, 16, 15, 2, 10 }, + { 13, 6, 2, 16, 15, 14, 5, 11, 1, 8, 7, 4, 10, 3, 9, 12 }, + { 14, 12, 8, 15, 13, 2, 4, 10, 6, 1, 16, 5, 9, 7, 3, 11 }, + { 7, 16, 15, 10, 12, 4, 1, 9, 13, 3, 14, 8, 2, 5, 11, 6 }, + { 11, 3, 9, 5, 8, 7, 2, 6, 16, 12, 10, 15, 4, 13, 14, 1 }, +}; sigma[11], sigma[12] = sigma[1], sigma[2] +local perm_blake3 = { + 1, 3, 4, 11, 13, 10, 12, 6, + 1, 3, 4, 11, 13, 10, + 2, 7, 5, 8, 14, 15, 16, 9, + 2, 7, 5, 8, 14, 15, +} + +local function build_keccak_format(elem) + local keccak_format = {} + for _, size in ipairs{1, 9, 13, 17, 18, 21} do + keccak_format[size] = "<"..string_rep(elem, size) + end + return keccak_format +end + + +if branch == "FFI" then + + local common_W_FFI_int32 = ffi.new("int32_t[?]", 80) -- 64 is enough for SHA256, but 80 is needed for SHA-1 + common_W_blake2s = common_W_FFI_int32 + v_for_blake2s_feed_64 = ffi.new("int32_t[?]", 16) + perm_blake3 = ffi.new("uint8_t[?]", #perm_blake3 + 1, 0, unpack(perm_blake3)) + for j = 1, 10 do + sigma[j] = ffi.new("uint8_t[?]", #sigma[j] + 1, 0, unpack(sigma[j])) + end; sigma[11], sigma[12] = sigma[1], sigma[2] + + + -- SHA256 implementation for "LuaJIT with FFI" branch + + function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W_FFI_int32, sha2_K_hi + for pos = offs, offs + size - 1, 64 do + for j = 0, 15 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 16, 63 do + local a, b = W[j-15], W[j-2] + W[j] = NORM( XOR(ROR(a, 7), ROL(a, 14), SHR(a, 3)) + XOR(ROL(b, 15), ROL(b, 13), SHR(b, 10)) + W[j-7] + W[j-16] ) + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 0, 63, 8 do -- Thanks to Peter Cawley for this workaround (unroll the loop to avoid "PHI shuffling too complex" due to PHIs overlap) + local z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j] + K[j+1] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+1] + K[j+2] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+2] + K[j+3] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+3] + K[j+4] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+4] + K[j+5] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+5] + K[j+6] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+6] + K[j+7] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(g, AND(e, XOR(f, g))) + XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + (W[j+7] + K[j+8] + h) ) + h, g, f, e = g, f, e, NORM( d + z ) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + H[5], H[6], H[7], H[8] = NORM(e + H[5]), NORM(f + H[6]), NORM(g + H[7]), NORM(h + H[8]) + end + end + + + local common_W_FFI_int64 = ffi.new("int64_t[?]", 80) + common_W_blake2b = common_W_FFI_int64 + local int64 = ffi.typeof"int64_t" + local int32 = ffi.typeof"int32_t" + local uint32 = ffi.typeof"uint32_t" + hi_factor = int64(2^32) + + if is_LuaJIT_21 then -- LuaJIT 2.1 supports bitwise 64-bit operations + + local AND64, OR64, XOR64, NOT64, SHL64, SHR64, ROL64, ROR64 -- introducing synonyms for better code readability + = AND, OR, XOR, NOT, SHL, SHR, ROL, ROR + HEX64 = HEX + + + -- BLAKE2b implementation for "LuaJIT 2.1 + FFI" branch + + do + local v = ffi.new("int64_t[?]", 16) + local W = common_W_blake2b + + local function G(a, b, c, d, k1, k2) + local va, vb, vc, vd = v[a], v[b], v[c], v[d] + va = W[k1] + (va + vb) + vd = ROR64(XOR64(vd, va), 32) + vc = vc + vd + vb = ROR64(XOR64(vb, vc), 24) + va = W[k2] + (va + vb) + vd = ROR64(XOR64(vd, va), 16) + vc = vc + vd + vb = ROL64(XOR64(vb, vc), 1) + v[a], v[b], v[c], v[d] = va, vb, vc, vd + end + + function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 16 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) + W[j] = XOR64(OR(SHL(h, 24), SHL(g, 16), SHL(f, 8), e) * int64(2^32), uint32(int32(OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)))) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB], v[0xD], v[0xE], v[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + v[0xC] = XOR64(sha2_H_lo[5], bytes_compressed) -- t0 = low_8_bytes(bytes_compressed) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + v[0xE] = NOT64(v[0xE]) + end + if is_last_node then -- flag f1 + v[0xF] = NOT64(v[0xF]) + end + for j = 1, 12 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1 = XOR64(h1, v[0x0], v[0x8]) + h2 = XOR64(h2, v[0x1], v[0x9]) + h3 = XOR64(h3, v[0x2], v[0xA]) + h4 = XOR64(h4, v[0x3], v[0xB]) + h5 = XOR64(h5, v[0x4], v[0xC]) + h6 = XOR64(h6, v[0x5], v[0xD]) + h7 = XOR64(h7, v[0x6], v[0xE]) + h8 = XOR64(h8, v[0x7], v[0xF]) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + end + + + -- SHA-3 implementation for "LuaJIT 2.1 + FFI" branch + + local arr64_t = ffi.typeof"int64_t[?]" + -- lanes array is indexed from 0 + lanes_index_base = 0 + hi_factor_keccak = int64(2^32) + + function create_array_of_lanes() + return arr64_t(30) -- 25 + 5 for temporary usage + end + + function keccak_feed(lanes, _, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC = sha3_RC_lo + local qwords_qty = SHR(block_size_in_bytes, 3) + for pos = offs, offs + size - 1, block_size_in_bytes do + for j = 0, qwords_qty - 1 do + pos = pos + 8 + local h, g, f, e, d, c, b, a = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness + lanes[j] = XOR64(lanes[j], OR64(OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32), uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h))))) + end + for round_idx = 1, 24 do + for j = 0, 4 do + lanes[25 + j] = XOR64(lanes[j], lanes[j+5], lanes[j+10], lanes[j+15], lanes[j+20]) + end + local D = XOR64(lanes[25], ROL64(lanes[27], 1)) + lanes[1], lanes[6], lanes[11], lanes[16] = ROL64(XOR64(D, lanes[6]), 44), ROL64(XOR64(D, lanes[16]), 45), ROL64(XOR64(D, lanes[1]), 1), ROL64(XOR64(D, lanes[11]), 10) + lanes[21] = ROL64(XOR64(D, lanes[21]), 2) + D = XOR64(lanes[26], ROL64(lanes[28], 1)) + lanes[2], lanes[7], lanes[12], lanes[22] = ROL64(XOR64(D, lanes[12]), 43), ROL64(XOR64(D, lanes[22]), 61), ROL64(XOR64(D, lanes[7]), 6), ROL64(XOR64(D, lanes[2]), 62) + lanes[17] = ROL64(XOR64(D, lanes[17]), 15) + D = XOR64(lanes[27], ROL64(lanes[29], 1)) + lanes[3], lanes[8], lanes[18], lanes[23] = ROL64(XOR64(D, lanes[18]), 21), ROL64(XOR64(D, lanes[3]), 28), ROL64(XOR64(D, lanes[23]), 56), ROL64(XOR64(D, lanes[8]), 55) + lanes[13] = ROL64(XOR64(D, lanes[13]), 25) + D = XOR64(lanes[28], ROL64(lanes[25], 1)) + lanes[4], lanes[14], lanes[19], lanes[24] = ROL64(XOR64(D, lanes[24]), 14), ROL64(XOR64(D, lanes[19]), 8), ROL64(XOR64(D, lanes[4]), 27), ROL64(XOR64(D, lanes[14]), 39) + lanes[9] = ROL64(XOR64(D, lanes[9]), 20) + D = XOR64(lanes[29], ROL64(lanes[26], 1)) + lanes[5], lanes[10], lanes[15], lanes[20] = ROL64(XOR64(D, lanes[10]), 3), ROL64(XOR64(D, lanes[20]), 18), ROL64(XOR64(D, lanes[5]), 36), ROL64(XOR64(D, lanes[15]), 41) + lanes[0] = XOR64(D, lanes[0]) + lanes[0], lanes[1], lanes[2], lanes[3], lanes[4] = XOR64(lanes[0], AND64(NOT64(lanes[1]), lanes[2]), RC[round_idx]), XOR64(lanes[1], AND64(NOT64(lanes[2]), lanes[3])), XOR64(lanes[2], AND64(NOT64(lanes[3]), lanes[4])), XOR64(lanes[3], AND64(NOT64(lanes[4]), lanes[0])), XOR64(lanes[4], AND64(NOT64(lanes[0]), lanes[1])) + lanes[5], lanes[6], lanes[7], lanes[8], lanes[9] = XOR64(lanes[8], AND64(NOT64(lanes[9]), lanes[5])), XOR64(lanes[9], AND64(NOT64(lanes[5]), lanes[6])), XOR64(lanes[5], AND64(NOT64(lanes[6]), lanes[7])), XOR64(lanes[6], AND64(NOT64(lanes[7]), lanes[8])), XOR64(lanes[7], AND64(NOT64(lanes[8]), lanes[9])) + lanes[10], lanes[11], lanes[12], lanes[13], lanes[14] = XOR64(lanes[11], AND64(NOT64(lanes[12]), lanes[13])), XOR64(lanes[12], AND64(NOT64(lanes[13]), lanes[14])), XOR64(lanes[13], AND64(NOT64(lanes[14]), lanes[10])), XOR64(lanes[14], AND64(NOT64(lanes[10]), lanes[11])), XOR64(lanes[10], AND64(NOT64(lanes[11]), lanes[12])) + lanes[15], lanes[16], lanes[17], lanes[18], lanes[19] = XOR64(lanes[19], AND64(NOT64(lanes[15]), lanes[16])), XOR64(lanes[15], AND64(NOT64(lanes[16]), lanes[17])), XOR64(lanes[16], AND64(NOT64(lanes[17]), lanes[18])), XOR64(lanes[17], AND64(NOT64(lanes[18]), lanes[19])), XOR64(lanes[18], AND64(NOT64(lanes[19]), lanes[15])) + lanes[20], lanes[21], lanes[22], lanes[23], lanes[24] = XOR64(lanes[22], AND64(NOT64(lanes[23]), lanes[24])), XOR64(lanes[23], AND64(NOT64(lanes[24]), lanes[20])), XOR64(lanes[24], AND64(NOT64(lanes[20]), lanes[21])), XOR64(lanes[20], AND64(NOT64(lanes[21]), lanes[22])), XOR64(lanes[21], AND64(NOT64(lanes[22]), lanes[23])) + end + end + end + + + local A5_long = 0xA5A5A5A5 * int64(2^32 + 1) -- It's impossible to use constant 0xA5A5A5A5A5A5A5A5LL because it will raise syntax error on other Lua versions + + function XORA5(long, long2) + return XOR64(long, long2 or A5_long) + end + + + -- SHA512 implementation for "LuaJIT 2.1 + FFI" branch + + function sha512_feed_128(H, _, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + local W, K = common_W_FFI_int64, sha2_K_lo + for pos = offs, offs + size - 1, 128 do + for j = 0, 15 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness + W[j] = OR64(OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32), uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h)))) + end + for j = 16, 79 do + local a, b = W[j-15], W[j-2] + W[j] = XOR64(ROR64(a, 1), ROR64(a, 8), SHR64(a, 7)) + XOR64(ROR64(b, 19), ROL64(b, 3), SHR64(b, 6)) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 0, 79, 8 do + local z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+1] + W[j] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+2] + W[j+1] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+3] + W[j+2] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+4] + W[j+3] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+5] + W[j+4] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+6] + W[j+5] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+7] + W[j+6] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + z = XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + XOR64(g, AND64(e, XOR64(f, g))) + h + K[j+8] + W[j+7] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + z + end + H[1] = a + H[1] + H[2] = b + H[2] + H[3] = c + H[3] + H[4] = d + H[4] + H[5] = e + H[5] + H[6] = f + H[6] + H[7] = g + H[7] + H[8] = h + H[8] + end + end + + else -- LuaJIT 2.0 doesn't support 64-bit bitwise operations + + local U = ffi.new("union{int64_t i64; struct{int32_t "..(ffi.abi("le") and "lo, hi" or "hi, lo")..";} i32;}[3]") + -- this array of unions is used for fast splitting int64 into int32_high and int32_low + + -- "xorrific" 64-bit functions :-) + -- int64 input is splitted into two int32 parts, some bitwise 32-bit operations are performed, finally the result is converted to int64 + -- these functions are needed because bit.* functions in LuaJIT 2.0 don't work with int64_t + + local function XORROR64_1(a) + -- return XOR64(ROR64(a, 1), ROR64(a, 8), SHR64(a, 7)) + U[0].i64 = a + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local t_lo = XOR(SHR(a_lo, 1), SHL(a_hi, 31), SHR(a_lo, 8), SHL(a_hi, 24), SHR(a_lo, 7), SHL(a_hi, 25)) + local t_hi = XOR(SHR(a_hi, 1), SHL(a_lo, 31), SHR(a_hi, 8), SHL(a_lo, 24), SHR(a_hi, 7)) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_2(b) + -- return XOR64(ROR64(b, 19), ROL64(b, 3), SHR64(b, 6)) + U[0].i64 = b + local b_lo, b_hi = U[0].i32.lo, U[0].i32.hi + local u_lo = XOR(SHR(b_lo, 19), SHL(b_hi, 13), SHL(b_lo, 3), SHR(b_hi, 29), SHR(b_lo, 6), SHL(b_hi, 26)) + local u_hi = XOR(SHR(b_hi, 19), SHL(b_lo, 13), SHL(b_hi, 3), SHR(b_lo, 29), SHR(b_hi, 6)) + return u_hi * int64(2^32) + uint32(int32(u_lo)) + end + + local function XORROR64_3(e) + -- return XOR64(ROR64(e, 14), ROR64(e, 18), ROL64(e, 23)) + U[0].i64 = e + local e_lo, e_hi = U[0].i32.lo, U[0].i32.hi + local u_lo = XOR(SHR(e_lo, 14), SHL(e_hi, 18), SHR(e_lo, 18), SHL(e_hi, 14), SHL(e_lo, 23), SHR(e_hi, 9)) + local u_hi = XOR(SHR(e_hi, 14), SHL(e_lo, 18), SHR(e_hi, 18), SHL(e_lo, 14), SHL(e_hi, 23), SHR(e_lo, 9)) + return u_hi * int64(2^32) + uint32(int32(u_lo)) + end + + local function XORROR64_6(a) + -- return XOR64(ROR64(a, 28), ROL64(a, 25), ROL64(a, 30)) + U[0].i64 = a + local b_lo, b_hi = U[0].i32.lo, U[0].i32.hi + local u_lo = XOR(SHR(b_lo, 28), SHL(b_hi, 4), SHL(b_lo, 30), SHR(b_hi, 2), SHL(b_lo, 25), SHR(b_hi, 7)) + local u_hi = XOR(SHR(b_hi, 28), SHL(b_lo, 4), SHL(b_hi, 30), SHR(b_lo, 2), SHL(b_hi, 25), SHR(b_lo, 7)) + return u_hi * int64(2^32) + uint32(int32(u_lo)) + end + + local function XORROR64_4(e, f, g) + -- return XOR64(g, AND64(e, XOR64(f, g))) + U[0].i64 = f + U[1].i64 = g + U[2].i64 = e + local f_lo, f_hi = U[0].i32.lo, U[0].i32.hi + local g_lo, g_hi = U[1].i32.lo, U[1].i32.hi + local e_lo, e_hi = U[2].i32.lo, U[2].i32.hi + local result_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo))) + local result_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi))) + return result_hi * int64(2^32) + uint32(int32(result_lo)) + end + + local function XORROR64_5(a, b, c) + -- return XOR64(AND64(XOR64(a, b), c), AND64(a, b)) + U[0].i64 = a + U[1].i64 = b + U[2].i64 = c + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = U[2].i32.lo, U[2].i32.hi + local result_lo = XOR(AND(XOR(a_lo, b_lo), c_lo), AND(a_lo, b_lo)) + local result_hi = XOR(AND(XOR(a_hi, b_hi), c_hi), AND(a_hi, b_hi)) + return result_hi * int64(2^32) + uint32(int32(result_lo)) + end + + local function XORROR64_7(a, b, m) + -- return ROR64(XOR64(a, b), m), m = 1..31 + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + local t_lo = XOR(SHR(c_lo, m), SHL(c_hi, -m)) + local t_hi = XOR(SHR(c_hi, m), SHL(c_lo, -m)) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_8(a, b) + -- return ROL64(XOR64(a, b), 1) + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + local t_lo = XOR(SHL(c_lo, 1), SHR(c_hi, 31)) + local t_hi = XOR(SHL(c_hi, 1), SHR(c_lo, 31)) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_9(a, b) + -- return ROR64(XOR64(a, b), 32) + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local t_hi, t_lo = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XOR64(a, b) + -- return XOR64(a, b) + U[0].i64 = a + U[1].i64 = b + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local t_lo, t_hi = XOR(a_lo, b_lo), XOR(a_hi, b_hi) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + local function XORROR64_11(a, b, c) + -- return XOR64(a, b, c) + U[0].i64 = a + U[1].i64 = b + U[2].i64 = c + local a_lo, a_hi = U[0].i32.lo, U[0].i32.hi + local b_lo, b_hi = U[1].i32.lo, U[1].i32.hi + local c_lo, c_hi = U[2].i32.lo, U[2].i32.hi + local t_lo, t_hi = XOR(a_lo, b_lo, c_lo), XOR(a_hi, b_hi, c_hi) + return t_hi * int64(2^32) + uint32(int32(t_lo)) + end + + function XORA5(long, long2) + -- return XOR64(long, long2 or 0xA5A5A5A5A5A5A5A5) + U[0].i64 = long + local lo32, hi32 = U[0].i32.lo, U[0].i32.hi + local long2_lo, long2_hi = 0xA5A5A5A5, 0xA5A5A5A5 + if long2 then + U[1].i64 = long2 + long2_lo, long2_hi = U[1].i32.lo, U[1].i32.hi + end + lo32 = XOR(lo32, long2_lo) + hi32 = XOR(hi32, long2_hi) + return hi32 * int64(2^32) + uint32(int32(lo32)) + end + + function HEX64(long) + U[0].i64 = long + return HEX(U[0].i32.hi)..HEX(U[0].i32.lo) + end + + + -- SHA512 implementation for "LuaJIT 2.0 + FFI" branch + + function sha512_feed_128(H, _, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + local W, K = common_W_FFI_int64, sha2_K_lo + for pos = offs, offs + size - 1, 128 do + for j = 0, 15 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) * int64(2^32) + uint32(int32(OR(SHL(e, 24), SHL(f, 16), SHL(g, 8), h))) + end + for j = 16, 79 do + W[j] = XORROR64_1(W[j-15]) + XORROR64_2(W[j-2]) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 0, 79, 8 do + local z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+1] + W[j] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+2] + W[j+1] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+3] + W[j+2] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+4] + W[j+3] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+5] + W[j+4] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+6] + W[j+5] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+7] + W[j+6] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + z = XORROR64_3(e) + XORROR64_4(e, f, g) + h + K[j+8] + W[j+7] + h, g, f, e = g, f, e, z + d + d, c, b, a = c, b, a, XORROR64_5(a, b, c) + XORROR64_6(a) + z + end + H[1] = a + H[1] + H[2] = b + H[2] + H[3] = c + H[3] + H[4] = d + H[4] + H[5] = e + H[5] + H[6] = f + H[6] + H[7] = g + H[7] + H[8] = h + H[8] + end + end + + + -- BLAKE2b implementation for "LuaJIT 2.0 + FFI" branch + + do + local v = ffi.new("int64_t[?]", 16) + local W = common_W_blake2b + + local function G(a, b, c, d, k1, k2) + local va, vb, vc, vd = v[a], v[b], v[c], v[d] + va = W[k1] + (va + vb) + vd = XORROR64_9(vd, va) + vc = vc + vd + vb = XORROR64_7(vb, vc, 24) + va = W[k2] + (va + vb) + vd = XORROR64_7(vd, va, 16) + vc = vc + vd + vb = XORROR64_8(vb, vc) + v[a], v[b], v[c], v[d] = va, vb, vc, vd + end + + function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 16 do + pos = pos + 8 + local a, b, c, d, e, f, g, h = byte(str, pos - 7, pos) + W[j] = XOR64(OR(SHL(h, 24), SHL(g, 16), SHL(f, 8), e) * int64(2^32), uint32(int32(OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)))) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB], v[0xD], v[0xE], v[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + v[0xC] = XOR64(sha2_H_lo[5], bytes_compressed) -- t0 = low_8_bytes(bytes_compressed) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + v[0xE] = -1 - v[0xE] + end + if is_last_node then -- flag f1 + v[0xF] = -1 - v[0xF] + end + for j = 1, 12 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1 = XORROR64_11(h1, v[0x0], v[0x8]) + h2 = XORROR64_11(h2, v[0x1], v[0x9]) + h3 = XORROR64_11(h3, v[0x2], v[0xA]) + h4 = XORROR64_11(h4, v[0x3], v[0xB]) + h5 = XORROR64_11(h5, v[0x4], v[0xC]) + h6 = XORROR64_11(h6, v[0x5], v[0xD]) + h7 = XORROR64_11(h7, v[0x6], v[0xE]) + h8 = XORROR64_11(h8, v[0x7], v[0xF]) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + end + + end + + + -- MD5 implementation for "LuaJIT with FFI" branch + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W_FFI_int32, md5_K + for pos = offs, offs + size - 1, 64 do + for j = 0, 15 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + local a, b, c, d = H[1], H[2], H[3], H[4] + for j = 0, 15, 4 do + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+1] + W[j ] + a), 7) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+2] + W[j+1] + a), 12) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+3] + W[j+2] + a), 17) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+4] + W[j+3] + a), 22) + b) + end + for j = 16, 31, 4 do + local g = 5*j + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+1] + W[AND(g + 1, 15)] + a), 5) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+2] + W[AND(g + 6, 15)] + a), 9) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+3] + W[AND(g - 5, 15)] + a), 14) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+4] + W[AND(g , 15)] + a), 20) + b) + end + for j = 32, 47, 4 do + local g = 3*j + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+1] + W[AND(g + 5, 15)] + a), 4) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+2] + W[AND(g + 8, 15)] + a), 11) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+3] + W[AND(g - 5, 15)] + a), 16) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+4] + W[AND(g - 2, 15)] + a), 23) + b) + end + for j = 48, 63, 4 do + local g = 7*j + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+1] + W[AND(g , 15)] + a), 6) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+2] + W[AND(g + 7, 15)] + a), 10) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+3] + W[AND(g - 2, 15)] + a), 15) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+4] + W[AND(g + 5, 15)] + a), 21) + b) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + end + end + + + -- SHA-1 implementation for "LuaJIT with FFI" branch + + function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W_FFI_int32 + for pos = offs, offs + size - 1, 64 do + for j = 0, 15 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) -- slow, but doesn't depend on endianness + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 16, 79 do + W[j] = ROL(XOR(W[j-3], W[j-8], W[j-14], W[j-16]), 1) + end + local a, b, c, d, e = H[1], H[2], H[3], H[4], H[5] + for j = 0, 19, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j] + 0x5A827999 + e)) -- constant = floor(2^30 * sqrt(2)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+1] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+2] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+3] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+4] + 0x5A827999 + e)) + end + for j = 20, 39, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0x6ED9EBA1 + e)) -- 2^30 * sqrt(3) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0x6ED9EBA1 + e)) + end + for j = 40, 59, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j] + 0x8F1BBCDC + e)) -- 2^30 * sqrt(5) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+1] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+2] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+3] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+4] + 0x8F1BBCDC + e)) + end + for j = 60, 79, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0xCA62C1D6 + e)) -- 2^30 * sqrt(10) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0xCA62C1D6 + e)) + end + H[1], H[2], H[3], H[4], H[5] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]), NORM(e + H[5]) + end + end + +end + + +if branch == "FFI" and not is_LuaJIT_21 or branch == "LJ" then + + if branch == "FFI" then + local arr32_t = ffi.typeof"int32_t[?]" + + function create_array_of_lanes() + return arr32_t(31) -- 25 + 5 + 1 (due to 1-based indexing) + end + + end + + + -- SHA-3 implementation for "LuaJIT 2.0 + FFI" and "LuaJIT without FFI" branches + + function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi + local qwords_qty = SHR(block_size_in_bytes, 3) + for pos = offs, offs + size - 1, block_size_in_bytes do + for j = 1, qwords_qty do + local a, b, c, d = byte(str, pos + 1, pos + 4) + lanes_lo[j] = XOR(lanes_lo[j], OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)) + pos = pos + 8 + a, b, c, d = byte(str, pos - 3, pos) + lanes_hi[j] = XOR(lanes_hi[j], OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a)) + end + for round_idx = 1, 24 do + for j = 1, 5 do + lanes_lo[25 + j] = XOR(lanes_lo[j], lanes_lo[j + 5], lanes_lo[j + 10], lanes_lo[j + 15], lanes_lo[j + 20]) + end + for j = 1, 5 do + lanes_hi[25 + j] = XOR(lanes_hi[j], lanes_hi[j + 5], lanes_hi[j + 10], lanes_hi[j + 15], lanes_hi[j + 20]) + end + local D_lo = XOR(lanes_lo[26], SHL(lanes_lo[28], 1), SHR(lanes_hi[28], 31)) + local D_hi = XOR(lanes_hi[26], SHL(lanes_hi[28], 1), SHR(lanes_lo[28], 31)) + lanes_lo[2], lanes_hi[2], lanes_lo[7], lanes_hi[7], lanes_lo[12], lanes_hi[12], lanes_lo[17], lanes_hi[17] = XOR(SHR(XOR(D_lo, lanes_lo[7]), 20), SHL(XOR(D_hi, lanes_hi[7]), 12)), XOR(SHR(XOR(D_hi, lanes_hi[7]), 20), SHL(XOR(D_lo, lanes_lo[7]), 12)), XOR(SHR(XOR(D_lo, lanes_lo[17]), 19), SHL(XOR(D_hi, lanes_hi[17]), 13)), XOR(SHR(XOR(D_hi, lanes_hi[17]), 19), SHL(XOR(D_lo, lanes_lo[17]), 13)), XOR(SHL(XOR(D_lo, lanes_lo[2]), 1), SHR(XOR(D_hi, lanes_hi[2]), 31)), XOR(SHL(XOR(D_hi, lanes_hi[2]), 1), SHR(XOR(D_lo, lanes_lo[2]), 31)), XOR(SHL(XOR(D_lo, lanes_lo[12]), 10), SHR(XOR(D_hi, lanes_hi[12]), 22)), XOR(SHL(XOR(D_hi, lanes_hi[12]), 10), SHR(XOR(D_lo, lanes_lo[12]), 22)) + local L, H = XOR(D_lo, lanes_lo[22]), XOR(D_hi, lanes_hi[22]) + lanes_lo[22], lanes_hi[22] = XOR(SHL(L, 2), SHR(H, 30)), XOR(SHL(H, 2), SHR(L, 30)) + D_lo = XOR(lanes_lo[27], SHL(lanes_lo[29], 1), SHR(lanes_hi[29], 31)) + D_hi = XOR(lanes_hi[27], SHL(lanes_hi[29], 1), SHR(lanes_lo[29], 31)) + lanes_lo[3], lanes_hi[3], lanes_lo[8], lanes_hi[8], lanes_lo[13], lanes_hi[13], lanes_lo[23], lanes_hi[23] = XOR(SHR(XOR(D_lo, lanes_lo[13]), 21), SHL(XOR(D_hi, lanes_hi[13]), 11)), XOR(SHR(XOR(D_hi, lanes_hi[13]), 21), SHL(XOR(D_lo, lanes_lo[13]), 11)), XOR(SHR(XOR(D_lo, lanes_lo[23]), 3), SHL(XOR(D_hi, lanes_hi[23]), 29)), XOR(SHR(XOR(D_hi, lanes_hi[23]), 3), SHL(XOR(D_lo, lanes_lo[23]), 29)), XOR(SHL(XOR(D_lo, lanes_lo[8]), 6), SHR(XOR(D_hi, lanes_hi[8]), 26)), XOR(SHL(XOR(D_hi, lanes_hi[8]), 6), SHR(XOR(D_lo, lanes_lo[8]), 26)), XOR(SHR(XOR(D_lo, lanes_lo[3]), 2), SHL(XOR(D_hi, lanes_hi[3]), 30)), XOR(SHR(XOR(D_hi, lanes_hi[3]), 2), SHL(XOR(D_lo, lanes_lo[3]), 30)) + L, H = XOR(D_lo, lanes_lo[18]), XOR(D_hi, lanes_hi[18]) + lanes_lo[18], lanes_hi[18] = XOR(SHL(L, 15), SHR(H, 17)), XOR(SHL(H, 15), SHR(L, 17)) + D_lo = XOR(lanes_lo[28], SHL(lanes_lo[30], 1), SHR(lanes_hi[30], 31)) + D_hi = XOR(lanes_hi[28], SHL(lanes_hi[30], 1), SHR(lanes_lo[30], 31)) + lanes_lo[4], lanes_hi[4], lanes_lo[9], lanes_hi[9], lanes_lo[19], lanes_hi[19], lanes_lo[24], lanes_hi[24] = XOR(SHL(XOR(D_lo, lanes_lo[19]), 21), SHR(XOR(D_hi, lanes_hi[19]), 11)), XOR(SHL(XOR(D_hi, lanes_hi[19]), 21), SHR(XOR(D_lo, lanes_lo[19]), 11)), XOR(SHL(XOR(D_lo, lanes_lo[4]), 28), SHR(XOR(D_hi, lanes_hi[4]), 4)), XOR(SHL(XOR(D_hi, lanes_hi[4]), 28), SHR(XOR(D_lo, lanes_lo[4]), 4)), XOR(SHR(XOR(D_lo, lanes_lo[24]), 8), SHL(XOR(D_hi, lanes_hi[24]), 24)), XOR(SHR(XOR(D_hi, lanes_hi[24]), 8), SHL(XOR(D_lo, lanes_lo[24]), 24)), XOR(SHR(XOR(D_lo, lanes_lo[9]), 9), SHL(XOR(D_hi, lanes_hi[9]), 23)), XOR(SHR(XOR(D_hi, lanes_hi[9]), 9), SHL(XOR(D_lo, lanes_lo[9]), 23)) + L, H = XOR(D_lo, lanes_lo[14]), XOR(D_hi, lanes_hi[14]) + lanes_lo[14], lanes_hi[14] = XOR(SHL(L, 25), SHR(H, 7)), XOR(SHL(H, 25), SHR(L, 7)) + D_lo = XOR(lanes_lo[29], SHL(lanes_lo[26], 1), SHR(lanes_hi[26], 31)) + D_hi = XOR(lanes_hi[29], SHL(lanes_hi[26], 1), SHR(lanes_lo[26], 31)) + lanes_lo[5], lanes_hi[5], lanes_lo[15], lanes_hi[15], lanes_lo[20], lanes_hi[20], lanes_lo[25], lanes_hi[25] = XOR(SHL(XOR(D_lo, lanes_lo[25]), 14), SHR(XOR(D_hi, lanes_hi[25]), 18)), XOR(SHL(XOR(D_hi, lanes_hi[25]), 14), SHR(XOR(D_lo, lanes_lo[25]), 18)), XOR(SHL(XOR(D_lo, lanes_lo[20]), 8), SHR(XOR(D_hi, lanes_hi[20]), 24)), XOR(SHL(XOR(D_hi, lanes_hi[20]), 8), SHR(XOR(D_lo, lanes_lo[20]), 24)), XOR(SHL(XOR(D_lo, lanes_lo[5]), 27), SHR(XOR(D_hi, lanes_hi[5]), 5)), XOR(SHL(XOR(D_hi, lanes_hi[5]), 27), SHR(XOR(D_lo, lanes_lo[5]), 5)), XOR(SHR(XOR(D_lo, lanes_lo[15]), 25), SHL(XOR(D_hi, lanes_hi[15]), 7)), XOR(SHR(XOR(D_hi, lanes_hi[15]), 25), SHL(XOR(D_lo, lanes_lo[15]), 7)) + L, H = XOR(D_lo, lanes_lo[10]), XOR(D_hi, lanes_hi[10]) + lanes_lo[10], lanes_hi[10] = XOR(SHL(L, 20), SHR(H, 12)), XOR(SHL(H, 20), SHR(L, 12)) + D_lo = XOR(lanes_lo[30], SHL(lanes_lo[27], 1), SHR(lanes_hi[27], 31)) + D_hi = XOR(lanes_hi[30], SHL(lanes_hi[27], 1), SHR(lanes_lo[27], 31)) + lanes_lo[6], lanes_hi[6], lanes_lo[11], lanes_hi[11], lanes_lo[16], lanes_hi[16], lanes_lo[21], lanes_hi[21] = XOR(SHL(XOR(D_lo, lanes_lo[11]), 3), SHR(XOR(D_hi, lanes_hi[11]), 29)), XOR(SHL(XOR(D_hi, lanes_hi[11]), 3), SHR(XOR(D_lo, lanes_lo[11]), 29)), XOR(SHL(XOR(D_lo, lanes_lo[21]), 18), SHR(XOR(D_hi, lanes_hi[21]), 14)), XOR(SHL(XOR(D_hi, lanes_hi[21]), 18), SHR(XOR(D_lo, lanes_lo[21]), 14)), XOR(SHR(XOR(D_lo, lanes_lo[6]), 28), SHL(XOR(D_hi, lanes_hi[6]), 4)), XOR(SHR(XOR(D_hi, lanes_hi[6]), 28), SHL(XOR(D_lo, lanes_lo[6]), 4)), XOR(SHR(XOR(D_lo, lanes_lo[16]), 23), SHL(XOR(D_hi, lanes_hi[16]), 9)), XOR(SHR(XOR(D_hi, lanes_hi[16]), 23), SHL(XOR(D_lo, lanes_lo[16]), 9)) + lanes_lo[1], lanes_hi[1] = XOR(D_lo, lanes_lo[1]), XOR(D_hi, lanes_hi[1]) + lanes_lo[1], lanes_lo[2], lanes_lo[3], lanes_lo[4], lanes_lo[5] = XOR(lanes_lo[1], AND(NOT(lanes_lo[2]), lanes_lo[3]), RC_lo[round_idx]), XOR(lanes_lo[2], AND(NOT(lanes_lo[3]), lanes_lo[4])), XOR(lanes_lo[3], AND(NOT(lanes_lo[4]), lanes_lo[5])), XOR(lanes_lo[4], AND(NOT(lanes_lo[5]), lanes_lo[1])), XOR(lanes_lo[5], AND(NOT(lanes_lo[1]), lanes_lo[2])) + lanes_lo[6], lanes_lo[7], lanes_lo[8], lanes_lo[9], lanes_lo[10] = XOR(lanes_lo[9], AND(NOT(lanes_lo[10]), lanes_lo[6])), XOR(lanes_lo[10], AND(NOT(lanes_lo[6]), lanes_lo[7])), XOR(lanes_lo[6], AND(NOT(lanes_lo[7]), lanes_lo[8])), XOR(lanes_lo[7], AND(NOT(lanes_lo[8]), lanes_lo[9])), XOR(lanes_lo[8], AND(NOT(lanes_lo[9]), lanes_lo[10])) + lanes_lo[11], lanes_lo[12], lanes_lo[13], lanes_lo[14], lanes_lo[15] = XOR(lanes_lo[12], AND(NOT(lanes_lo[13]), lanes_lo[14])), XOR(lanes_lo[13], AND(NOT(lanes_lo[14]), lanes_lo[15])), XOR(lanes_lo[14], AND(NOT(lanes_lo[15]), lanes_lo[11])), XOR(lanes_lo[15], AND(NOT(lanes_lo[11]), lanes_lo[12])), XOR(lanes_lo[11], AND(NOT(lanes_lo[12]), lanes_lo[13])) + lanes_lo[16], lanes_lo[17], lanes_lo[18], lanes_lo[19], lanes_lo[20] = XOR(lanes_lo[20], AND(NOT(lanes_lo[16]), lanes_lo[17])), XOR(lanes_lo[16], AND(NOT(lanes_lo[17]), lanes_lo[18])), XOR(lanes_lo[17], AND(NOT(lanes_lo[18]), lanes_lo[19])), XOR(lanes_lo[18], AND(NOT(lanes_lo[19]), lanes_lo[20])), XOR(lanes_lo[19], AND(NOT(lanes_lo[20]), lanes_lo[16])) + lanes_lo[21], lanes_lo[22], lanes_lo[23], lanes_lo[24], lanes_lo[25] = XOR(lanes_lo[23], AND(NOT(lanes_lo[24]), lanes_lo[25])), XOR(lanes_lo[24], AND(NOT(lanes_lo[25]), lanes_lo[21])), XOR(lanes_lo[25], AND(NOT(lanes_lo[21]), lanes_lo[22])), XOR(lanes_lo[21], AND(NOT(lanes_lo[22]), lanes_lo[23])), XOR(lanes_lo[22], AND(NOT(lanes_lo[23]), lanes_lo[24])) + lanes_hi[1], lanes_hi[2], lanes_hi[3], lanes_hi[4], lanes_hi[5] = XOR(lanes_hi[1], AND(NOT(lanes_hi[2]), lanes_hi[3]), RC_hi[round_idx]), XOR(lanes_hi[2], AND(NOT(lanes_hi[3]), lanes_hi[4])), XOR(lanes_hi[3], AND(NOT(lanes_hi[4]), lanes_hi[5])), XOR(lanes_hi[4], AND(NOT(lanes_hi[5]), lanes_hi[1])), XOR(lanes_hi[5], AND(NOT(lanes_hi[1]), lanes_hi[2])) + lanes_hi[6], lanes_hi[7], lanes_hi[8], lanes_hi[9], lanes_hi[10] = XOR(lanes_hi[9], AND(NOT(lanes_hi[10]), lanes_hi[6])), XOR(lanes_hi[10], AND(NOT(lanes_hi[6]), lanes_hi[7])), XOR(lanes_hi[6], AND(NOT(lanes_hi[7]), lanes_hi[8])), XOR(lanes_hi[7], AND(NOT(lanes_hi[8]), lanes_hi[9])), XOR(lanes_hi[8], AND(NOT(lanes_hi[9]), lanes_hi[10])) + lanes_hi[11], lanes_hi[12], lanes_hi[13], lanes_hi[14], lanes_hi[15] = XOR(lanes_hi[12], AND(NOT(lanes_hi[13]), lanes_hi[14])), XOR(lanes_hi[13], AND(NOT(lanes_hi[14]), lanes_hi[15])), XOR(lanes_hi[14], AND(NOT(lanes_hi[15]), lanes_hi[11])), XOR(lanes_hi[15], AND(NOT(lanes_hi[11]), lanes_hi[12])), XOR(lanes_hi[11], AND(NOT(lanes_hi[12]), lanes_hi[13])) + lanes_hi[16], lanes_hi[17], lanes_hi[18], lanes_hi[19], lanes_hi[20] = XOR(lanes_hi[20], AND(NOT(lanes_hi[16]), lanes_hi[17])), XOR(lanes_hi[16], AND(NOT(lanes_hi[17]), lanes_hi[18])), XOR(lanes_hi[17], AND(NOT(lanes_hi[18]), lanes_hi[19])), XOR(lanes_hi[18], AND(NOT(lanes_hi[19]), lanes_hi[20])), XOR(lanes_hi[19], AND(NOT(lanes_hi[20]), lanes_hi[16])) + lanes_hi[21], lanes_hi[22], lanes_hi[23], lanes_hi[24], lanes_hi[25] = XOR(lanes_hi[23], AND(NOT(lanes_hi[24]), lanes_hi[25])), XOR(lanes_hi[24], AND(NOT(lanes_hi[25]), lanes_hi[21])), XOR(lanes_hi[25], AND(NOT(lanes_hi[21]), lanes_hi[22])), XOR(lanes_hi[21], AND(NOT(lanes_hi[22]), lanes_hi[23])), XOR(lanes_hi[22], AND(NOT(lanes_hi[23]), lanes_hi[24])) + end + end + end + +end + + +if branch == "LJ" then + + + -- SHA256 implementation for "LuaJIT without FFI" branch + + function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 17, 64 do + local a, b = W[j-15], W[j-2] + W[j] = NORM( NORM( XOR(ROR(a, 7), ROL(a, 14), SHR(a, 3)) + XOR(ROL(b, 15), ROL(b, 13), SHR(b, 10)) ) + NORM( W[j-7] + W[j-16] ) ) + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for j = 1, 64, 8 do -- Thanks to Peter Cawley for this workaround (unroll the loop to avoid "PHI shuffling too complex" due to PHIs overlap) + local z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j] + W[j] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+1] + W[j+1] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+2] + W[j+2] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+3] + W[j+3] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+4] + W[j+4] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+5] + W[j+5] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+6] + W[j+6] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + z = NORM( XOR(ROR(e, 6), ROR(e, 11), ROL(e, 7)) + XOR(g, AND(e, XOR(f, g))) + (K[j+7] + W[j+7] + h) ) + h, g, f, e = g, f, e, NORM(d + z) + d, c, b, a = c, b, a, NORM( XOR(AND(a, XOR(b, c)), AND(b, c)) + XOR(ROR(a, 2), ROR(a, 13), ROL(a, 10)) + z ) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + H[5], H[6], H[7], H[8] = NORM(e + H[5]), NORM(f + H[6]), NORM(g + H[7]), NORM(h + H[8]) + end + end + + local function ADD64_4(a_lo, a_hi, b_lo, b_hi, c_lo, c_hi, d_lo, d_hi) + local sum_lo = a_lo % 2^32 + b_lo % 2^32 + c_lo % 2^32 + d_lo % 2^32 + local sum_hi = a_hi + b_hi + c_hi + d_hi + local result_lo = NORM( sum_lo ) + local result_hi = NORM( sum_hi + floor(sum_lo / 2^32) ) + return result_lo, result_hi + end + + if LuaJIT_arch == "x86" then -- Special trick is required to avoid "PHI shuffling too complex" on x86 platform + + + -- SHA512 implementation for "LuaJIT x86 without FFI" branch + + function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi + for pos = offs, offs + size - 1, 128 do + for j = 1, 16*2 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for jj = 17*2, 80*2, 2 do + local a_lo, a_hi = W[jj-30], W[jj-31] + local t_lo = XOR(OR(SHR(a_lo, 1), SHL(a_hi, 31)), OR(SHR(a_lo, 8), SHL(a_hi, 24)), OR(SHR(a_lo, 7), SHL(a_hi, 25))) + local t_hi = XOR(OR(SHR(a_hi, 1), SHL(a_lo, 31)), OR(SHR(a_hi, 8), SHL(a_lo, 24)), SHR(a_hi, 7)) + local b_lo, b_hi = W[jj-4], W[jj-5] + local u_lo = XOR(OR(SHR(b_lo, 19), SHL(b_hi, 13)), OR(SHL(b_lo, 3), SHR(b_hi, 29)), OR(SHR(b_lo, 6), SHL(b_hi, 26))) + local u_hi = XOR(OR(SHR(b_hi, 19), SHL(b_lo, 13)), OR(SHL(b_hi, 3), SHR(b_lo, 29)), SHR(b_hi, 6)) + W[jj], W[jj-1] = ADD64_4(t_lo, t_hi, u_lo, u_hi, W[jj-14], W[jj-15], W[jj-32], W[jj-33]) + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + local zero = 0 + for j = 1, 80 do + local t_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo))) + local t_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi))) + local u_lo = XOR(OR(SHR(e_lo, 14), SHL(e_hi, 18)), OR(SHR(e_lo, 18), SHL(e_hi, 14)), OR(SHL(e_lo, 23), SHR(e_hi, 9))) + local u_hi = XOR(OR(SHR(e_hi, 14), SHL(e_lo, 18)), OR(SHR(e_hi, 18), SHL(e_lo, 14)), OR(SHL(e_hi, 23), SHR(e_lo, 9))) + local sum_lo = u_lo % 2^32 + t_lo % 2^32 + h_lo % 2^32 + K_lo[j] + W[2*j] % 2^32 + local z_lo, z_hi = NORM( sum_lo ), NORM( u_hi + t_hi + h_hi + K_hi[j] + W[2*j-1] + floor(sum_lo / 2^32) ) + zero = zero + zero -- this thick is needed to avoid "PHI shuffling too complex" due to PHIs overlap + h_lo, h_hi, g_lo, g_hi, f_lo, f_hi = OR(zero, g_lo), OR(zero, g_hi), OR(zero, f_lo), OR(zero, f_hi), OR(zero, e_lo), OR(zero, e_hi) + local sum_lo = z_lo % 2^32 + d_lo % 2^32 + e_lo, e_hi = NORM( sum_lo ), NORM( z_hi + d_hi + floor(sum_lo / 2^32) ) + d_lo, d_hi, c_lo, c_hi, b_lo, b_hi = OR(zero, c_lo), OR(zero, c_hi), OR(zero, b_lo), OR(zero, b_hi), OR(zero, a_lo), OR(zero, a_hi) + u_lo = XOR(OR(SHR(b_lo, 28), SHL(b_hi, 4)), OR(SHL(b_lo, 30), SHR(b_hi, 2)), OR(SHL(b_lo, 25), SHR(b_hi, 7))) + u_hi = XOR(OR(SHR(b_hi, 28), SHL(b_lo, 4)), OR(SHL(b_hi, 30), SHR(b_lo, 2)), OR(SHL(b_hi, 25), SHR(b_lo, 7))) + t_lo = OR(AND(d_lo, c_lo), AND(b_lo, XOR(d_lo, c_lo))) + t_hi = OR(AND(d_hi, c_hi), AND(b_hi, XOR(d_hi, c_hi))) + local sum_lo = z_lo % 2^32 + t_lo % 2^32 + u_lo % 2^32 + a_lo, a_hi = NORM( sum_lo ), NORM( z_hi + t_hi + u_hi + floor(sum_lo / 2^32) ) + end + H_lo[1], H_hi[1] = ADD64_4(H_lo[1], H_hi[1], a_lo, a_hi, 0, 0, 0, 0) + H_lo[2], H_hi[2] = ADD64_4(H_lo[2], H_hi[2], b_lo, b_hi, 0, 0, 0, 0) + H_lo[3], H_hi[3] = ADD64_4(H_lo[3], H_hi[3], c_lo, c_hi, 0, 0, 0, 0) + H_lo[4], H_hi[4] = ADD64_4(H_lo[4], H_hi[4], d_lo, d_hi, 0, 0, 0, 0) + H_lo[5], H_hi[5] = ADD64_4(H_lo[5], H_hi[5], e_lo, e_hi, 0, 0, 0, 0) + H_lo[6], H_hi[6] = ADD64_4(H_lo[6], H_hi[6], f_lo, f_hi, 0, 0, 0, 0) + H_lo[7], H_hi[7] = ADD64_4(H_lo[7], H_hi[7], g_lo, g_hi, 0, 0, 0, 0) + H_lo[8], H_hi[8] = ADD64_4(H_lo[8], H_hi[8], h_lo, h_hi, 0, 0, 0, 0) + end + end + + else -- all platforms except x86 + + + -- SHA512 implementation for "LuaJIT non-x86 without FFI" branch + + function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi + for pos = offs, offs + size - 1, 128 do + for j = 1, 16*2 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for jj = 17*2, 80*2, 2 do + local a_lo, a_hi = W[jj-30], W[jj-31] + local t_lo = XOR(OR(SHR(a_lo, 1), SHL(a_hi, 31)), OR(SHR(a_lo, 8), SHL(a_hi, 24)), OR(SHR(a_lo, 7), SHL(a_hi, 25))) + local t_hi = XOR(OR(SHR(a_hi, 1), SHL(a_lo, 31)), OR(SHR(a_hi, 8), SHL(a_lo, 24)), SHR(a_hi, 7)) + local b_lo, b_hi = W[jj-4], W[jj-5] + local u_lo = XOR(OR(SHR(b_lo, 19), SHL(b_hi, 13)), OR(SHL(b_lo, 3), SHR(b_hi, 29)), OR(SHR(b_lo, 6), SHL(b_hi, 26))) + local u_hi = XOR(OR(SHR(b_hi, 19), SHL(b_lo, 13)), OR(SHL(b_hi, 3), SHR(b_lo, 29)), SHR(b_hi, 6)) + W[jj], W[jj-1] = ADD64_4(t_lo, t_hi, u_lo, u_hi, W[jj-14], W[jj-15], W[jj-32], W[jj-33]) + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for j = 1, 80 do + local t_lo = XOR(g_lo, AND(e_lo, XOR(f_lo, g_lo))) + local t_hi = XOR(g_hi, AND(e_hi, XOR(f_hi, g_hi))) + local u_lo = XOR(OR(SHR(e_lo, 14), SHL(e_hi, 18)), OR(SHR(e_lo, 18), SHL(e_hi, 14)), OR(SHL(e_lo, 23), SHR(e_hi, 9))) + local u_hi = XOR(OR(SHR(e_hi, 14), SHL(e_lo, 18)), OR(SHR(e_hi, 18), SHL(e_lo, 14)), OR(SHL(e_hi, 23), SHR(e_lo, 9))) + local sum_lo = u_lo % 2^32 + t_lo % 2^32 + h_lo % 2^32 + K_lo[j] + W[2*j] % 2^32 + local z_lo, z_hi = NORM( sum_lo ), NORM( u_hi + t_hi + h_hi + K_hi[j] + W[2*j-1] + floor(sum_lo / 2^32) ) + h_lo, h_hi, g_lo, g_hi, f_lo, f_hi = g_lo, g_hi, f_lo, f_hi, e_lo, e_hi + local sum_lo = z_lo % 2^32 + d_lo % 2^32 + e_lo, e_hi = NORM( sum_lo ), NORM( z_hi + d_hi + floor(sum_lo / 2^32) ) + d_lo, d_hi, c_lo, c_hi, b_lo, b_hi = c_lo, c_hi, b_lo, b_hi, a_lo, a_hi + u_lo = XOR(OR(SHR(b_lo, 28), SHL(b_hi, 4)), OR(SHL(b_lo, 30), SHR(b_hi, 2)), OR(SHL(b_lo, 25), SHR(b_hi, 7))) + u_hi = XOR(OR(SHR(b_hi, 28), SHL(b_lo, 4)), OR(SHL(b_hi, 30), SHR(b_lo, 2)), OR(SHL(b_hi, 25), SHR(b_lo, 7))) + t_lo = OR(AND(d_lo, c_lo), AND(b_lo, XOR(d_lo, c_lo))) + t_hi = OR(AND(d_hi, c_hi), AND(b_hi, XOR(d_hi, c_hi))) + local sum_lo = z_lo % 2^32 + u_lo % 2^32 + t_lo % 2^32 + a_lo, a_hi = NORM( sum_lo ), NORM( z_hi + u_hi + t_hi + floor(sum_lo / 2^32) ) + end + H_lo[1], H_hi[1] = ADD64_4(H_lo[1], H_hi[1], a_lo, a_hi, 0, 0, 0, 0) + H_lo[2], H_hi[2] = ADD64_4(H_lo[2], H_hi[2], b_lo, b_hi, 0, 0, 0, 0) + H_lo[3], H_hi[3] = ADD64_4(H_lo[3], H_hi[3], c_lo, c_hi, 0, 0, 0, 0) + H_lo[4], H_hi[4] = ADD64_4(H_lo[4], H_hi[4], d_lo, d_hi, 0, 0, 0, 0) + H_lo[5], H_hi[5] = ADD64_4(H_lo[5], H_hi[5], e_lo, e_hi, 0, 0, 0, 0) + H_lo[6], H_hi[6] = ADD64_4(H_lo[6], H_hi[6], f_lo, f_hi, 0, 0, 0, 0) + H_lo[7], H_hi[7] = ADD64_4(H_lo[7], H_hi[7], g_lo, g_hi, 0, 0, 0, 0) + H_lo[8], H_hi[8] = ADD64_4(H_lo[8], H_hi[8], h_lo, h_hi, 0, 0, 0, 0) + end + end + + end + + + -- MD5 implementation for "LuaJIT without FFI" branch + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, md5_K + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + local a, b, c, d = H[1], H[2], H[3], H[4] + for j = 1, 16, 4 do + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j ] + W[j ] + a), 7) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+1] + W[j+1] + a), 12) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+2] + W[j+2] + a), 17) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(d, AND(b, XOR(c, d))) + (K[j+3] + W[j+3] + a), 22) + b) + end + for j = 17, 32, 4 do + local g = 5*j-4 + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j ] + W[AND(g , 15) + 1] + a), 5) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+1] + W[AND(g + 5, 15) + 1] + a), 9) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+2] + W[AND(g + 10, 15) + 1] + a), 14) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, AND(d, XOR(b, c))) + (K[j+3] + W[AND(g - 1, 15) + 1] + a), 20) + b) + end + for j = 33, 48, 4 do + local g = 3*j+2 + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j ] + W[AND(g , 15) + 1] + a), 4) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+1] + W[AND(g + 3, 15) + 1] + a), 11) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+2] + W[AND(g + 6, 15) + 1] + a), 16) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(b, c, d) + (K[j+3] + W[AND(g - 7, 15) + 1] + a), 23) + b) + end + for j = 49, 64, 4 do + local g = j*7 + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j ] + W[AND(g - 7, 15) + 1] + a), 6) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+1] + W[AND(g , 15) + 1] + a), 10) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+2] + W[AND(g + 7, 15) + 1] + a), 15) + b) + a, d, c, b = d, c, b, NORM(ROL(XOR(c, OR(b, NOT(d))) + (K[j+3] + W[AND(g - 2, 15) + 1] + a), 21) + b) + end + H[1], H[2], H[3], H[4] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]) + end + end + + + -- SHA-1 implementation for "LuaJIT without FFI" branch + + function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(a, 24), SHL(b, 16), SHL(c, 8), d) + end + for j = 17, 80 do + W[j] = ROL(XOR(W[j-3], W[j-8], W[j-14], W[j-16]), 1) + end + local a, b, c, d, e = H[1], H[2], H[3], H[4], H[5] + for j = 1, 20, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j] + 0x5A827999 + e)) -- constant = floor(2^30 * sqrt(2)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+1] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+2] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+3] + 0x5A827999 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(d, AND(b, XOR(d, c))) + (W[j+4] + 0x5A827999 + e)) + end + for j = 21, 40, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0x6ED9EBA1 + e)) -- 2^30 * sqrt(3) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0x6ED9EBA1 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0x6ED9EBA1 + e)) + end + for j = 41, 60, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j] + 0x8F1BBCDC + e)) -- 2^30 * sqrt(5) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+1] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+2] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+3] + 0x8F1BBCDC + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(AND(d, XOR(b, c)), AND(b, c)) + (W[j+4] + 0x8F1BBCDC + e)) + end + for j = 61, 80, 5 do + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j] + 0xCA62C1D6 + e)) -- 2^30 * sqrt(10) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+1] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+2] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+3] + 0xCA62C1D6 + e)) + e, d, c, b, a = d, c, ROR(b, 2), a, NORM(ROL(a, 5) + XOR(b, c, d) + (W[j+4] + 0xCA62C1D6 + e)) + end + H[1], H[2], H[3], H[4], H[5] = NORM(a + H[1]), NORM(b + H[2]), NORM(c + H[3]), NORM(d + H[4]), NORM(e + H[5]) + end + end + + + -- BLAKE2b implementation for "LuaJIT without FFI" branch + + do + local v_lo, v_hi = {}, {} + + local function G(a, b, c, d, k1, k2) + local W = common_W + local va_lo, vb_lo, vc_lo, vd_lo = v_lo[a], v_lo[b], v_lo[c], v_lo[d] + local va_hi, vb_hi, vc_hi, vd_hi = v_hi[a], v_hi[b], v_hi[c], v_hi[d] + local z = W[2*k1-1] + (va_lo % 2^32 + vb_lo % 2^32) + va_lo = NORM(z) + va_hi = NORM(W[2*k1] + (va_hi + vb_hi + floor(z / 2^32))) + vd_lo, vd_hi = XOR(vd_hi, va_hi), XOR(vd_lo, va_lo) + z = vc_lo % 2^32 + vd_lo % 2^32 + vc_lo = NORM(z) + vc_hi = NORM(vc_hi + vd_hi + floor(z / 2^32)) + vb_lo, vb_hi = XOR(vb_lo, vc_lo), XOR(vb_hi, vc_hi) + vb_lo, vb_hi = XOR(SHR(vb_lo, 24), SHL(vb_hi, 8)), XOR(SHR(vb_hi, 24), SHL(vb_lo, 8)) + z = W[2*k2-1] + (va_lo % 2^32 + vb_lo % 2^32) + va_lo = NORM(z) + va_hi = NORM(W[2*k2] + (va_hi + vb_hi + floor(z / 2^32))) + vd_lo, vd_hi = XOR(vd_lo, va_lo), XOR(vd_hi, va_hi) + vd_lo, vd_hi = XOR(SHR(vd_lo, 16), SHL(vd_hi, 16)), XOR(SHR(vd_hi, 16), SHL(vd_lo, 16)) + z = vc_lo % 2^32 + vd_lo % 2^32 + vc_lo = NORM(z) + vc_hi = NORM(vc_hi + vd_hi + floor(z / 2^32)) + vb_lo, vb_hi = XOR(vb_lo, vc_lo), XOR(vb_hi, vc_hi) + vb_lo, vb_hi = XOR(SHL(vb_lo, 1), SHR(vb_hi, 31)), XOR(SHL(vb_hi, 1), SHR(vb_lo, 31)) + v_lo[a], v_lo[b], v_lo[c], v_lo[d] = va_lo, vb_lo, vc_lo, vd_lo + v_hi[a], v_hi[b], v_hi[c], v_hi[d] = va_hi, vb_hi, vc_hi, vd_hi + end + + function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 32 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = d * 2^24 + OR(SHL(c, 16), SHL(b, 8), a) + end + end + v_lo[0x0], v_lo[0x1], v_lo[0x2], v_lo[0x3], v_lo[0x4], v_lo[0x5], v_lo[0x6], v_lo[0x7] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + v_lo[0x8], v_lo[0x9], v_lo[0xA], v_lo[0xB], v_lo[0xC], v_lo[0xD], v_lo[0xE], v_lo[0xF] = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[5], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + v_hi[0x0], v_hi[0x1], v_hi[0x2], v_hi[0x3], v_hi[0x4], v_hi[0x5], v_hi[0x6], v_hi[0x7] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + v_hi[0x8], v_hi[0x9], v_hi[0xA], v_hi[0xB], v_hi[0xC], v_hi[0xD], v_hi[0xE], v_hi[0xF] = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + local t0_lo = bytes_compressed % 2^32 + local t0_hi = floor(bytes_compressed / 2^32) + v_lo[0xC] = XOR(v_lo[0xC], t0_lo) -- t0 = low_8_bytes(bytes_compressed) + v_hi[0xC] = XOR(v_hi[0xC], t0_hi) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + v_lo[0xE] = NOT(v_lo[0xE]) + v_hi[0xE] = NOT(v_hi[0xE]) + end + if is_last_node then -- flag f1 + v_lo[0xF] = NOT(v_lo[0xF]) + v_hi[0xF] = NOT(v_hi[0xF]) + end + for j = 1, 12 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1_lo = XOR(h1_lo, v_lo[0x0], v_lo[0x8]) + h2_lo = XOR(h2_lo, v_lo[0x1], v_lo[0x9]) + h3_lo = XOR(h3_lo, v_lo[0x2], v_lo[0xA]) + h4_lo = XOR(h4_lo, v_lo[0x3], v_lo[0xB]) + h5_lo = XOR(h5_lo, v_lo[0x4], v_lo[0xC]) + h6_lo = XOR(h6_lo, v_lo[0x5], v_lo[0xD]) + h7_lo = XOR(h7_lo, v_lo[0x6], v_lo[0xE]) + h8_lo = XOR(h8_lo, v_lo[0x7], v_lo[0xF]) + h1_hi = XOR(h1_hi, v_hi[0x0], v_hi[0x8]) + h2_hi = XOR(h2_hi, v_hi[0x1], v_hi[0x9]) + h3_hi = XOR(h3_hi, v_hi[0x2], v_hi[0xA]) + h4_hi = XOR(h4_hi, v_hi[0x3], v_hi[0xB]) + h5_hi = XOR(h5_hi, v_hi[0x4], v_hi[0xC]) + h6_hi = XOR(h6_hi, v_hi[0x5], v_hi[0xD]) + h7_hi = XOR(h7_hi, v_hi[0x6], v_hi[0xE]) + h8_hi = XOR(h8_hi, v_hi[0x7], v_hi[0xF]) + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo % 2^32, h2_lo % 2^32, h3_lo % 2^32, h4_lo % 2^32, h5_lo % 2^32, h6_lo % 2^32, h7_lo % 2^32, h8_lo % 2^32 + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi % 2^32, h2_hi % 2^32, h3_hi % 2^32, h4_hi % 2^32, h5_hi % 2^32, h6_hi % 2^32, h7_hi % 2^32, h8_hi % 2^32 + return bytes_compressed + end + + end +end + + +if branch == "FFI" or branch == "LJ" then + + + -- BLAKE2s and BLAKE3 implementations for "LuaJIT with FFI" and "LuaJIT without FFI" branches + + do + local W = common_W_blake2s + local v = v_for_blake2s_feed_64 + + local function G(a, b, c, d, k1, k2) + local va, vb, vc, vd = v[a], v[b], v[c], v[d] + va = NORM(W[k1] + (va + vb)) + vd = ROR(XOR(vd, va), 16) + vc = NORM(vc + vd) + vb = ROR(XOR(vb, vc), 12) + va = NORM(W[k2] + (va + vb)) + vd = ROR(XOR(vd, va), 8) + vc = NORM(vc + vd) + vb = ROR(XOR(vb, vc), 7) + v[a], v[b], v[c], v[d] = va, vb, vc, vd + end + + function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local h1, h2, h3, h4, h5, h6, h7, h8 = NORM(H[1]), NORM(H[2]), NORM(H[3]), NORM(H[4]), NORM(H[5]), NORM(H[6]), NORM(H[7]), NORM(H[8]) + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB], v[0xE], v[0xF] = NORM(sha2_H_hi[1]), NORM(sha2_H_hi[2]), NORM(sha2_H_hi[3]), NORM(sha2_H_hi[4]), NORM(sha2_H_hi[7]), NORM(sha2_H_hi[8]) + bytes_compressed = bytes_compressed + (last_block_size or 64) + local t0 = bytes_compressed % 2^32 + local t1 = floor(bytes_compressed / 2^32) + v[0xC] = XOR(sha2_H_hi[5], t0) -- t0 = low_4_bytes(bytes_compressed) + v[0xD] = XOR(sha2_H_hi[6], t1) -- t1 = high_4_bytes(bytes_compressed + if last_block_size then -- flag f0 + v[0xE] = NOT(v[0xE]) + end + if is_last_node then -- flag f1 + v[0xF] = NOT(v[0xF]) + end + for j = 1, 10 do + local row = sigma[j] + G(0, 4, 8, 12, row[ 1], row[ 2]) + G(1, 5, 9, 13, row[ 3], row[ 4]) + G(2, 6, 10, 14, row[ 5], row[ 6]) + G(3, 7, 11, 15, row[ 7], row[ 8]) + G(0, 5, 10, 15, row[ 9], row[10]) + G(1, 6, 11, 12, row[11], row[12]) + G(2, 7, 8, 13, row[13], row[14]) + G(3, 4, 9, 14, row[15], row[16]) + end + h1 = XOR(h1, v[0x0], v[0x8]) + h2 = XOR(h2, v[0x1], v[0x9]) + h3 = XOR(h3, v[0x2], v[0xA]) + h4 = XOR(h4, v[0x3], v[0xB]) + h5 = XOR(h5, v[0x4], v[0xC]) + h6 = XOR(h6, v[0x5], v[0xD]) + h7 = XOR(h7, v[0x6], v[0xE]) + h8 = XOR(h8, v[0x7], v[0xF]) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local h1, h2, h3, h4, h5, h6, h7, h8 = NORM(H_in[1]), NORM(H_in[2]), NORM(H_in[3]), NORM(H_in[4]), NORM(H_in[5]), NORM(H_in[6]), NORM(H_in[7]), NORM(H_in[8]) + H_out = H_out or H_in + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + end + v[0x0], v[0x1], v[0x2], v[0x3], v[0x4], v[0x5], v[0x6], v[0x7] = h1, h2, h3, h4, h5, h6, h7, h8 + v[0x8], v[0x9], v[0xA], v[0xB] = NORM(sha2_H_hi[1]), NORM(sha2_H_hi[2]), NORM(sha2_H_hi[3]), NORM(sha2_H_hi[4]) + v[0xC] = NORM(chunk_index % 2^32) -- t0 = low_4_bytes(chunk_index) + v[0xD] = floor(chunk_index / 2^32) -- t1 = high_4_bytes(chunk_index) + v[0xE], v[0xF] = block_length, flags + for j = 1, 7 do + G(0, 4, 8, 12, perm_blake3[j], perm_blake3[j + 14]) + G(1, 5, 9, 13, perm_blake3[j + 1], perm_blake3[j + 2]) + G(2, 6, 10, 14, perm_blake3[j + 16], perm_blake3[j + 7]) + G(3, 7, 11, 15, perm_blake3[j + 15], perm_blake3[j + 17]) + G(0, 5, 10, 15, perm_blake3[j + 21], perm_blake3[j + 5]) + G(1, 6, 11, 12, perm_blake3[j + 3], perm_blake3[j + 6]) + G(2, 7, 8, 13, perm_blake3[j + 4], perm_blake3[j + 18]) + G(3, 4, 9, 14, perm_blake3[j + 19], perm_blake3[j + 20]) + end + if wide_output then + H_out[ 9] = XOR(h1, v[0x8]) + H_out[10] = XOR(h2, v[0x9]) + H_out[11] = XOR(h3, v[0xA]) + H_out[12] = XOR(h4, v[0xB]) + H_out[13] = XOR(h5, v[0xC]) + H_out[14] = XOR(h6, v[0xD]) + H_out[15] = XOR(h7, v[0xE]) + H_out[16] = XOR(h8, v[0xF]) + end + h1 = XOR(v[0x0], v[0x8]) + h2 = XOR(v[0x1], v[0x9]) + h3 = XOR(v[0x2], v[0xA]) + h4 = XOR(v[0x3], v[0xB]) + h5 = XOR(v[0x4], v[0xC]) + h6 = XOR(v[0x5], v[0xD]) + h7 = XOR(v[0x6], v[0xE]) + h8 = XOR(v[0x7], v[0xF]) + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + end + +end + + +if branch == "INT64" then + + + -- implementation for Lua 5.3/5.4 + + hi_factor = 4294967296 + hi_factor_keccak = 4294967296 + lanes_index_base = 1 + + HEX64, XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 = load[=[-- branch "INT64" + local md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3 = ... + local string_format, string_unpack = string.format, string.unpack + + local function HEX64(x) + return string_format("%016x", x) + end + + local function XORA5(x, y) + return x ~ (y or 0xa5a5a5a5a5a5a5a5) + end + + local function XOR_BYTE(x, y) + return x ~ y + end + + local function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", str, pos) + for j = 17, 64 do + local a = W[j-15] + a = a<<32 | a + local b = W[j-2] + b = b<<32 | b + W[j] = (a>>7 ~ a>>18 ~ a>>35) + (b>>17 ~ b>>19 ~ b>>42) + W[j-7] + W[j-16] & (1<<32)-1 + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 64 do + e = e<<32 | e & (1<<32)-1 + local z = (e>>6 ~ e>>11 ~ e>>25) + (g ~ e & (f ~ g)) + h + K[j] + W[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = a<<32 | a & (1<<32)-1 + a = z + ((a ~ c) & d ~ a & c) + (a>>2 ~ a>>13 ~ a>>22) + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + h6 = f + h6 + h7 = g + h7 + h8 = h + h8 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + local function sha512_feed_128(H, _, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + local W, K = common_W, sha2_K_lo + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 128 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8i8", str, pos) + for j = 17, 80 do + local a = W[j-15] + local b = W[j-2] + W[j] = (a >> 1 ~ a >> 7 ~ a >> 8 ~ a << 56 ~ a << 63) + (b >> 6 ~ b >> 19 ~ b >> 61 ~ b << 3 ~ b << 45) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 80 do + local z = (e >> 14 ~ e >> 18 ~ e >> 41 ~ e << 23 ~ e << 46 ~ e << 50) + (g ~ e & (f ~ g)) + h + K[j] + W[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = z + ((a ~ c) & d ~ a & c) + (a >> 28 ~ a >> 34 ~ a >> 39 ~ a << 25 ~ a << 30 ~ a << 36) + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + h6 = f + h6 + h7 = g + h7 + h8 = h + h8 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + local function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> s) + b + s = md5_next_shift[s] + end + s = 32-5 + for j = 17, 32 do + local F = (c ~ d & (b ~ c)) + a + K[j] + W[(5*j-4 & 15) + 1] + a = d + d = c + c = b + b = ((F<<32 | F & (1<<32)-1) >> s) + b + s = md5_next_shift[s] + end + s = 32-4 + for j = 33, 48 do + local F = (b ~ c ~ d) + a + K[j] + W[(3*j+2 & 15) + 1] + a = d + d = c + c = b + b = ((F<<32 | F & (1<<32)-1) >> s) + b + s = md5_next_shift[s] + end + s = 32-6 + for j = 49, 64 do + local F = (c ~ (b | ~d)) + a + K[j] + W[(j*7-7 & 15) + 1] + a = d + d = c + c = b + b = ((F<<32 | F & (1<<32)-1) >> s) + b + s = md5_next_shift[s] + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + local function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", str, pos) + for j = 17, 80 do + local a = W[j-3] ~ W[j-8] ~ W[j-14] ~ W[j-16] + W[j] = (a<<32 | a) << 1 >> 32 + end + local a, b, c, d, e = h1, h2, h3, h4, h5 + for j = 1, 20 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + (d ~ b & (c ~ d)) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2)) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + for j = 21, 40 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + (b ~ c ~ d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + for j = 41, 60 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + ((b ~ c) & d ~ b & c) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + for j = 61, 80 do + local z = ((a<<32 | a & (1<<32)-1) >> 27) + (b ~ c ~ d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10) + e = d + d = c + c = (b<<32 | b & (1<<32)-1) >> 2 + b = a + a = z + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + end + H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5 + end + + local keccak_format_i8 = build_keccak_format("i8") + + local function keccak_feed(lanes, _, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC = sha3_RC_lo + local qwords_qty = block_size_in_bytes / 8 + local keccak_format = keccak_format_i8[qwords_qty] + for pos = offs + 1, offs + size, block_size_in_bytes do + local qwords_from_message = {string_unpack(keccak_format, str, pos)} + for j = 1, qwords_qty do + lanes[j] = lanes[j] ~ qwords_from_message[j] + end + local L01, L02, L03, L04, L05, L06, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18, L19, L20, L21, L22, L23, L24, L25 = + lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7], lanes[8], lanes[9], lanes[10], lanes[11], lanes[12], lanes[13], + lanes[14], lanes[15], lanes[16], lanes[17], lanes[18], lanes[19], lanes[20], lanes[21], lanes[22], lanes[23], lanes[24], lanes[25] + for round_idx = 1, 24 do + local C1 = L01 ~ L06 ~ L11 ~ L16 ~ L21 + local C2 = L02 ~ L07 ~ L12 ~ L17 ~ L22 + local C3 = L03 ~ L08 ~ L13 ~ L18 ~ L23 + local C4 = L04 ~ L09 ~ L14 ~ L19 ~ L24 + local C5 = L05 ~ L10 ~ L15 ~ L20 ~ L25 + local D = C1 ~ C3<<1 ~ C3>>63 + local T0 = D ~ L02 + local T1 = D ~ L07 + local T2 = D ~ L12 + local T3 = D ~ L17 + local T4 = D ~ L22 + L02 = T1<<44 ~ T1>>20 + L07 = T3<<45 ~ T3>>19 + L12 = T0<<1 ~ T0>>63 + L17 = T2<<10 ~ T2>>54 + L22 = T4<<2 ~ T4>>62 + D = C2 ~ C4<<1 ~ C4>>63 + T0 = D ~ L03 + T1 = D ~ L08 + T2 = D ~ L13 + T3 = D ~ L18 + T4 = D ~ L23 + L03 = T2<<43 ~ T2>>21 + L08 = T4<<61 ~ T4>>3 + L13 = T1<<6 ~ T1>>58 + L18 = T3<<15 ~ T3>>49 + L23 = T0<<62 ~ T0>>2 + D = C3 ~ C5<<1 ~ C5>>63 + T0 = D ~ L04 + T1 = D ~ L09 + T2 = D ~ L14 + T3 = D ~ L19 + T4 = D ~ L24 + L04 = T3<<21 ~ T3>>43 + L09 = T0<<28 ~ T0>>36 + L14 = T2<<25 ~ T2>>39 + L19 = T4<<56 ~ T4>>8 + L24 = T1<<55 ~ T1>>9 + D = C4 ~ C1<<1 ~ C1>>63 + T0 = D ~ L05 + T1 = D ~ L10 + T2 = D ~ L15 + T3 = D ~ L20 + T4 = D ~ L25 + L05 = T4<<14 ~ T4>>50 + L10 = T1<<20 ~ T1>>44 + L15 = T3<<8 ~ T3>>56 + L20 = T0<<27 ~ T0>>37 + L25 = T2<<39 ~ T2>>25 + D = C5 ~ C2<<1 ~ C2>>63 + T1 = D ~ L06 + T2 = D ~ L11 + T3 = D ~ L16 + T4 = D ~ L21 + L06 = T2<<3 ~ T2>>61 + L11 = T4<<18 ~ T4>>46 + L16 = T1<<36 ~ T1>>28 + L21 = T3<<41 ~ T3>>23 + L01 = D ~ L01 + L01, L02, L03, L04, L05 = L01 ~ ~L02 & L03, L02 ~ ~L03 & L04, L03 ~ ~L04 & L05, L04 ~ ~L05 & L01, L05 ~ ~L01 & L02 + L06, L07, L08, L09, L10 = L09 ~ ~L10 & L06, L10 ~ ~L06 & L07, L06 ~ ~L07 & L08, L07 ~ ~L08 & L09, L08 ~ ~L09 & L10 + L11, L12, L13, L14, L15 = L12 ~ ~L13 & L14, L13 ~ ~L14 & L15, L14 ~ ~L15 & L11, L15 ~ ~L11 & L12, L11 ~ ~L12 & L13 + L16, L17, L18, L19, L20 = L20 ~ ~L16 & L17, L16 ~ ~L17 & L18, L17 ~ ~L18 & L19, L18 ~ ~L19 & L20, L19 ~ ~L20 & L16 + L21, L22, L23, L24, L25 = L23 ~ ~L24 & L25, L24 ~ ~L25 & L21, L25 ~ ~L21 & L22, L21 ~ ~L22 & L23, L22 ~ ~L23 & L24 + L01 = L01 ~ RC[round_idx] + end + lanes[1] = L01 + lanes[2] = L02 + lanes[3] = L03 + lanes[4] = L04 + lanes[5] = L05 + lanes[6] = L06 + lanes[7] = L07 + lanes[8] = L08 + lanes[9] = L09 + lanes[10] = L10 + lanes[11] = L11 + lanes[12] = L12 + lanes[13] = L13 + lanes[14] = L14 + lanes[15] = L15 + lanes[16] = L16 + lanes[17] = L17 + lanes[18] = L18 + lanes[19] = L19 + lanes[20] = L20 + lanes[21] = L21 + lanes[22] = L22 + lanes[23] = L23 + lanes[24] = L24 + lanes[25] = L25 + end + end + + local function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 32 -- t1 = high_4_bytes(bytes_compressed) + if last_block_size then -- flag f0 + vE = ~vE + end + if is_last_node then -- flag f1 + vF = ~vF + end + for j = 1, 10 do + local row = sigma[j] + v0 = v0 + v4 + W[row[1]] + vC = vC ~ v0 + vC = (vC & (1<<32)-1) >> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v0 = v0 + v4 + W[row[2]] + vC = vC ~ v0 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + v1 = v1 + v5 + W[row[3]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v1 = v1 + v5 + W[row[4]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v2 = v2 + v6 + W[row[5]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v2 = v2 + v6 + W[row[6]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v3 = v3 + v7 + W[row[7]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v3 = v3 + v7 + W[row[8]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v0 = v0 + v5 + W[row[9]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v0 = v0 + v5 + W[row[10]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v1 = v1 + v6 + W[row[11]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v1 = v1 + v6 + W[row[12]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v2 = v2 + v7 + W[row[13]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v2 = v2 + v7 + W[row[14]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v3 = v3 + v4 + W[row[15]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v3 = v3 + v4 + W[row[16]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + end + h1 = h1 ~ v0 ~ v8 + h2 = h2 ~ v1 ~ v9 + h3 = h3 ~ v2 ~ vA + h4 = h4 ~ v3 ~ vB + h5 = h5 ~ v4 ~ vC + h6 = h6 ~ v5 ~ vD + h7 = h7 ~ v6 ~ vE + h8 = h8 ~ v7 ~ vF + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + local function blake2b_feed_128(H, _, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 128 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 32 | vC << 32 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 24 | v4 << 40 + v0 = v0 + v4 + W[row[2]] + vC = vC ~ v0 + vC = vC >> 16 | vC << 48 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 63 | v4 << 1 + v1 = v1 + v5 + W[row[3]] + vD = vD ~ v1 + vD = vD >> 32 | vD << 32 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 24 | v5 << 40 + v1 = v1 + v5 + W[row[4]] + vD = vD ~ v1 + vD = vD >> 16 | vD << 48 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 63 | v5 << 1 + v2 = v2 + v6 + W[row[5]] + vE = vE ~ v2 + vE = vE >> 32 | vE << 32 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 24 | v6 << 40 + v2 = v2 + v6 + W[row[6]] + vE = vE ~ v2 + vE = vE >> 16 | vE << 48 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 63 | v6 << 1 + v3 = v3 + v7 + W[row[7]] + vF = vF ~ v3 + vF = vF >> 32 | vF << 32 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 24 | v7 << 40 + v3 = v3 + v7 + W[row[8]] + vF = vF ~ v3 + vF = vF >> 16 | vF << 48 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 63 | v7 << 1 + v0 = v0 + v5 + W[row[9]] + vF = vF ~ v0 + vF = vF >> 32 | vF << 32 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 24 | v5 << 40 + v0 = v0 + v5 + W[row[10]] + vF = vF ~ v0 + vF = vF >> 16 | vF << 48 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 63 | v5 << 1 + v1 = v1 + v6 + W[row[11]] + vC = vC ~ v1 + vC = vC >> 32 | vC << 32 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 24 | v6 << 40 + v1 = v1 + v6 + W[row[12]] + vC = vC ~ v1 + vC = vC >> 16 | vC << 48 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 63 | v6 << 1 + v2 = v2 + v7 + W[row[13]] + vD = vD ~ v2 + vD = vD >> 32 | vD << 32 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 24 | v7 << 40 + v2 = v2 + v7 + W[row[14]] + vD = vD ~ v2 + vD = vD >> 16 | vD << 48 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 63 | v7 << 1 + v3 = v3 + v4 + W[row[15]] + vE = vE ~ v3 + vE = vE >> 32 | vE << 32 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 24 | v4 << 40 + v3 = v3 + v4 + W[row[16]] + vE = vE ~ v3 + vE = vE >> 16 | vE << 48 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 63 | v4 << 1 + end + h1 = h1 ~ v0 ~ v8 + h2 = h2 ~ v1 ~ v9 + h3 = h3 ~ v2 ~ vA + h4 = h4 ~ v3 ~ vB + h5 = h5 ~ v4 ~ vC + h6 = h6 ~ v5 ~ vD + h7 = h7 ~ v6 ~ vE + h8 = h8 ~ v7 ~ vF + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + local function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8] + H_out = H_out or H_in + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v0 = v0 + v4 + W[perm_blake3[j + 14]] + vC = vC ~ v0 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + v1 = v1 + v5 + W[perm_blake3[j + 1]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v1 = v1 + v5 + W[perm_blake3[j + 2]] + vD = vD ~ v1 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v2 = v2 + v6 + W[perm_blake3[j + 16]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v2 = v2 + v6 + W[perm_blake3[j + 7]] + vE = vE ~ v2 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v3 = v3 + v7 + W[perm_blake3[j + 15]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v3 = v3 + v7 + W[perm_blake3[j + 17]] + vF = vF ~ v3 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v0 = v0 + v5 + W[perm_blake3[j + 21]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 12 | v5 << 20 + v0 = v0 + v5 + W[perm_blake3[j + 5]] + vF = vF ~ v0 + vF = (vF & (1<<32)-1) >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = (v5 & (1<<32)-1) >> 7 | v5 << 25 + v1 = v1 + v6 + W[perm_blake3[j + 3]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 12 | v6 << 20 + v1 = v1 + v6 + W[perm_blake3[j + 6]] + vC = vC ~ v1 + vC = (vC & (1<<32)-1) >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = (v6 & (1<<32)-1) >> 7 | v6 << 25 + v2 = v2 + v7 + W[perm_blake3[j + 4]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 12 | v7 << 20 + v2 = v2 + v7 + W[perm_blake3[j + 18]] + vD = vD ~ v2 + vD = (vD & (1<<32)-1) >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = (v7 & (1<<32)-1) >> 7 | v7 << 25 + v3 = v3 + v4 + W[perm_blake3[j + 19]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 12 | v4 << 20 + v3 = v3 + v4 + W[perm_blake3[j + 20]] + vE = vE ~ v3 + vE = (vE & (1<<32)-1) >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = (v4 & (1<<32)-1) >> 7 | v4 << 25 + end + if wide_output then + H_out[ 9] = h1 ~ v8 + H_out[10] = h2 ~ v9 + H_out[11] = h3 ~ vA + H_out[12] = h4 ~ vB + H_out[13] = h5 ~ vC + H_out[14] = h6 ~ vD + H_out[15] = h7 ~ vE + H_out[16] = h8 ~ vF + end + h1 = v0 ~ v8 + h2 = v1 ~ v9 + h3 = v2 ~ vA + h4 = v3 ~ vB + h5 = v4 ~ vC + h6 = v5 ~ vD + h7 = v6 ~ vE + h8 = v7 ~ vF + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + return HEX64, XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 + ]=](md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3) + +end + + +if branch == "INT32" then + + + -- implementation for Lua 5.3/5.4 having non-standard numbers config "int32"+"double" (built with LUA_INT_TYPE=LUA_INT_INT) + + K_lo_modulo = 2^32 + + function HEX(x) -- returns string of 8 lowercase hexadecimal digits + return string_format("%08x", x) + end + + XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 = load[=[-- branch "INT32" + local md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sha3_RC_hi, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3 = ... + local string_unpack, floor = string.unpack, math.floor + + local function XORA5(x, y) + return x ~ (y and (y + 2^31) % 2^32 - 2^31 or 0xA5A5A5A5) + end + + local function XOR_BYTE(x, y) + return x ~ y + end + + local function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos) + for j = 17, 64 do + local a, b = W[j-15], W[j-2] + W[j] = (a>>7 ~ a<<25 ~ a<<14 ~ a>>18 ~ a>>3) + (b<<15 ~ b>>17 ~ b<<13 ~ b>>19 ~ b>>10) + W[j-7] + W[j-16] + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 64 do + local z = (e>>6 ~ e<<26 ~ e>>11 ~ e<<21 ~ e>>25 ~ e<<7) + (g ~ e & (f ~ g)) + h + K[j] + W[j] + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a + a = z + ((a ~ c) & d ~ a & c) + (a>>2 ~ a<<30 ~ a>>13 ~ a<<19 ~ a<<10 ~ a>>22) + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + h6 = f + h6 + h7 = g + h7 + h8 = h + h8 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + local function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local floor, W, K_lo, K_hi = floor, common_W, sha2_K_lo, sha2_K_hi + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs + 1, offs + size, 128 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16], + W[17], W[18], W[19], W[20], W[21], W[22], W[23], W[24], W[25], W[26], W[27], W[28], W[29], W[30], W[31], W[32] = + string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos) + for jj = 17*2, 80*2, 2 do + local a_lo, a_hi, b_lo, b_hi = W[jj-30], W[jj-31], W[jj-4], W[jj-5] + local tmp = + (a_lo>>1 ~ a_hi<<31 ~ a_lo>>8 ~ a_hi<<24 ~ a_lo>>7 ~ a_hi<<25) % 2^32 + + (b_lo>>19 ~ b_hi<<13 ~ b_lo<<3 ~ b_hi>>29 ~ b_lo>>6 ~ b_hi<<26) % 2^32 + + W[jj-14] % 2^32 + W[jj-32] % 2^32 + W[jj-1] = + (a_hi>>1 ~ a_lo<<31 ~ a_hi>>8 ~ a_lo<<24 ~ a_hi>>7) + + (b_hi>>19 ~ b_lo<<13 ~ b_hi<<3 ~ b_lo>>29 ~ b_hi>>6) + + W[jj-15] + W[jj-33] + floor(tmp / 2^32) + W[jj] = 0|((tmp + 2^31) % 2^32 - 2^31) + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + for j = 1, 80 do + local jj = 2*j + local z_lo = (e_lo>>14 ~ e_hi<<18 ~ e_lo>>18 ~ e_hi<<14 ~ e_lo<<23 ~ e_hi>>9) % 2^32 + (g_lo ~ e_lo & (f_lo ~ g_lo)) % 2^32 + h_lo % 2^32 + K_lo[j] + W[jj] % 2^32 + local z_hi = (e_hi>>14 ~ e_lo<<18 ~ e_hi>>18 ~ e_lo<<14 ~ e_hi<<23 ~ e_lo>>9) + (g_hi ~ e_hi & (f_hi ~ g_hi)) + h_hi + K_hi[j] + W[jj-1] + floor(z_lo / 2^32) + z_lo = z_lo % 2^32 + h_lo = g_lo; h_hi = g_hi + g_lo = f_lo; g_hi = f_hi + f_lo = e_lo; f_hi = e_hi + e_lo = z_lo + d_lo % 2^32 + e_hi = z_hi + d_hi + floor(e_lo / 2^32) + e_lo = 0|((e_lo + 2^31) % 2^32 - 2^31) + d_lo = c_lo; d_hi = c_hi + c_lo = b_lo; c_hi = b_hi + b_lo = a_lo; b_hi = a_hi + z_lo = z_lo + (d_lo & c_lo ~ b_lo & (d_lo ~ c_lo)) % 2^32 + (b_lo>>28 ~ b_hi<<4 ~ b_lo<<30 ~ b_hi>>2 ~ b_lo<<25 ~ b_hi>>7) % 2^32 + a_hi = z_hi + (d_hi & c_hi ~ b_hi & (d_hi ~ c_hi)) + (b_hi>>28 ~ b_lo<<4 ~ b_hi<<30 ~ b_lo>>2 ~ b_hi<<25 ~ b_lo>>7) + floor(z_lo / 2^32) + a_lo = 0|((z_lo + 2^31) % 2^32 - 2^31) + end + a_lo = h1_lo % 2^32 + a_lo % 2^32 + h1_hi = h1_hi + a_hi + floor(a_lo / 2^32) + h1_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h2_lo % 2^32 + b_lo % 2^32 + h2_hi = h2_hi + b_hi + floor(a_lo / 2^32) + h2_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h3_lo % 2^32 + c_lo % 2^32 + h3_hi = h3_hi + c_hi + floor(a_lo / 2^32) + h3_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h4_lo % 2^32 + d_lo % 2^32 + h4_hi = h4_hi + d_hi + floor(a_lo / 2^32) + h4_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h5_lo % 2^32 + e_lo % 2^32 + h5_hi = h5_hi + e_hi + floor(a_lo / 2^32) + h5_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h6_lo % 2^32 + f_lo % 2^32 + h6_hi = h6_hi + f_hi + floor(a_lo / 2^32) + h6_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h7_lo % 2^32 + g_lo % 2^32 + h7_hi = h7_hi + g_hi + floor(a_lo / 2^32) + h7_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + a_lo = h8_lo % 2^32 + h_lo % 2^32 + h8_hi = h8_hi + h_hi + floor(a_lo / 2^32) + h8_lo = 0|((a_lo + 2^31) % 2^32 - 2^31) + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + end + + local function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">s) + b + s = md5_next_shift[s] + end + s = 32-5 + for j = 17, 32 do + local F = (c ~ d & (b ~ c)) + a + K[j] + W[(5*j-4 & 15) + 1] + a = d + d = c + c = b + b = (F << 32-s | F>>s) + b + s = md5_next_shift[s] + end + s = 32-4 + for j = 33, 48 do + local F = (b ~ c ~ d) + a + K[j] + W[(3*j+2 & 15) + 1] + a = d + d = c + c = b + b = (F << 32-s | F>>s) + b + s = md5_next_shift[s] + end + s = 32-6 + for j = 49, 64 do + local F = (c ~ (b | ~d)) + a + K[j] + W[(j*7-7 & 15) + 1] + a = d + d = c + c = b + b = (F << 32-s | F>>s) + b + s = md5_next_shift[s] + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + local function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5] + for pos = offs + 1, offs + size, 64 do + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack(">i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4i4", str, pos) + for j = 17, 80 do + local a = W[j-3] ~ W[j-8] ~ W[j-14] ~ W[j-16] + W[j] = a << 1 ~ a >> 31 + end + local a, b, c, d, e = h1, h2, h3, h4, h5 + for j = 1, 20 do + local z = (a << 5 ~ a >> 27) + (d ~ b & (c ~ d)) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2)) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + for j = 21, 40 do + local z = (a << 5 ~ a >> 27) + (b ~ c ~ d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + for j = 41, 60 do + local z = (a << 5 ~ a >> 27) + ((b ~ c) & d ~ b & c) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + for j = 61, 80 do + local z = (a << 5 ~ a >> 27) + (b ~ c ~ d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10) + e = d + d = c + c = b << 30 ~ b >> 2 + b = a + a = z + end + h1 = a + h1 + h2 = b + h2 + h3 = c + h3 + h4 = d + h4 + h5 = e + h5 + end + H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5 + end + + local keccak_format_i4i4 = build_keccak_format("i4i4") + + local function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi + local qwords_qty = block_size_in_bytes / 8 + local keccak_format = keccak_format_i4i4[qwords_qty] + for pos = offs + 1, offs + size, block_size_in_bytes do + local dwords_from_message = {string_unpack(keccak_format, str, pos)} + for j = 1, qwords_qty do + lanes_lo[j] = lanes_lo[j] ~ dwords_from_message[2*j-1] + lanes_hi[j] = lanes_hi[j] ~ dwords_from_message[2*j] + end + local L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi, + L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi, + L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi = + lanes_lo[1], lanes_hi[1], lanes_lo[2], lanes_hi[2], lanes_lo[3], lanes_hi[3], lanes_lo[4], lanes_hi[4], lanes_lo[5], lanes_hi[5], + lanes_lo[6], lanes_hi[6], lanes_lo[7], lanes_hi[7], lanes_lo[8], lanes_hi[8], lanes_lo[9], lanes_hi[9], lanes_lo[10], lanes_hi[10], + lanes_lo[11], lanes_hi[11], lanes_lo[12], lanes_hi[12], lanes_lo[13], lanes_hi[13], lanes_lo[14], lanes_hi[14], lanes_lo[15], lanes_hi[15], + lanes_lo[16], lanes_hi[16], lanes_lo[17], lanes_hi[17], lanes_lo[18], lanes_hi[18], lanes_lo[19], lanes_hi[19], lanes_lo[20], lanes_hi[20], + lanes_lo[21], lanes_hi[21], lanes_lo[22], lanes_hi[22], lanes_lo[23], lanes_hi[23], lanes_lo[24], lanes_hi[24], lanes_lo[25], lanes_hi[25] + for round_idx = 1, 24 do + local C1_lo = L01_lo ~ L06_lo ~ L11_lo ~ L16_lo ~ L21_lo + local C1_hi = L01_hi ~ L06_hi ~ L11_hi ~ L16_hi ~ L21_hi + local C2_lo = L02_lo ~ L07_lo ~ L12_lo ~ L17_lo ~ L22_lo + local C2_hi = L02_hi ~ L07_hi ~ L12_hi ~ L17_hi ~ L22_hi + local C3_lo = L03_lo ~ L08_lo ~ L13_lo ~ L18_lo ~ L23_lo + local C3_hi = L03_hi ~ L08_hi ~ L13_hi ~ L18_hi ~ L23_hi + local C4_lo = L04_lo ~ L09_lo ~ L14_lo ~ L19_lo ~ L24_lo + local C4_hi = L04_hi ~ L09_hi ~ L14_hi ~ L19_hi ~ L24_hi + local C5_lo = L05_lo ~ L10_lo ~ L15_lo ~ L20_lo ~ L25_lo + local C5_hi = L05_hi ~ L10_hi ~ L15_hi ~ L20_hi ~ L25_hi + local D_lo = C1_lo ~ C3_lo<<1 ~ C3_hi>>31 + local D_hi = C1_hi ~ C3_hi<<1 ~ C3_lo>>31 + local T0_lo = D_lo ~ L02_lo + local T0_hi = D_hi ~ L02_hi + local T1_lo = D_lo ~ L07_lo + local T1_hi = D_hi ~ L07_hi + local T2_lo = D_lo ~ L12_lo + local T2_hi = D_hi ~ L12_hi + local T3_lo = D_lo ~ L17_lo + local T3_hi = D_hi ~ L17_hi + local T4_lo = D_lo ~ L22_lo + local T4_hi = D_hi ~ L22_hi + L02_lo = T1_lo>>20 ~ T1_hi<<12 + L02_hi = T1_hi>>20 ~ T1_lo<<12 + L07_lo = T3_lo>>19 ~ T3_hi<<13 + L07_hi = T3_hi>>19 ~ T3_lo<<13 + L12_lo = T0_lo<<1 ~ T0_hi>>31 + L12_hi = T0_hi<<1 ~ T0_lo>>31 + L17_lo = T2_lo<<10 ~ T2_hi>>22 + L17_hi = T2_hi<<10 ~ T2_lo>>22 + L22_lo = T4_lo<<2 ~ T4_hi>>30 + L22_hi = T4_hi<<2 ~ T4_lo>>30 + D_lo = C2_lo ~ C4_lo<<1 ~ C4_hi>>31 + D_hi = C2_hi ~ C4_hi<<1 ~ C4_lo>>31 + T0_lo = D_lo ~ L03_lo + T0_hi = D_hi ~ L03_hi + T1_lo = D_lo ~ L08_lo + T1_hi = D_hi ~ L08_hi + T2_lo = D_lo ~ L13_lo + T2_hi = D_hi ~ L13_hi + T3_lo = D_lo ~ L18_lo + T3_hi = D_hi ~ L18_hi + T4_lo = D_lo ~ L23_lo + T4_hi = D_hi ~ L23_hi + L03_lo = T2_lo>>21 ~ T2_hi<<11 + L03_hi = T2_hi>>21 ~ T2_lo<<11 + L08_lo = T4_lo>>3 ~ T4_hi<<29 + L08_hi = T4_hi>>3 ~ T4_lo<<29 + L13_lo = T1_lo<<6 ~ T1_hi>>26 + L13_hi = T1_hi<<6 ~ T1_lo>>26 + L18_lo = T3_lo<<15 ~ T3_hi>>17 + L18_hi = T3_hi<<15 ~ T3_lo>>17 + L23_lo = T0_lo>>2 ~ T0_hi<<30 + L23_hi = T0_hi>>2 ~ T0_lo<<30 + D_lo = C3_lo ~ C5_lo<<1 ~ C5_hi>>31 + D_hi = C3_hi ~ C5_hi<<1 ~ C5_lo>>31 + T0_lo = D_lo ~ L04_lo + T0_hi = D_hi ~ L04_hi + T1_lo = D_lo ~ L09_lo + T1_hi = D_hi ~ L09_hi + T2_lo = D_lo ~ L14_lo + T2_hi = D_hi ~ L14_hi + T3_lo = D_lo ~ L19_lo + T3_hi = D_hi ~ L19_hi + T4_lo = D_lo ~ L24_lo + T4_hi = D_hi ~ L24_hi + L04_lo = T3_lo<<21 ~ T3_hi>>11 + L04_hi = T3_hi<<21 ~ T3_lo>>11 + L09_lo = T0_lo<<28 ~ T0_hi>>4 + L09_hi = T0_hi<<28 ~ T0_lo>>4 + L14_lo = T2_lo<<25 ~ T2_hi>>7 + L14_hi = T2_hi<<25 ~ T2_lo>>7 + L19_lo = T4_lo>>8 ~ T4_hi<<24 + L19_hi = T4_hi>>8 ~ T4_lo<<24 + L24_lo = T1_lo>>9 ~ T1_hi<<23 + L24_hi = T1_hi>>9 ~ T1_lo<<23 + D_lo = C4_lo ~ C1_lo<<1 ~ C1_hi>>31 + D_hi = C4_hi ~ C1_hi<<1 ~ C1_lo>>31 + T0_lo = D_lo ~ L05_lo + T0_hi = D_hi ~ L05_hi + T1_lo = D_lo ~ L10_lo + T1_hi = D_hi ~ L10_hi + T2_lo = D_lo ~ L15_lo + T2_hi = D_hi ~ L15_hi + T3_lo = D_lo ~ L20_lo + T3_hi = D_hi ~ L20_hi + T4_lo = D_lo ~ L25_lo + T4_hi = D_hi ~ L25_hi + L05_lo = T4_lo<<14 ~ T4_hi>>18 + L05_hi = T4_hi<<14 ~ T4_lo>>18 + L10_lo = T1_lo<<20 ~ T1_hi>>12 + L10_hi = T1_hi<<20 ~ T1_lo>>12 + L15_lo = T3_lo<<8 ~ T3_hi>>24 + L15_hi = T3_hi<<8 ~ T3_lo>>24 + L20_lo = T0_lo<<27 ~ T0_hi>>5 + L20_hi = T0_hi<<27 ~ T0_lo>>5 + L25_lo = T2_lo>>25 ~ T2_hi<<7 + L25_hi = T2_hi>>25 ~ T2_lo<<7 + D_lo = C5_lo ~ C2_lo<<1 ~ C2_hi>>31 + D_hi = C5_hi ~ C2_hi<<1 ~ C2_lo>>31 + T1_lo = D_lo ~ L06_lo + T1_hi = D_hi ~ L06_hi + T2_lo = D_lo ~ L11_lo + T2_hi = D_hi ~ L11_hi + T3_lo = D_lo ~ L16_lo + T3_hi = D_hi ~ L16_hi + T4_lo = D_lo ~ L21_lo + T4_hi = D_hi ~ L21_hi + L06_lo = T2_lo<<3 ~ T2_hi>>29 + L06_hi = T2_hi<<3 ~ T2_lo>>29 + L11_lo = T4_lo<<18 ~ T4_hi>>14 + L11_hi = T4_hi<<18 ~ T4_lo>>14 + L16_lo = T1_lo>>28 ~ T1_hi<<4 + L16_hi = T1_hi>>28 ~ T1_lo<<4 + L21_lo = T3_lo>>23 ~ T3_hi<<9 + L21_hi = T3_hi>>23 ~ T3_lo<<9 + L01_lo = D_lo ~ L01_lo + L01_hi = D_hi ~ L01_hi + L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = L01_lo ~ ~L02_lo & L03_lo, L02_lo ~ ~L03_lo & L04_lo, L03_lo ~ ~L04_lo & L05_lo, L04_lo ~ ~L05_lo & L01_lo, L05_lo ~ ~L01_lo & L02_lo + L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = L01_hi ~ ~L02_hi & L03_hi, L02_hi ~ ~L03_hi & L04_hi, L03_hi ~ ~L04_hi & L05_hi, L04_hi ~ ~L05_hi & L01_hi, L05_hi ~ ~L01_hi & L02_hi + L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = L09_lo ~ ~L10_lo & L06_lo, L10_lo ~ ~L06_lo & L07_lo, L06_lo ~ ~L07_lo & L08_lo, L07_lo ~ ~L08_lo & L09_lo, L08_lo ~ ~L09_lo & L10_lo + L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = L09_hi ~ ~L10_hi & L06_hi, L10_hi ~ ~L06_hi & L07_hi, L06_hi ~ ~L07_hi & L08_hi, L07_hi ~ ~L08_hi & L09_hi, L08_hi ~ ~L09_hi & L10_hi + L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = L12_lo ~ ~L13_lo & L14_lo, L13_lo ~ ~L14_lo & L15_lo, L14_lo ~ ~L15_lo & L11_lo, L15_lo ~ ~L11_lo & L12_lo, L11_lo ~ ~L12_lo & L13_lo + L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = L12_hi ~ ~L13_hi & L14_hi, L13_hi ~ ~L14_hi & L15_hi, L14_hi ~ ~L15_hi & L11_hi, L15_hi ~ ~L11_hi & L12_hi, L11_hi ~ ~L12_hi & L13_hi + L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = L20_lo ~ ~L16_lo & L17_lo, L16_lo ~ ~L17_lo & L18_lo, L17_lo ~ ~L18_lo & L19_lo, L18_lo ~ ~L19_lo & L20_lo, L19_lo ~ ~L20_lo & L16_lo + L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = L20_hi ~ ~L16_hi & L17_hi, L16_hi ~ ~L17_hi & L18_hi, L17_hi ~ ~L18_hi & L19_hi, L18_hi ~ ~L19_hi & L20_hi, L19_hi ~ ~L20_hi & L16_hi + L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = L23_lo ~ ~L24_lo & L25_lo, L24_lo ~ ~L25_lo & L21_lo, L25_lo ~ ~L21_lo & L22_lo, L21_lo ~ ~L22_lo & L23_lo, L22_lo ~ ~L23_lo & L24_lo + L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = L23_hi ~ ~L24_hi & L25_hi, L24_hi ~ ~L25_hi & L21_hi, L25_hi ~ ~L21_hi & L22_hi, L21_hi ~ ~L22_hi & L23_hi, L22_hi ~ ~L23_hi & L24_hi + L01_lo = L01_lo ~ RC_lo[round_idx] + L01_hi = L01_hi ~ RC_hi[round_idx] + end + lanes_lo[1] = L01_lo; lanes_hi[1] = L01_hi + lanes_lo[2] = L02_lo; lanes_hi[2] = L02_hi + lanes_lo[3] = L03_lo; lanes_hi[3] = L03_hi + lanes_lo[4] = L04_lo; lanes_hi[4] = L04_hi + lanes_lo[5] = L05_lo; lanes_hi[5] = L05_hi + lanes_lo[6] = L06_lo; lanes_hi[6] = L06_hi + lanes_lo[7] = L07_lo; lanes_hi[7] = L07_hi + lanes_lo[8] = L08_lo; lanes_hi[8] = L08_hi + lanes_lo[9] = L09_lo; lanes_hi[9] = L09_hi + lanes_lo[10] = L10_lo; lanes_hi[10] = L10_hi + lanes_lo[11] = L11_lo; lanes_hi[11] = L11_hi + lanes_lo[12] = L12_lo; lanes_hi[12] = L12_hi + lanes_lo[13] = L13_lo; lanes_hi[13] = L13_hi + lanes_lo[14] = L14_lo; lanes_hi[14] = L14_hi + lanes_lo[15] = L15_lo; lanes_hi[15] = L15_hi + lanes_lo[16] = L16_lo; lanes_hi[16] = L16_hi + lanes_lo[17] = L17_lo; lanes_hi[17] = L17_hi + lanes_lo[18] = L18_lo; lanes_hi[18] = L18_hi + lanes_lo[19] = L19_lo; lanes_hi[19] = L19_hi + lanes_lo[20] = L20_lo; lanes_hi[20] = L20_hi + lanes_lo[21] = L21_lo; lanes_hi[21] = L21_hi + lanes_lo[22] = L22_lo; lanes_hi[22] = L22_hi + lanes_lo[23] = L23_lo; lanes_hi[23] = L23_hi + lanes_lo[24] = L24_lo; lanes_hi[24] = L24_hi + lanes_lo[25] = L25_lo; lanes_hi[25] = L25_hi + end + end + + local function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 12 | v4 << 20 + v0 = v0 + v4 + W[row[2]] + vC = vC ~ v0 + vC = vC >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 7 | v4 << 25 + v1 = v1 + v5 + W[row[3]] + vD = vD ~ v1 + vD = vD >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 12 | v5 << 20 + v1 = v1 + v5 + W[row[4]] + vD = vD ~ v1 + vD = vD >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 7 | v5 << 25 + v2 = v2 + v6 + W[row[5]] + vE = vE ~ v2 + vE = vE >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 12 | v6 << 20 + v2 = v2 + v6 + W[row[6]] + vE = vE ~ v2 + vE = vE >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 7 | v6 << 25 + v3 = v3 + v7 + W[row[7]] + vF = vF ~ v3 + vF = vF >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 12 | v7 << 20 + v3 = v3 + v7 + W[row[8]] + vF = vF ~ v3 + vF = vF >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 7 | v7 << 25 + v0 = v0 + v5 + W[row[9]] + vF = vF ~ v0 + vF = vF >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 12 | v5 << 20 + v0 = v0 + v5 + W[row[10]] + vF = vF ~ v0 + vF = vF >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 7 | v5 << 25 + v1 = v1 + v6 + W[row[11]] + vC = vC ~ v1 + vC = vC >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 12 | v6 << 20 + v1 = v1 + v6 + W[row[12]] + vC = vC ~ v1 + vC = vC >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 7 | v6 << 25 + v2 = v2 + v7 + W[row[13]] + vD = vD ~ v2 + vD = vD >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 12 | v7 << 20 + v2 = v2 + v7 + W[row[14]] + vD = vD ~ v2 + vD = vD >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 7 | v7 << 25 + v3 = v3 + v4 + W[row[15]] + vE = vE ~ v3 + vE = vE >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 12 | v4 << 20 + v3 = v3 + v4 + W[row[16]] + vE = vE ~ v3 + vE = vE >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 7 | v4 << 25 + end + h1 = h1 ~ v0 ~ v8 + h2 = h2 ~ v1 ~ v9 + h3 = h3 ~ v2 ~ vA + h4 = h4 ~ v3 ~ vB + h5 = h5 ~ v4 ~ vC + h6 = h6 ~ v5 ~ vD + h7 = h7 ~ v6 ~ vE + h8 = h8 ~ v7 ~ vF + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + local function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs + 1, offs + size, 128 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16], + W[17], W[18], W[19], W[20], W[21], W[22], W[23], W[24], W[25], W[26], W[27], W[28], W[29], W[30], W[31], W[32] = + string_unpack("> 24 | v4_hi << 8, v4_hi >> 24 | v4_lo << 8 + k = row[2] * 2 + v0_lo = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32 + v0_hi = v0_hi + v4_hi + floor(v0_lo / 2^32) + W[k] + v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31) + vC_lo, vC_hi = vC_lo ~ v0_lo, vC_hi ~ v0_hi + vC_lo, vC_hi = vC_lo >> 16 | vC_hi << 16, vC_hi >> 16 | vC_lo << 16 + v8_lo = v8_lo % 2^32 + vC_lo % 2^32 + v8_hi = v8_hi + vC_hi + floor(v8_lo / 2^32) + v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31) + v4_lo, v4_hi = v4_lo ~ v8_lo, v4_hi ~ v8_hi + v4_lo, v4_hi = v4_lo << 1 | v4_hi >> 31, v4_hi << 1 | v4_lo >> 31 + k = row[3] * 2 + v1_lo = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v5_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_hi ~ v1_hi, vD_lo ~ v1_lo + v9_lo = v9_lo % 2^32 + vD_lo % 2^32 + v9_hi = v9_hi + vD_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ v9_lo, v5_hi ~ v9_hi + v5_lo, v5_hi = v5_lo >> 24 | v5_hi << 8, v5_hi >> 24 | v5_lo << 8 + k = row[4] * 2 + v1_lo = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v5_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_lo ~ v1_lo, vD_hi ~ v1_hi + vD_lo, vD_hi = vD_lo >> 16 | vD_hi << 16, vD_hi >> 16 | vD_lo << 16 + v9_lo = v9_lo % 2^32 + vD_lo % 2^32 + v9_hi = v9_hi + vD_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ v9_lo, v5_hi ~ v9_hi + v5_lo, v5_hi = v5_lo << 1 | v5_hi >> 31, v5_hi << 1 | v5_lo >> 31 + k = row[5] * 2 + v2_lo = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v6_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_hi ~ v2_hi, vE_lo ~ v2_lo + vA_lo = vA_lo % 2^32 + vE_lo % 2^32 + vA_hi = vA_hi + vE_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vA_lo, v6_hi ~ vA_hi + v6_lo, v6_hi = v6_lo >> 24 | v6_hi << 8, v6_hi >> 24 | v6_lo << 8 + k = row[6] * 2 + v2_lo = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v6_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_lo ~ v2_lo, vE_hi ~ v2_hi + vE_lo, vE_hi = vE_lo >> 16 | vE_hi << 16, vE_hi >> 16 | vE_lo << 16 + vA_lo = vA_lo % 2^32 + vE_lo % 2^32 + vA_hi = vA_hi + vE_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vA_lo, v6_hi ~ vA_hi + v6_lo, v6_hi = v6_lo << 1 | v6_hi >> 31, v6_hi << 1 | v6_lo >> 31 + k = row[7] * 2 + v3_lo = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v7_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_hi ~ v3_hi, vF_lo ~ v3_lo + vB_lo = vB_lo % 2^32 + vF_lo % 2^32 + vB_hi = vB_hi + vF_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ vB_lo, v7_hi ~ vB_hi + v7_lo, v7_hi = v7_lo >> 24 | v7_hi << 8, v7_hi >> 24 | v7_lo << 8 + k = row[8] * 2 + v3_lo = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v7_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_lo ~ v3_lo, vF_hi ~ v3_hi + vF_lo, vF_hi = vF_lo >> 16 | vF_hi << 16, vF_hi >> 16 | vF_lo << 16 + vB_lo = vB_lo % 2^32 + vF_lo % 2^32 + vB_hi = vB_hi + vF_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ vB_lo, v7_hi ~ vB_hi + v7_lo, v7_hi = v7_lo << 1 | v7_hi >> 31, v7_hi << 1 | v7_lo >> 31 + k = row[9] * 2 + v0_lo = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v0_hi = v0_hi + v5_hi + floor(v0_lo / 2^32) + W[k] + v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_hi ~ v0_hi, vF_lo ~ v0_lo + vA_lo = vA_lo % 2^32 + vF_lo % 2^32 + vA_hi = vA_hi + vF_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ vA_lo, v5_hi ~ vA_hi + v5_lo, v5_hi = v5_lo >> 24 | v5_hi << 8, v5_hi >> 24 | v5_lo << 8 + k = row[10] * 2 + v0_lo = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] % 2^32 + v0_hi = v0_hi + v5_hi + floor(v0_lo / 2^32) + W[k] + v0_lo = 0|((v0_lo + 2^31) % 2^32 - 2^31) + vF_lo, vF_hi = vF_lo ~ v0_lo, vF_hi ~ v0_hi + vF_lo, vF_hi = vF_lo >> 16 | vF_hi << 16, vF_hi >> 16 | vF_lo << 16 + vA_lo = vA_lo % 2^32 + vF_lo % 2^32 + vA_hi = vA_hi + vF_hi + floor(vA_lo / 2^32) + vA_lo = 0|((vA_lo + 2^31) % 2^32 - 2^31) + v5_lo, v5_hi = v5_lo ~ vA_lo, v5_hi ~ vA_hi + v5_lo, v5_hi = v5_lo << 1 | v5_hi >> 31, v5_hi << 1 | v5_lo >> 31 + k = row[11] * 2 + v1_lo = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v6_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vC_lo, vC_hi = vC_hi ~ v1_hi, vC_lo ~ v1_lo + vB_lo = vB_lo % 2^32 + vC_lo % 2^32 + vB_hi = vB_hi + vC_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vB_lo, v6_hi ~ vB_hi + v6_lo, v6_hi = v6_lo >> 24 | v6_hi << 8, v6_hi >> 24 | v6_lo << 8 + k = row[12] * 2 + v1_lo = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] % 2^32 + v1_hi = v1_hi + v6_hi + floor(v1_lo / 2^32) + W[k] + v1_lo = 0|((v1_lo + 2^31) % 2^32 - 2^31) + vC_lo, vC_hi = vC_lo ~ v1_lo, vC_hi ~ v1_hi + vC_lo, vC_hi = vC_lo >> 16 | vC_hi << 16, vC_hi >> 16 | vC_lo << 16 + vB_lo = vB_lo % 2^32 + vC_lo % 2^32 + vB_hi = vB_hi + vC_hi + floor(vB_lo / 2^32) + vB_lo = 0|((vB_lo + 2^31) % 2^32 - 2^31) + v6_lo, v6_hi = v6_lo ~ vB_lo, v6_hi ~ vB_hi + v6_lo, v6_hi = v6_lo << 1 | v6_hi >> 31, v6_hi << 1 | v6_lo >> 31 + k = row[13] * 2 + v2_lo = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v7_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_hi ~ v2_hi, vD_lo ~ v2_lo + v8_lo = v8_lo % 2^32 + vD_lo % 2^32 + v8_hi = v8_hi + vD_hi + floor(v8_lo / 2^32) + v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ v8_lo, v7_hi ~ v8_hi + v7_lo, v7_hi = v7_lo >> 24 | v7_hi << 8, v7_hi >> 24 | v7_lo << 8 + k = row[14] * 2 + v2_lo = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] % 2^32 + v2_hi = v2_hi + v7_hi + floor(v2_lo / 2^32) + W[k] + v2_lo = 0|((v2_lo + 2^31) % 2^32 - 2^31) + vD_lo, vD_hi = vD_lo ~ v2_lo, vD_hi ~ v2_hi + vD_lo, vD_hi = vD_lo >> 16 | vD_hi << 16, vD_hi >> 16 | vD_lo << 16 + v8_lo = v8_lo % 2^32 + vD_lo % 2^32 + v8_hi = v8_hi + vD_hi + floor(v8_lo / 2^32) + v8_lo = 0|((v8_lo + 2^31) % 2^32 - 2^31) + v7_lo, v7_hi = v7_lo ~ v8_lo, v7_hi ~ v8_hi + v7_lo, v7_hi = v7_lo << 1 | v7_hi >> 31, v7_hi << 1 | v7_lo >> 31 + k = row[15] * 2 + v3_lo = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v4_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_hi ~ v3_hi, vE_lo ~ v3_lo + v9_lo = v9_lo % 2^32 + vE_lo % 2^32 + v9_hi = v9_hi + vE_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v4_lo, v4_hi = v4_lo ~ v9_lo, v4_hi ~ v9_hi + v4_lo, v4_hi = v4_lo >> 24 | v4_hi << 8, v4_hi >> 24 | v4_lo << 8 + k = row[16] * 2 + v3_lo = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] % 2^32 + v3_hi = v3_hi + v4_hi + floor(v3_lo / 2^32) + W[k] + v3_lo = 0|((v3_lo + 2^31) % 2^32 - 2^31) + vE_lo, vE_hi = vE_lo ~ v3_lo, vE_hi ~ v3_hi + vE_lo, vE_hi = vE_lo >> 16 | vE_hi << 16, vE_hi >> 16 | vE_lo << 16 + v9_lo = v9_lo % 2^32 + vE_lo % 2^32 + v9_hi = v9_hi + vE_hi + floor(v9_lo / 2^32) + v9_lo = 0|((v9_lo + 2^31) % 2^32 - 2^31) + v4_lo, v4_hi = v4_lo ~ v9_lo, v4_hi ~ v9_hi + v4_lo, v4_hi = v4_lo << 1 | v4_hi >> 31, v4_hi << 1 | v4_lo >> 31 + end + h1_lo = h1_lo ~ v0_lo ~ v8_lo + h2_lo = h2_lo ~ v1_lo ~ v9_lo + h3_lo = h3_lo ~ v2_lo ~ vA_lo + h4_lo = h4_lo ~ v3_lo ~ vB_lo + h5_lo = h5_lo ~ v4_lo ~ vC_lo + h6_lo = h6_lo ~ v5_lo ~ vD_lo + h7_lo = h7_lo ~ v6_lo ~ vE_lo + h8_lo = h8_lo ~ v7_lo ~ vF_lo + h1_hi = h1_hi ~ v0_hi ~ v8_hi + h2_hi = h2_hi ~ v1_hi ~ v9_hi + h3_hi = h3_hi ~ v2_hi ~ vA_hi + h4_hi = h4_hi ~ v3_hi ~ vB_hi + h5_hi = h5_hi ~ v4_hi ~ vC_hi + h6_hi = h6_hi ~ v5_hi ~ vD_hi + h7_hi = h7_hi ~ v6_hi ~ vE_hi + h8_hi = h8_hi ~ v7_hi ~ vF_hi + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + return bytes_compressed + end + + local function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8] + H_out = H_out or H_in + for pos = offs + 1, offs + size, 64 do + if str then + W[1], W[2], W[3], W[4], W[5], W[6], W[7], W[8], W[9], W[10], W[11], W[12], W[13], W[14], W[15], W[16] = + string_unpack("> 16 | vC << 16 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 12 | v4 << 20 + v0 = v0 + v4 + W[perm_blake3[j + 14]] + vC = vC ~ v0 + vC = vC >> 8 | vC << 24 + v8 = v8 + vC + v4 = v4 ~ v8 + v4 = v4 >> 7 | v4 << 25 + v1 = v1 + v5 + W[perm_blake3[j + 1]] + vD = vD ~ v1 + vD = vD >> 16 | vD << 16 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 12 | v5 << 20 + v1 = v1 + v5 + W[perm_blake3[j + 2]] + vD = vD ~ v1 + vD = vD >> 8 | vD << 24 + v9 = v9 + vD + v5 = v5 ~ v9 + v5 = v5 >> 7 | v5 << 25 + v2 = v2 + v6 + W[perm_blake3[j + 16]] + vE = vE ~ v2 + vE = vE >> 16 | vE << 16 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 12 | v6 << 20 + v2 = v2 + v6 + W[perm_blake3[j + 7]] + vE = vE ~ v2 + vE = vE >> 8 | vE << 24 + vA = vA + vE + v6 = v6 ~ vA + v6 = v6 >> 7 | v6 << 25 + v3 = v3 + v7 + W[perm_blake3[j + 15]] + vF = vF ~ v3 + vF = vF >> 16 | vF << 16 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 12 | v7 << 20 + v3 = v3 + v7 + W[perm_blake3[j + 17]] + vF = vF ~ v3 + vF = vF >> 8 | vF << 24 + vB = vB + vF + v7 = v7 ~ vB + v7 = v7 >> 7 | v7 << 25 + v0 = v0 + v5 + W[perm_blake3[j + 21]] + vF = vF ~ v0 + vF = vF >> 16 | vF << 16 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 12 | v5 << 20 + v0 = v0 + v5 + W[perm_blake3[j + 5]] + vF = vF ~ v0 + vF = vF >> 8 | vF << 24 + vA = vA + vF + v5 = v5 ~ vA + v5 = v5 >> 7 | v5 << 25 + v1 = v1 + v6 + W[perm_blake3[j + 3]] + vC = vC ~ v1 + vC = vC >> 16 | vC << 16 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 12 | v6 << 20 + v1 = v1 + v6 + W[perm_blake3[j + 6]] + vC = vC ~ v1 + vC = vC >> 8 | vC << 24 + vB = vB + vC + v6 = v6 ~ vB + v6 = v6 >> 7 | v6 << 25 + v2 = v2 + v7 + W[perm_blake3[j + 4]] + vD = vD ~ v2 + vD = vD >> 16 | vD << 16 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 12 | v7 << 20 + v2 = v2 + v7 + W[perm_blake3[j + 18]] + vD = vD ~ v2 + vD = vD >> 8 | vD << 24 + v8 = v8 + vD + v7 = v7 ~ v8 + v7 = v7 >> 7 | v7 << 25 + v3 = v3 + v4 + W[perm_blake3[j + 19]] + vE = vE ~ v3 + vE = vE >> 16 | vE << 16 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 12 | v4 << 20 + v3 = v3 + v4 + W[perm_blake3[j + 20]] + vE = vE ~ v3 + vE = vE >> 8 | vE << 24 + v9 = v9 + vE + v4 = v4 ~ v9 + v4 = v4 >> 7 | v4 << 25 + end + if wide_output then + H_out[ 9] = h1 ~ v8 + H_out[10] = h2 ~ v9 + H_out[11] = h3 ~ vA + H_out[12] = h4 ~ vB + H_out[13] = h5 ~ vC + H_out[14] = h6 ~ vD + H_out[15] = h7 ~ vE + H_out[16] = h8 ~ vF + end + h1 = v0 ~ v8 + h2 = v1 ~ v9 + h3 = v2 ~ vA + h4 = v3 ~ vB + h5 = v4 ~ vC + h6 = v5 ~ vD + h7 = v6 ~ vE + h8 = v7 ~ vF + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + return XORA5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64, keccak_feed, blake2s_feed_64, blake2b_feed_128, blake3_feed_64 + ]=](md5_next_shift, md5_K, sha2_K_lo, sha2_K_hi, build_keccak_format, sha3_RC_lo, sha3_RC_hi, sigma, common_W, sha2_H_lo, sha2_H_hi, perm_blake3) + +end + +XOR = XOR or XORA5 + +if branch == "LIB32" or branch == "EMUL" then + + + -- implementation for Lua 5.1/5.2 (with or without bitwise library available) + + function sha256_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K = common_W, sha2_K_hi + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((a * 256 + b) * 256 + c) * 256 + d + end + for j = 17, 64 do + local a, b = W[j-15], W[j-2] + local a7, a18, b17, b19 = a / 2^7, a / 2^18, b / 2^17, b / 2^19 + W[j] = (XOR(a7 % 1 * (2^32 - 1) + a7, a18 % 1 * (2^32 - 1) + a18, (a - a % 2^3) / 2^3) + W[j-16] + W[j-7] + + XOR(b17 % 1 * (2^32 - 1) + b17, b19 % 1 * (2^32 - 1) + b19, (b - b % 2^10) / 2^10)) % 2^32 + end + local a, b, c, d, e, f, g, h = h1, h2, h3, h4, h5, h6, h7, h8 + for j = 1, 64 do + e = e % 2^32 + local e6, e11, e7 = e / 2^6, e / 2^11, e * 2^7 + local e7_lo = e7 % 2^32 + local z = AND(e, f) + AND(-1-e, g) + h + K[j] + W[j] + + XOR(e6 % 1 * (2^32 - 1) + e6, e11 % 1 * (2^32 - 1) + e11, e7_lo + (e7 - e7_lo) / 2^32) + h = g + g = f + f = e + e = z + d + d = c + c = b + b = a % 2^32 + local b2, b13, b10 = b / 2^2, b / 2^13, b * 2^10 + local b10_lo = b10 % 2^32 + a = z + AND(d, c) + AND(b, XOR(d, c)) + + XOR(b2 % 1 * (2^32 - 1) + b2, b13 % 1 * (2^32 - 1) + b13, b10_lo + (b10 - b10_lo) / 2^32) + end + h1, h2, h3, h4 = (a + h1) % 2^32, (b + h2) % 2^32, (c + h3) % 2^32, (d + h4) % 2^32 + h5, h6, h7, h8 = (e + h5) % 2^32, (f + h6) % 2^32, (g + h7) % 2^32, (h + h8) % 2^32 + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + + + function sha512_feed_128(H_lo, H_hi, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 128 + -- W1_hi, W1_lo, W2_hi, W2_lo, ... Wk_hi = W[2*k-1], Wk_lo = W[2*k] + local W, K_lo, K_hi = common_W, sha2_K_lo, sha2_K_hi + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs, offs + size - 1, 128 do + for j = 1, 16*2 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((a * 256 + b) * 256 + c) * 256 + d + end + for jj = 17*2, 80*2, 2 do + local a_hi, a_lo, b_hi, b_lo = W[jj-31], W[jj-30], W[jj-5], W[jj-4] + local b_hi_6, b_hi_19, b_hi_29, b_lo_19, b_lo_29, a_hi_1, a_hi_7, a_hi_8, a_lo_1, a_lo_8 = + b_hi % 2^6, b_hi % 2^19, b_hi % 2^29, b_lo % 2^19, b_lo % 2^29, a_hi % 2^1, a_hi % 2^7, a_hi % 2^8, a_lo % 2^1, a_lo % 2^8 + local tmp1 = XOR((a_lo - a_lo_1) / 2^1 + a_hi_1 * 2^31, (a_lo - a_lo_8) / 2^8 + a_hi_8 * 2^24, (a_lo - a_lo % 2^7) / 2^7 + a_hi_7 * 2^25) % 2^32 + + XOR((b_lo - b_lo_19) / 2^19 + b_hi_19 * 2^13, b_lo_29 * 2^3 + (b_hi - b_hi_29) / 2^29, (b_lo - b_lo % 2^6) / 2^6 + b_hi_6 * 2^26) % 2^32 + + W[jj-14] + W[jj-32] + local tmp2 = tmp1 % 2^32 + W[jj-1] = (XOR((a_hi - a_hi_1) / 2^1 + a_lo_1 * 2^31, (a_hi - a_hi_8) / 2^8 + a_lo_8 * 2^24, (a_hi - a_hi_7) / 2^7) + + XOR((b_hi - b_hi_19) / 2^19 + b_lo_19 * 2^13, b_hi_29 * 2^3 + (b_lo - b_lo_29) / 2^29, (b_hi - b_hi_6) / 2^6) + + W[jj-15] + W[jj-33] + (tmp1 - tmp2) / 2^32) % 2^32 + W[jj] = tmp2 + end + local a_lo, b_lo, c_lo, d_lo, e_lo, f_lo, g_lo, h_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + local a_hi, b_hi, c_hi, d_hi, e_hi, f_hi, g_hi, h_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + for j = 1, 80 do + local jj = 2*j + local e_lo_9, e_lo_14, e_lo_18, e_hi_9, e_hi_14, e_hi_18 = e_lo % 2^9, e_lo % 2^14, e_lo % 2^18, e_hi % 2^9, e_hi % 2^14, e_hi % 2^18 + local tmp1 = (AND(e_lo, f_lo) + AND(-1-e_lo, g_lo)) % 2^32 + h_lo + K_lo[j] + W[jj] + + XOR((e_lo - e_lo_14) / 2^14 + e_hi_14 * 2^18, (e_lo - e_lo_18) / 2^18 + e_hi_18 * 2^14, e_lo_9 * 2^23 + (e_hi - e_hi_9) / 2^9) % 2^32 + local z_lo = tmp1 % 2^32 + local z_hi = AND(e_hi, f_hi) + AND(-1-e_hi, g_hi) + h_hi + K_hi[j] + W[jj-1] + (tmp1 - z_lo) / 2^32 + + XOR((e_hi - e_hi_14) / 2^14 + e_lo_14 * 2^18, (e_hi - e_hi_18) / 2^18 + e_lo_18 * 2^14, e_hi_9 * 2^23 + (e_lo - e_lo_9) / 2^9) + h_lo = g_lo; h_hi = g_hi + g_lo = f_lo; g_hi = f_hi + f_lo = e_lo; f_hi = e_hi + tmp1 = z_lo + d_lo + e_lo = tmp1 % 2^32 + e_hi = (z_hi + d_hi + (tmp1 - e_lo) / 2^32) % 2^32 + d_lo = c_lo; d_hi = c_hi + c_lo = b_lo; c_hi = b_hi + b_lo = a_lo; b_hi = a_hi + local b_lo_2, b_lo_7, b_lo_28, b_hi_2, b_hi_7, b_hi_28 = b_lo % 2^2, b_lo % 2^7, b_lo % 2^28, b_hi % 2^2, b_hi % 2^7, b_hi % 2^28 + tmp1 = z_lo + (AND(d_lo, c_lo) + AND(b_lo, XOR(d_lo, c_lo))) % 2^32 + + XOR((b_lo - b_lo_28) / 2^28 + b_hi_28 * 2^4, b_lo_2 * 2^30 + (b_hi - b_hi_2) / 2^2, b_lo_7 * 2^25 + (b_hi - b_hi_7) / 2^7) % 2^32 + a_lo = tmp1 % 2^32 + a_hi = (z_hi + AND(d_hi, c_hi) + AND(b_hi, XOR(d_hi, c_hi)) + (tmp1 - a_lo) / 2^32 + + XOR((b_hi - b_hi_28) / 2^28 + b_lo_28 * 2^4, b_hi_2 * 2^30 + (b_lo - b_lo_2) / 2^2, b_hi_7 * 2^25 + (b_lo - b_lo_7) / 2^7)) % 2^32 + end + a_lo = h1_lo + a_lo + h1_lo = a_lo % 2^32 + h1_hi = (h1_hi + a_hi + (a_lo - h1_lo) / 2^32) % 2^32 + a_lo = h2_lo + b_lo + h2_lo = a_lo % 2^32 + h2_hi = (h2_hi + b_hi + (a_lo - h2_lo) / 2^32) % 2^32 + a_lo = h3_lo + c_lo + h3_lo = a_lo % 2^32 + h3_hi = (h3_hi + c_hi + (a_lo - h3_lo) / 2^32) % 2^32 + a_lo = h4_lo + d_lo + h4_lo = a_lo % 2^32 + h4_hi = (h4_hi + d_hi + (a_lo - h4_lo) / 2^32) % 2^32 + a_lo = h5_lo + e_lo + h5_lo = a_lo % 2^32 + h5_hi = (h5_hi + e_hi + (a_lo - h5_lo) / 2^32) % 2^32 + a_lo = h6_lo + f_lo + h6_lo = a_lo % 2^32 + h6_hi = (h6_hi + f_hi + (a_lo - h6_lo) / 2^32) % 2^32 + a_lo = h7_lo + g_lo + h7_lo = a_lo % 2^32 + h7_hi = (h7_hi + g_hi + (a_lo - h7_lo) / 2^32) % 2^32 + a_lo = h8_lo + h_lo + h8_lo = a_lo % 2^32 + h8_hi = (h8_hi + h_hi + (a_lo - h8_lo) / 2^32) % 2^32 + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + end + + + if branch == "LIB32" then + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + local a, b, c, d = h1, h2, h3, h4 + local s = 25 + for j = 1, 16 do + local F = ROR(AND(b, c) + AND(-1-b, d) + a + K[j] + W[j], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + s = 27 + for j = 17, 32 do + local F = ROR(AND(d, b) + AND(-1-d, c) + a + K[j] + W[(5*j-4) % 16 + 1], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + s = 28 + for j = 33, 48 do + local F = ROR(XOR(XOR(b, c), d) + a + K[j] + W[(3*j+2) % 16 + 1], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + s = 26 + for j = 49, 64 do + local F = ROR(XOR(c, OR(b, -1-d)) + a + K[j] + W[(j*7-7) % 16 + 1], s) + b + s = md5_next_shift[s] + a = d + d = c + c = b + b = F + end + h1 = (a + h1) % 2^32 + h2 = (b + h2) % 2^32 + h3 = (c + h3) % 2^32 + h4 = (d + h4) % 2^32 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + elseif branch == "EMUL" then + + function md5_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W, K, md5_next_shift = common_W, md5_K, md5_next_shift + local h1, h2, h3, h4 = H[1], H[2], H[3], H[4] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + local a, b, c, d = h1, h2, h3, h4 + local s = 25 + for j = 1, 16 do + local z = (AND(b, c) + AND(-1-b, d) + a + K[j] + W[j]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + s = 27 + for j = 17, 32 do + local z = (AND(d, b) + AND(-1-d, c) + a + K[j] + W[(5*j-4) % 16 + 1]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + s = 28 + for j = 33, 48 do + local z = (XOR(XOR(b, c), d) + a + K[j] + W[(3*j+2) % 16 + 1]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + s = 26 + for j = 49, 64 do + local z = (XOR(c, OR(b, -1-d)) + a + K[j] + W[(j*7-7) % 16 + 1]) % 2^32 / 2^s + local y = z % 1 + s = md5_next_shift[s] + a = d + d = c + c = b + b = y * 2^32 + (z - y) + b + end + h1 = (a + h1) % 2^32 + h2 = (b + h2) % 2^32 + h3 = (c + h3) % 2^32 + h4 = (d + h4) % 2^32 + end + H[1], H[2], H[3], H[4] = h1, h2, h3, h4 + end + + end + + + function sha1_feed_64(H, str, offs, size) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5 = H[1], H[2], H[3], H[4], H[5] + for pos = offs, offs + size - 1, 64 do + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((a * 256 + b) * 256 + c) * 256 + d + end + for j = 17, 80 do + local a = XOR(W[j-3], W[j-8], W[j-14], W[j-16]) % 2^32 * 2 + local b = a % 2^32 + W[j] = b + (a - b) / 2^32 + end + local a, b, c, d, e = h1, h2, h3, h4, h5 + for j = 1, 20 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + AND(b, c) + AND(-1-b, d) + 0x5A827999 + W[j] + e -- constant = floor(2^30 * sqrt(2)) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + for j = 21, 40 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + XOR(b, c, d) + 0x6ED9EBA1 + W[j] + e -- 2^30 * sqrt(3) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + for j = 41, 60 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + AND(d, c) + AND(b, XOR(d, c)) + 0x8F1BBCDC + W[j] + e -- 2^30 * sqrt(5) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + for j = 61, 80 do + local a5 = a * 2^5 + local z = a5 % 2^32 + z = z + (a5 - z) / 2^32 + XOR(b, c, d) + 0xCA62C1D6 + W[j] + e -- 2^30 * sqrt(10) + e = d + d = c + c = b / 2^2 + c = c % 1 * (2^32 - 1) + c + b = a + a = z % 2^32 + end + h1 = (a + h1) % 2^32 + h2 = (b + h2) % 2^32 + h3 = (c + h3) % 2^32 + h4 = (d + h4) % 2^32 + h5 = (e + h5) % 2^32 + end + H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5 + end + + + function keccak_feed(lanes_lo, lanes_hi, str, offs, size, block_size_in_bytes) + -- This is an example of a Lua function having 79 local variables :-) + -- offs >= 0, size >= 0, size is multiple of block_size_in_bytes, block_size_in_bytes is positive multiple of 8 + local RC_lo, RC_hi = sha3_RC_lo, sha3_RC_hi + local qwords_qty = block_size_in_bytes / 8 + for pos = offs, offs + size - 1, block_size_in_bytes do + for j = 1, qwords_qty do + local a, b, c, d = byte(str, pos + 1, pos + 4) + lanes_lo[j] = XOR(lanes_lo[j], ((d * 256 + c) * 256 + b) * 256 + a) + pos = pos + 8 + a, b, c, d = byte(str, pos - 3, pos) + lanes_hi[j] = XOR(lanes_hi[j], ((d * 256 + c) * 256 + b) * 256 + a) + end + local L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi, + L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi, + L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi = + lanes_lo[1], lanes_hi[1], lanes_lo[2], lanes_hi[2], lanes_lo[3], lanes_hi[3], lanes_lo[4], lanes_hi[4], lanes_lo[5], lanes_hi[5], + lanes_lo[6], lanes_hi[6], lanes_lo[7], lanes_hi[7], lanes_lo[8], lanes_hi[8], lanes_lo[9], lanes_hi[9], lanes_lo[10], lanes_hi[10], + lanes_lo[11], lanes_hi[11], lanes_lo[12], lanes_hi[12], lanes_lo[13], lanes_hi[13], lanes_lo[14], lanes_hi[14], lanes_lo[15], lanes_hi[15], + lanes_lo[16], lanes_hi[16], lanes_lo[17], lanes_hi[17], lanes_lo[18], lanes_hi[18], lanes_lo[19], lanes_hi[19], lanes_lo[20], lanes_hi[20], + lanes_lo[21], lanes_hi[21], lanes_lo[22], lanes_hi[22], lanes_lo[23], lanes_hi[23], lanes_lo[24], lanes_hi[24], lanes_lo[25], lanes_hi[25] + for round_idx = 1, 24 do + local C1_lo = XOR(L01_lo, L06_lo, L11_lo, L16_lo, L21_lo) + local C1_hi = XOR(L01_hi, L06_hi, L11_hi, L16_hi, L21_hi) + local C2_lo = XOR(L02_lo, L07_lo, L12_lo, L17_lo, L22_lo) + local C2_hi = XOR(L02_hi, L07_hi, L12_hi, L17_hi, L22_hi) + local C3_lo = XOR(L03_lo, L08_lo, L13_lo, L18_lo, L23_lo) + local C3_hi = XOR(L03_hi, L08_hi, L13_hi, L18_hi, L23_hi) + local C4_lo = XOR(L04_lo, L09_lo, L14_lo, L19_lo, L24_lo) + local C4_hi = XOR(L04_hi, L09_hi, L14_hi, L19_hi, L24_hi) + local C5_lo = XOR(L05_lo, L10_lo, L15_lo, L20_lo, L25_lo) + local C5_hi = XOR(L05_hi, L10_hi, L15_hi, L20_hi, L25_hi) + local D_lo = XOR(C1_lo, C3_lo * 2 + (C3_hi % 2^32 - C3_hi % 2^31) / 2^31) + local D_hi = XOR(C1_hi, C3_hi * 2 + (C3_lo % 2^32 - C3_lo % 2^31) / 2^31) + local T0_lo = XOR(D_lo, L02_lo) + local T0_hi = XOR(D_hi, L02_hi) + local T1_lo = XOR(D_lo, L07_lo) + local T1_hi = XOR(D_hi, L07_hi) + local T2_lo = XOR(D_lo, L12_lo) + local T2_hi = XOR(D_hi, L12_hi) + local T3_lo = XOR(D_lo, L17_lo) + local T3_hi = XOR(D_hi, L17_hi) + local T4_lo = XOR(D_lo, L22_lo) + local T4_hi = XOR(D_hi, L22_hi) + L02_lo = (T1_lo % 2^32 - T1_lo % 2^20) / 2^20 + T1_hi * 2^12 + L02_hi = (T1_hi % 2^32 - T1_hi % 2^20) / 2^20 + T1_lo * 2^12 + L07_lo = (T3_lo % 2^32 - T3_lo % 2^19) / 2^19 + T3_hi * 2^13 + L07_hi = (T3_hi % 2^32 - T3_hi % 2^19) / 2^19 + T3_lo * 2^13 + L12_lo = T0_lo * 2 + (T0_hi % 2^32 - T0_hi % 2^31) / 2^31 + L12_hi = T0_hi * 2 + (T0_lo % 2^32 - T0_lo % 2^31) / 2^31 + L17_lo = T2_lo * 2^10 + (T2_hi % 2^32 - T2_hi % 2^22) / 2^22 + L17_hi = T2_hi * 2^10 + (T2_lo % 2^32 - T2_lo % 2^22) / 2^22 + L22_lo = T4_lo * 2^2 + (T4_hi % 2^32 - T4_hi % 2^30) / 2^30 + L22_hi = T4_hi * 2^2 + (T4_lo % 2^32 - T4_lo % 2^30) / 2^30 + D_lo = XOR(C2_lo, C4_lo * 2 + (C4_hi % 2^32 - C4_hi % 2^31) / 2^31) + D_hi = XOR(C2_hi, C4_hi * 2 + (C4_lo % 2^32 - C4_lo % 2^31) / 2^31) + T0_lo = XOR(D_lo, L03_lo) + T0_hi = XOR(D_hi, L03_hi) + T1_lo = XOR(D_lo, L08_lo) + T1_hi = XOR(D_hi, L08_hi) + T2_lo = XOR(D_lo, L13_lo) + T2_hi = XOR(D_hi, L13_hi) + T3_lo = XOR(D_lo, L18_lo) + T3_hi = XOR(D_hi, L18_hi) + T4_lo = XOR(D_lo, L23_lo) + T4_hi = XOR(D_hi, L23_hi) + L03_lo = (T2_lo % 2^32 - T2_lo % 2^21) / 2^21 + T2_hi * 2^11 + L03_hi = (T2_hi % 2^32 - T2_hi % 2^21) / 2^21 + T2_lo * 2^11 + L08_lo = (T4_lo % 2^32 - T4_lo % 2^3) / 2^3 + T4_hi * 2^29 % 2^32 + L08_hi = (T4_hi % 2^32 - T4_hi % 2^3) / 2^3 + T4_lo * 2^29 % 2^32 + L13_lo = T1_lo * 2^6 + (T1_hi % 2^32 - T1_hi % 2^26) / 2^26 + L13_hi = T1_hi * 2^6 + (T1_lo % 2^32 - T1_lo % 2^26) / 2^26 + L18_lo = T3_lo * 2^15 + (T3_hi % 2^32 - T3_hi % 2^17) / 2^17 + L18_hi = T3_hi * 2^15 + (T3_lo % 2^32 - T3_lo % 2^17) / 2^17 + L23_lo = (T0_lo % 2^32 - T0_lo % 2^2) / 2^2 + T0_hi * 2^30 % 2^32 + L23_hi = (T0_hi % 2^32 - T0_hi % 2^2) / 2^2 + T0_lo * 2^30 % 2^32 + D_lo = XOR(C3_lo, C5_lo * 2 + (C5_hi % 2^32 - C5_hi % 2^31) / 2^31) + D_hi = XOR(C3_hi, C5_hi * 2 + (C5_lo % 2^32 - C5_lo % 2^31) / 2^31) + T0_lo = XOR(D_lo, L04_lo) + T0_hi = XOR(D_hi, L04_hi) + T1_lo = XOR(D_lo, L09_lo) + T1_hi = XOR(D_hi, L09_hi) + T2_lo = XOR(D_lo, L14_lo) + T2_hi = XOR(D_hi, L14_hi) + T3_lo = XOR(D_lo, L19_lo) + T3_hi = XOR(D_hi, L19_hi) + T4_lo = XOR(D_lo, L24_lo) + T4_hi = XOR(D_hi, L24_hi) + L04_lo = T3_lo * 2^21 % 2^32 + (T3_hi % 2^32 - T3_hi % 2^11) / 2^11 + L04_hi = T3_hi * 2^21 % 2^32 + (T3_lo % 2^32 - T3_lo % 2^11) / 2^11 + L09_lo = T0_lo * 2^28 % 2^32 + (T0_hi % 2^32 - T0_hi % 2^4) / 2^4 + L09_hi = T0_hi * 2^28 % 2^32 + (T0_lo % 2^32 - T0_lo % 2^4) / 2^4 + L14_lo = T2_lo * 2^25 % 2^32 + (T2_hi % 2^32 - T2_hi % 2^7) / 2^7 + L14_hi = T2_hi * 2^25 % 2^32 + (T2_lo % 2^32 - T2_lo % 2^7) / 2^7 + L19_lo = (T4_lo % 2^32 - T4_lo % 2^8) / 2^8 + T4_hi * 2^24 % 2^32 + L19_hi = (T4_hi % 2^32 - T4_hi % 2^8) / 2^8 + T4_lo * 2^24 % 2^32 + L24_lo = (T1_lo % 2^32 - T1_lo % 2^9) / 2^9 + T1_hi * 2^23 % 2^32 + L24_hi = (T1_hi % 2^32 - T1_hi % 2^9) / 2^9 + T1_lo * 2^23 % 2^32 + D_lo = XOR(C4_lo, C1_lo * 2 + (C1_hi % 2^32 - C1_hi % 2^31) / 2^31) + D_hi = XOR(C4_hi, C1_hi * 2 + (C1_lo % 2^32 - C1_lo % 2^31) / 2^31) + T0_lo = XOR(D_lo, L05_lo) + T0_hi = XOR(D_hi, L05_hi) + T1_lo = XOR(D_lo, L10_lo) + T1_hi = XOR(D_hi, L10_hi) + T2_lo = XOR(D_lo, L15_lo) + T2_hi = XOR(D_hi, L15_hi) + T3_lo = XOR(D_lo, L20_lo) + T3_hi = XOR(D_hi, L20_hi) + T4_lo = XOR(D_lo, L25_lo) + T4_hi = XOR(D_hi, L25_hi) + L05_lo = T4_lo * 2^14 + (T4_hi % 2^32 - T4_hi % 2^18) / 2^18 + L05_hi = T4_hi * 2^14 + (T4_lo % 2^32 - T4_lo % 2^18) / 2^18 + L10_lo = T1_lo * 2^20 % 2^32 + (T1_hi % 2^32 - T1_hi % 2^12) / 2^12 + L10_hi = T1_hi * 2^20 % 2^32 + (T1_lo % 2^32 - T1_lo % 2^12) / 2^12 + L15_lo = T3_lo * 2^8 + (T3_hi % 2^32 - T3_hi % 2^24) / 2^24 + L15_hi = T3_hi * 2^8 + (T3_lo % 2^32 - T3_lo % 2^24) / 2^24 + L20_lo = T0_lo * 2^27 % 2^32 + (T0_hi % 2^32 - T0_hi % 2^5) / 2^5 + L20_hi = T0_hi * 2^27 % 2^32 + (T0_lo % 2^32 - T0_lo % 2^5) / 2^5 + L25_lo = (T2_lo % 2^32 - T2_lo % 2^25) / 2^25 + T2_hi * 2^7 + L25_hi = (T2_hi % 2^32 - T2_hi % 2^25) / 2^25 + T2_lo * 2^7 + D_lo = XOR(C5_lo, C2_lo * 2 + (C2_hi % 2^32 - C2_hi % 2^31) / 2^31) + D_hi = XOR(C5_hi, C2_hi * 2 + (C2_lo % 2^32 - C2_lo % 2^31) / 2^31) + T1_lo = XOR(D_lo, L06_lo) + T1_hi = XOR(D_hi, L06_hi) + T2_lo = XOR(D_lo, L11_lo) + T2_hi = XOR(D_hi, L11_hi) + T3_lo = XOR(D_lo, L16_lo) + T3_hi = XOR(D_hi, L16_hi) + T4_lo = XOR(D_lo, L21_lo) + T4_hi = XOR(D_hi, L21_hi) + L06_lo = T2_lo * 2^3 + (T2_hi % 2^32 - T2_hi % 2^29) / 2^29 + L06_hi = T2_hi * 2^3 + (T2_lo % 2^32 - T2_lo % 2^29) / 2^29 + L11_lo = T4_lo * 2^18 + (T4_hi % 2^32 - T4_hi % 2^14) / 2^14 + L11_hi = T4_hi * 2^18 + (T4_lo % 2^32 - T4_lo % 2^14) / 2^14 + L16_lo = (T1_lo % 2^32 - T1_lo % 2^28) / 2^28 + T1_hi * 2^4 + L16_hi = (T1_hi % 2^32 - T1_hi % 2^28) / 2^28 + T1_lo * 2^4 + L21_lo = (T3_lo % 2^32 - T3_lo % 2^23) / 2^23 + T3_hi * 2^9 + L21_hi = (T3_hi % 2^32 - T3_hi % 2^23) / 2^23 + T3_lo * 2^9 + L01_lo = XOR(D_lo, L01_lo) + L01_hi = XOR(D_hi, L01_hi) + L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = XOR(L01_lo, AND(-1-L02_lo, L03_lo)), XOR(L02_lo, AND(-1-L03_lo, L04_lo)), XOR(L03_lo, AND(-1-L04_lo, L05_lo)), XOR(L04_lo, AND(-1-L05_lo, L01_lo)), XOR(L05_lo, AND(-1-L01_lo, L02_lo)) + L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = XOR(L01_hi, AND(-1-L02_hi, L03_hi)), XOR(L02_hi, AND(-1-L03_hi, L04_hi)), XOR(L03_hi, AND(-1-L04_hi, L05_hi)), XOR(L04_hi, AND(-1-L05_hi, L01_hi)), XOR(L05_hi, AND(-1-L01_hi, L02_hi)) + L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = XOR(L09_lo, AND(-1-L10_lo, L06_lo)), XOR(L10_lo, AND(-1-L06_lo, L07_lo)), XOR(L06_lo, AND(-1-L07_lo, L08_lo)), XOR(L07_lo, AND(-1-L08_lo, L09_lo)), XOR(L08_lo, AND(-1-L09_lo, L10_lo)) + L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = XOR(L09_hi, AND(-1-L10_hi, L06_hi)), XOR(L10_hi, AND(-1-L06_hi, L07_hi)), XOR(L06_hi, AND(-1-L07_hi, L08_hi)), XOR(L07_hi, AND(-1-L08_hi, L09_hi)), XOR(L08_hi, AND(-1-L09_hi, L10_hi)) + L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = XOR(L12_lo, AND(-1-L13_lo, L14_lo)), XOR(L13_lo, AND(-1-L14_lo, L15_lo)), XOR(L14_lo, AND(-1-L15_lo, L11_lo)), XOR(L15_lo, AND(-1-L11_lo, L12_lo)), XOR(L11_lo, AND(-1-L12_lo, L13_lo)) + L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = XOR(L12_hi, AND(-1-L13_hi, L14_hi)), XOR(L13_hi, AND(-1-L14_hi, L15_hi)), XOR(L14_hi, AND(-1-L15_hi, L11_hi)), XOR(L15_hi, AND(-1-L11_hi, L12_hi)), XOR(L11_hi, AND(-1-L12_hi, L13_hi)) + L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = XOR(L20_lo, AND(-1-L16_lo, L17_lo)), XOR(L16_lo, AND(-1-L17_lo, L18_lo)), XOR(L17_lo, AND(-1-L18_lo, L19_lo)), XOR(L18_lo, AND(-1-L19_lo, L20_lo)), XOR(L19_lo, AND(-1-L20_lo, L16_lo)) + L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = XOR(L20_hi, AND(-1-L16_hi, L17_hi)), XOR(L16_hi, AND(-1-L17_hi, L18_hi)), XOR(L17_hi, AND(-1-L18_hi, L19_hi)), XOR(L18_hi, AND(-1-L19_hi, L20_hi)), XOR(L19_hi, AND(-1-L20_hi, L16_hi)) + L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = XOR(L23_lo, AND(-1-L24_lo, L25_lo)), XOR(L24_lo, AND(-1-L25_lo, L21_lo)), XOR(L25_lo, AND(-1-L21_lo, L22_lo)), XOR(L21_lo, AND(-1-L22_lo, L23_lo)), XOR(L22_lo, AND(-1-L23_lo, L24_lo)) + L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = XOR(L23_hi, AND(-1-L24_hi, L25_hi)), XOR(L24_hi, AND(-1-L25_hi, L21_hi)), XOR(L25_hi, AND(-1-L21_hi, L22_hi)), XOR(L21_hi, AND(-1-L22_hi, L23_hi)), XOR(L22_hi, AND(-1-L23_hi, L24_hi)) + L01_lo = XOR(L01_lo, RC_lo[round_idx]) + L01_hi = L01_hi + RC_hi[round_idx] -- RC_hi[] is either 0 or 0x80000000, so we could use fast addition instead of slow XOR + end + lanes_lo[1] = L01_lo; lanes_hi[1] = L01_hi + lanes_lo[2] = L02_lo; lanes_hi[2] = L02_hi + lanes_lo[3] = L03_lo; lanes_hi[3] = L03_hi + lanes_lo[4] = L04_lo; lanes_hi[4] = L04_hi + lanes_lo[5] = L05_lo; lanes_hi[5] = L05_hi + lanes_lo[6] = L06_lo; lanes_hi[6] = L06_hi + lanes_lo[7] = L07_lo; lanes_hi[7] = L07_hi + lanes_lo[8] = L08_lo; lanes_hi[8] = L08_hi + lanes_lo[9] = L09_lo; lanes_hi[9] = L09_hi + lanes_lo[10] = L10_lo; lanes_hi[10] = L10_hi + lanes_lo[11] = L11_lo; lanes_hi[11] = L11_hi + lanes_lo[12] = L12_lo; lanes_hi[12] = L12_hi + lanes_lo[13] = L13_lo; lanes_hi[13] = L13_hi + lanes_lo[14] = L14_lo; lanes_hi[14] = L14_hi + lanes_lo[15] = L15_lo; lanes_hi[15] = L15_hi + lanes_lo[16] = L16_lo; lanes_hi[16] = L16_hi + lanes_lo[17] = L17_lo; lanes_hi[17] = L17_hi + lanes_lo[18] = L18_lo; lanes_hi[18] = L18_hi + lanes_lo[19] = L19_lo; lanes_hi[19] = L19_hi + lanes_lo[20] = L20_lo; lanes_hi[20] = L20_hi + lanes_lo[21] = L21_lo; lanes_hi[21] = L21_hi + lanes_lo[22] = L22_lo; lanes_hi[22] = L22_hi + lanes_lo[23] = L23_lo; lanes_hi[23] = L23_hi + lanes_lo[24] = L24_lo; lanes_hi[24] = L24_hi + lanes_lo[25] = L25_lo; lanes_hi[25] = L25_hi + end + end + + + function blake2s_feed_64(H, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + local v0, v1, v2, v3, v4, v5, v6, v7 = h1, h2, h3, h4, h5, h6, h7, h8 + local v8, v9, vA, vB, vC, vD, vE, vF = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8] + bytes_compressed = bytes_compressed + (last_block_size or 64) + local t0 = bytes_compressed % 2^32 + local t1 = (bytes_compressed - t0) / 2^32 + vC = XOR(vC, t0) -- t0 = low_4_bytes(bytes_compressed) + vD = XOR(vD, t1) -- t1 = high_4_bytes(bytes_compressed) + if last_block_size then -- flag f0 + vE = -1 - vE + end + if is_last_node then -- flag f1 + vF = -1 - vF + end + for j = 1, 10 do + local row = sigma[j] + v0 = v0 + v4 + W[row[1]] + vC = XOR(vC, v0) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v0 = v0 + v4 + W[row[2]] + vC = XOR(vC, v0) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + v1 = v1 + v5 + W[row[3]] + vD = XOR(vD, v1) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v5 + W[row[4]] + vD = XOR(vD, v1) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v2 = v2 + v6 + W[row[5]] + vE = XOR(vE, v2) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v6 + W[row[6]] + vE = XOR(vE, v2) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v3 = v3 + v7 + W[row[7]] + vF = XOR(vF, v3) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v7 + W[row[8]] + vF = XOR(vF, v3) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v0 = v0 + v5 + W[row[9]] + vF = XOR(vF, v0) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v0 = v0 + v5 + W[row[10]] + vF = XOR(vF, v0) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v6 + W[row[11]] + vC = XOR(vC, v1) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v1 = v1 + v6 + W[row[12]] + vC = XOR(vC, v1) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v7 + W[row[13]] + vD = XOR(vD, v2) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v2 = v2 + v7 + W[row[14]] + vD = XOR(vD, v2) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v4 + W[row[15]] + vE = XOR(vE, v3) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v3 = v3 + v4 + W[row[16]] + vE = XOR(vE, v3) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + end + h1 = XOR(h1, v0, v8) + h2 = XOR(h2, v1, v9) + h3 = XOR(h3, v2, vA) + h4 = XOR(h4, v3, vB) + h5 = XOR(h5, v4, vC) + h6 = XOR(h6, v5, vD) + h7 = XOR(h7, v6, vE) + h8 = XOR(h8, v7, vF) + end + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] = h1, h2, h3, h4, h5, h6, h7, h8 + return bytes_compressed + end + + + function blake2b_feed_128(H_lo, H_hi, str, offs, size, bytes_compressed, last_block_size, is_last_node) + -- offs >= 0, size >= 0, size is multiple of 128 + local W = common_W + local h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo = H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] + local h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi = H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] + for pos = offs, offs + size - 1, 128 do + if str then + for j = 1, 32 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + local v0_lo, v1_lo, v2_lo, v3_lo, v4_lo, v5_lo, v6_lo, v7_lo = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + local v0_hi, v1_hi, v2_hi, v3_hi, v4_hi, v5_hi, v6_hi, v7_hi = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + local v8_lo, v9_lo, vA_lo, vB_lo, vC_lo, vD_lo, vE_lo, vF_lo = sha2_H_lo[1], sha2_H_lo[2], sha2_H_lo[3], sha2_H_lo[4], sha2_H_lo[5], sha2_H_lo[6], sha2_H_lo[7], sha2_H_lo[8] + local v8_hi, v9_hi, vA_hi, vB_hi, vC_hi, vD_hi, vE_hi, vF_hi = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4], sha2_H_hi[5], sha2_H_hi[6], sha2_H_hi[7], sha2_H_hi[8] + bytes_compressed = bytes_compressed + (last_block_size or 128) + local t0_lo = bytes_compressed % 2^32 + local t0_hi = (bytes_compressed - t0_lo) / 2^32 + vC_lo = XOR(vC_lo, t0_lo) -- t0 = low_8_bytes(bytes_compressed) + vC_hi = XOR(vC_hi, t0_hi) + -- t1 = high_8_bytes(bytes_compressed) = 0, message length is always below 2^53 bytes + if last_block_size then -- flag f0 + vE_lo = -1 - vE_lo + vE_hi = -1 - vE_hi + end + if is_last_node then -- flag f1 + vF_lo = -1 - vF_lo + vF_hi = -1 - vF_hi + end + for j = 1, 12 do + local row = sigma[j] + local k = row[1] * 2 + local z = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v4_hi + (z - v0_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_hi, v0_hi), XOR(vC_lo, v0_lo) + z = v8_lo % 2^32 + vC_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vC_hi + (z - v8_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v8_lo), XOR(v4_hi, v8_hi) + local z_lo, z_hi = v4_lo % 2^24, v4_hi % 2^24 + v4_lo, v4_hi = (v4_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v4_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[2] * 2 + z = v0_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v4_hi + (z - v0_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_lo, v0_lo), XOR(vC_hi, v0_hi) + z_lo, z_hi = vC_lo % 2^16, vC_hi % 2^16 + vC_lo, vC_hi = (vC_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vC_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v8_lo % 2^32 + vC_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vC_hi + (z - v8_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v8_lo), XOR(v4_hi, v8_hi) + z_lo, z_hi = v4_lo % 2^31, v4_hi % 2^31 + v4_lo, v4_hi = z_lo * 2^1 + (v4_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v4_lo - z_lo) / 2^31 % 2^1 + k = row[3] * 2 + z = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v5_hi + (z - v1_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_hi, v1_hi), XOR(vD_lo, v1_lo) + z = v9_lo % 2^32 + vD_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vD_hi + (z - v9_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, v9_lo), XOR(v5_hi, v9_hi) + z_lo, z_hi = v5_lo % 2^24, v5_hi % 2^24 + v5_lo, v5_hi = (v5_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v5_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[4] * 2 + z = v1_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v5_hi + (z - v1_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_lo, v1_lo), XOR(vD_hi, v1_hi) + z_lo, z_hi = vD_lo % 2^16, vD_hi % 2^16 + vD_lo, vD_hi = (vD_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vD_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v9_lo % 2^32 + vD_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vD_hi + (z - v9_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, v9_lo), XOR(v5_hi, v9_hi) + z_lo, z_hi = v5_lo % 2^31, v5_hi % 2^31 + v5_lo, v5_hi = z_lo * 2^1 + (v5_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v5_lo - z_lo) / 2^31 % 2^1 + k = row[5] * 2 + z = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v6_hi + (z - v2_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_hi, v2_hi), XOR(vE_lo, v2_lo) + z = vA_lo % 2^32 + vE_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vE_hi + (z - vA_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vA_lo), XOR(v6_hi, vA_hi) + z_lo, z_hi = v6_lo % 2^24, v6_hi % 2^24 + v6_lo, v6_hi = (v6_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v6_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[6] * 2 + z = v2_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v6_hi + (z - v2_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_lo, v2_lo), XOR(vE_hi, v2_hi) + z_lo, z_hi = vE_lo % 2^16, vE_hi % 2^16 + vE_lo, vE_hi = (vE_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vE_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vA_lo % 2^32 + vE_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vE_hi + (z - vA_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vA_lo), XOR(v6_hi, vA_hi) + z_lo, z_hi = v6_lo % 2^31, v6_hi % 2^31 + v6_lo, v6_hi = z_lo * 2^1 + (v6_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v6_lo - z_lo) / 2^31 % 2^1 + k = row[7] * 2 + z = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v7_hi + (z - v3_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_hi, v3_hi), XOR(vF_lo, v3_lo) + z = vB_lo % 2^32 + vF_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vF_hi + (z - vB_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, vB_lo), XOR(v7_hi, vB_hi) + z_lo, z_hi = v7_lo % 2^24, v7_hi % 2^24 + v7_lo, v7_hi = (v7_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v7_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[8] * 2 + z = v3_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v7_hi + (z - v3_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_lo, v3_lo), XOR(vF_hi, v3_hi) + z_lo, z_hi = vF_lo % 2^16, vF_hi % 2^16 + vF_lo, vF_hi = (vF_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vF_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vB_lo % 2^32 + vF_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vF_hi + (z - vB_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, vB_lo), XOR(v7_hi, vB_hi) + z_lo, z_hi = v7_lo % 2^31, v7_hi % 2^31 + v7_lo, v7_hi = z_lo * 2^1 + (v7_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v7_lo - z_lo) / 2^31 % 2^1 + k = row[9] * 2 + z = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v5_hi + (z - v0_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_hi, v0_hi), XOR(vF_lo, v0_lo) + z = vA_lo % 2^32 + vF_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vF_hi + (z - vA_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, vA_lo), XOR(v5_hi, vA_hi) + z_lo, z_hi = v5_lo % 2^24, v5_hi % 2^24 + v5_lo, v5_hi = (v5_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v5_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[10] * 2 + z = v0_lo % 2^32 + v5_lo % 2^32 + W[k-1] + v0_lo = z % 2^32 + v0_hi = v0_hi + v5_hi + (z - v0_lo) / 2^32 + W[k] + vF_lo, vF_hi = XOR(vF_lo, v0_lo), XOR(vF_hi, v0_hi) + z_lo, z_hi = vF_lo % 2^16, vF_hi % 2^16 + vF_lo, vF_hi = (vF_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vF_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vA_lo % 2^32 + vF_lo % 2^32 + vA_lo = z % 2^32 + vA_hi = vA_hi + vF_hi + (z - vA_lo) / 2^32 + v5_lo, v5_hi = XOR(v5_lo, vA_lo), XOR(v5_hi, vA_hi) + z_lo, z_hi = v5_lo % 2^31, v5_hi % 2^31 + v5_lo, v5_hi = z_lo * 2^1 + (v5_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v5_lo - z_lo) / 2^31 % 2^1 + k = row[11] * 2 + z = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v6_hi + (z - v1_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_hi, v1_hi), XOR(vC_lo, v1_lo) + z = vB_lo % 2^32 + vC_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vC_hi + (z - vB_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vB_lo), XOR(v6_hi, vB_hi) + z_lo, z_hi = v6_lo % 2^24, v6_hi % 2^24 + v6_lo, v6_hi = (v6_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v6_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[12] * 2 + z = v1_lo % 2^32 + v6_lo % 2^32 + W[k-1] + v1_lo = z % 2^32 + v1_hi = v1_hi + v6_hi + (z - v1_lo) / 2^32 + W[k] + vC_lo, vC_hi = XOR(vC_lo, v1_lo), XOR(vC_hi, v1_hi) + z_lo, z_hi = vC_lo % 2^16, vC_hi % 2^16 + vC_lo, vC_hi = (vC_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vC_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = vB_lo % 2^32 + vC_lo % 2^32 + vB_lo = z % 2^32 + vB_hi = vB_hi + vC_hi + (z - vB_lo) / 2^32 + v6_lo, v6_hi = XOR(v6_lo, vB_lo), XOR(v6_hi, vB_hi) + z_lo, z_hi = v6_lo % 2^31, v6_hi % 2^31 + v6_lo, v6_hi = z_lo * 2^1 + (v6_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v6_lo - z_lo) / 2^31 % 2^1 + k = row[13] * 2 + z = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v7_hi + (z - v2_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_hi, v2_hi), XOR(vD_lo, v2_lo) + z = v8_lo % 2^32 + vD_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vD_hi + (z - v8_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, v8_lo), XOR(v7_hi, v8_hi) + z_lo, z_hi = v7_lo % 2^24, v7_hi % 2^24 + v7_lo, v7_hi = (v7_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v7_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[14] * 2 + z = v2_lo % 2^32 + v7_lo % 2^32 + W[k-1] + v2_lo = z % 2^32 + v2_hi = v2_hi + v7_hi + (z - v2_lo) / 2^32 + W[k] + vD_lo, vD_hi = XOR(vD_lo, v2_lo), XOR(vD_hi, v2_hi) + z_lo, z_hi = vD_lo % 2^16, vD_hi % 2^16 + vD_lo, vD_hi = (vD_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vD_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v8_lo % 2^32 + vD_lo % 2^32 + v8_lo = z % 2^32 + v8_hi = v8_hi + vD_hi + (z - v8_lo) / 2^32 + v7_lo, v7_hi = XOR(v7_lo, v8_lo), XOR(v7_hi, v8_hi) + z_lo, z_hi = v7_lo % 2^31, v7_hi % 2^31 + v7_lo, v7_hi = z_lo * 2^1 + (v7_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v7_lo - z_lo) / 2^31 % 2^1 + k = row[15] * 2 + z = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v4_hi + (z - v3_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_hi, v3_hi), XOR(vE_lo, v3_lo) + z = v9_lo % 2^32 + vE_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vE_hi + (z - v9_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v9_lo), XOR(v4_hi, v9_hi) + z_lo, z_hi = v4_lo % 2^24, v4_hi % 2^24 + v4_lo, v4_hi = (v4_lo - z_lo) / 2^24 % 2^8 + z_hi * 2^8, (v4_hi - z_hi) / 2^24 % 2^8 + z_lo * 2^8 + k = row[16] * 2 + z = v3_lo % 2^32 + v4_lo % 2^32 + W[k-1] + v3_lo = z % 2^32 + v3_hi = v3_hi + v4_hi + (z - v3_lo) / 2^32 + W[k] + vE_lo, vE_hi = XOR(vE_lo, v3_lo), XOR(vE_hi, v3_hi) + z_lo, z_hi = vE_lo % 2^16, vE_hi % 2^16 + vE_lo, vE_hi = (vE_lo - z_lo) / 2^16 % 2^16 + z_hi * 2^16, (vE_hi - z_hi) / 2^16 % 2^16 + z_lo * 2^16 + z = v9_lo % 2^32 + vE_lo % 2^32 + v9_lo = z % 2^32 + v9_hi = v9_hi + vE_hi + (z - v9_lo) / 2^32 + v4_lo, v4_hi = XOR(v4_lo, v9_lo), XOR(v4_hi, v9_hi) + z_lo, z_hi = v4_lo % 2^31, v4_hi % 2^31 + v4_lo, v4_hi = z_lo * 2^1 + (v4_hi - z_hi) / 2^31 % 2^1, z_hi * 2^1 + (v4_lo - z_lo) / 2^31 % 2^1 + end + h1_lo = XOR(h1_lo, v0_lo, v8_lo) % 2^32 + h2_lo = XOR(h2_lo, v1_lo, v9_lo) % 2^32 + h3_lo = XOR(h3_lo, v2_lo, vA_lo) % 2^32 + h4_lo = XOR(h4_lo, v3_lo, vB_lo) % 2^32 + h5_lo = XOR(h5_lo, v4_lo, vC_lo) % 2^32 + h6_lo = XOR(h6_lo, v5_lo, vD_lo) % 2^32 + h7_lo = XOR(h7_lo, v6_lo, vE_lo) % 2^32 + h8_lo = XOR(h8_lo, v7_lo, vF_lo) % 2^32 + h1_hi = XOR(h1_hi, v0_hi, v8_hi) % 2^32 + h2_hi = XOR(h2_hi, v1_hi, v9_hi) % 2^32 + h3_hi = XOR(h3_hi, v2_hi, vA_hi) % 2^32 + h4_hi = XOR(h4_hi, v3_hi, vB_hi) % 2^32 + h5_hi = XOR(h5_hi, v4_hi, vC_hi) % 2^32 + h6_hi = XOR(h6_hi, v5_hi, vD_hi) % 2^32 + h7_hi = XOR(h7_hi, v6_hi, vE_hi) % 2^32 + h8_hi = XOR(h8_hi, v7_hi, vF_hi) % 2^32 + end + H_lo[1], H_lo[2], H_lo[3], H_lo[4], H_lo[5], H_lo[6], H_lo[7], H_lo[8] = h1_lo, h2_lo, h3_lo, h4_lo, h5_lo, h6_lo, h7_lo, h8_lo + H_hi[1], H_hi[2], H_hi[3], H_hi[4], H_hi[5], H_hi[6], H_hi[7], H_hi[8] = h1_hi, h2_hi, h3_hi, h4_hi, h5_hi, h6_hi, h7_hi, h8_hi + return bytes_compressed + end + + + function blake3_feed_64(str, offs, size, flags, chunk_index, H_in, H_out, wide_output, block_length) + -- offs >= 0, size >= 0, size is multiple of 64 + block_length = block_length or 64 + local W = common_W + local h1, h2, h3, h4, h5, h6, h7, h8 = H_in[1], H_in[2], H_in[3], H_in[4], H_in[5], H_in[6], H_in[7], H_in[8] + H_out = H_out or H_in + for pos = offs, offs + size - 1, 64 do + if str then + for j = 1, 16 do + pos = pos + 4 + local a, b, c, d = byte(str, pos - 3, pos) + W[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + local v0, v1, v2, v3, v4, v5, v6, v7 = h1, h2, h3, h4, h5, h6, h7, h8 + local v8, v9, vA, vB = sha2_H_hi[1], sha2_H_hi[2], sha2_H_hi[3], sha2_H_hi[4] + local vC = chunk_index % 2^32 -- t0 = low_4_bytes(chunk_index) + local vD = (chunk_index - vC) / 2^32 -- t1 = high_4_bytes(chunk_index) + local vE, vF = block_length, flags + for j = 1, 7 do + v0 = v0 + v4 + W[perm_blake3[j]] + vC = XOR(vC, v0) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v0 = v0 + v4 + W[perm_blake3[j + 14]] + vC = XOR(vC, v0) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + v8 = v8 + vC + v4 = XOR(v4, v8) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + v1 = v1 + v5 + W[perm_blake3[j + 1]] + vD = XOR(vD, v1) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v5 + W[perm_blake3[j + 2]] + vD = XOR(vD, v1) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v9 = v9 + vD + v5 = XOR(v5, v9) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v2 = v2 + v6 + W[perm_blake3[j + 16]] + vE = XOR(vE, v2) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v6 + W[perm_blake3[j + 7]] + vE = XOR(vE, v2) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + vA = vA + vE + v6 = XOR(v6, vA) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v3 = v3 + v7 + W[perm_blake3[j + 15]] + vF = XOR(vF, v3) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v7 + W[perm_blake3[j + 17]] + vF = XOR(vF, v3) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vB = vB + vF + v7 = XOR(v7, vB) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v0 = v0 + v5 + W[perm_blake3[j + 21]] + vF = XOR(vF, v0) % 2^32 / 2^16 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^12 + v5 = v5 % 1 * (2^32 - 1) + v5 + v0 = v0 + v5 + W[perm_blake3[j + 5]] + vF = XOR(vF, v0) % 2^32 / 2^8 + vF = vF % 1 * (2^32 - 1) + vF + vA = vA + vF + v5 = XOR(v5, vA) % 2^32 / 2^7 + v5 = v5 % 1 * (2^32 - 1) + v5 + v1 = v1 + v6 + W[perm_blake3[j + 3]] + vC = XOR(vC, v1) % 2^32 / 2^16 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^12 + v6 = v6 % 1 * (2^32 - 1) + v6 + v1 = v1 + v6 + W[perm_blake3[j + 6]] + vC = XOR(vC, v1) % 2^32 / 2^8 + vC = vC % 1 * (2^32 - 1) + vC + vB = vB + vC + v6 = XOR(v6, vB) % 2^32 / 2^7 + v6 = v6 % 1 * (2^32 - 1) + v6 + v2 = v2 + v7 + W[perm_blake3[j + 4]] + vD = XOR(vD, v2) % 2^32 / 2^16 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^12 + v7 = v7 % 1 * (2^32 - 1) + v7 + v2 = v2 + v7 + W[perm_blake3[j + 18]] + vD = XOR(vD, v2) % 2^32 / 2^8 + vD = vD % 1 * (2^32 - 1) + vD + v8 = v8 + vD + v7 = XOR(v7, v8) % 2^32 / 2^7 + v7 = v7 % 1 * (2^32 - 1) + v7 + v3 = v3 + v4 + W[perm_blake3[j + 19]] + vE = XOR(vE, v3) % 2^32 / 2^16 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^12 + v4 = v4 % 1 * (2^32 - 1) + v4 + v3 = v3 + v4 + W[perm_blake3[j + 20]] + vE = XOR(vE, v3) % 2^32 / 2^8 + vE = vE % 1 * (2^32 - 1) + vE + v9 = v9 + vE + v4 = XOR(v4, v9) % 2^32 / 2^7 + v4 = v4 % 1 * (2^32 - 1) + v4 + end + if wide_output then + H_out[ 9] = XOR(h1, v8) + H_out[10] = XOR(h2, v9) + H_out[11] = XOR(h3, vA) + H_out[12] = XOR(h4, vB) + H_out[13] = XOR(h5, vC) + H_out[14] = XOR(h6, vD) + H_out[15] = XOR(h7, vE) + H_out[16] = XOR(h8, vF) + end + h1 = XOR(v0, v8) + h2 = XOR(v1, v9) + h3 = XOR(v2, vA) + h4 = XOR(v3, vB) + h5 = XOR(v4, vC) + h6 = XOR(v5, vD) + h7 = XOR(v6, vE) + h8 = XOR(v7, vF) + end + H_out[1], H_out[2], H_out[3], H_out[4], H_out[5], H_out[6], H_out[7], H_out[8] = h1, h2, h3, h4, h5, h6, h7, h8 + end + +end + + +-------------------------------------------------------------------------------- +-- MAGIC NUMBERS CALCULATOR +-------------------------------------------------------------------------------- +-- Q: +-- Is 53-bit "double" math enough to calculate square roots and cube roots of primes with 64 correct bits after decimal point? +-- A: +-- Yes, 53-bit "double" arithmetic is enough. +-- We could obtain first 40 bits by direct calculation of p^(1/3) and next 40 bits by one step of Newton's method. + +do + local function mul(src1, src2, factor, result_length) + -- src1, src2 - long integers (arrays of digits in base 2^24) + -- factor - small integer + -- returns long integer result (src1 * src2 * factor) and its floating point approximation + local result, carry, value, weight = {}, 0.0, 0.0, 1.0 + for j = 1, result_length do + for k = math_max(1, j + 1 - #src2), math_min(j, #src1) do + carry = carry + factor * src1[k] * src2[j + 1 - k] -- "int32" is not enough for multiplication result, that's why "factor" must be of type "double" + end + local digit = carry % 2^24 + result[j] = floor(digit) + carry = (carry - digit) / 2^24 + value = value + digit * weight + weight = weight * 2^24 + end + return result, value + end + + local idx, step, p, one, sqrt_hi, sqrt_lo = 0, {4, 1, 2, -2, 2}, 4, {1}, sha2_H_hi, sha2_H_lo + repeat + p = p + step[p % 6] + local d = 1 + repeat + d = d + step[d % 6] + if d*d > p then -- next prime number is found + local root = p^(1/3) + local R = root * 2^40 + R = mul({R - R % 1}, one, 1.0, 2) + local _, delta = mul(R, mul(R, R, 1.0, 4), -1.0, 4) + local hi = R[2] % 65536 * 65536 + floor(R[1] / 256) + local lo = R[1] % 256 * 16777216 + floor(delta * (2^-56 / 3) * root / p) + if idx < 16 then + root = p^(1/2) + R = root * 2^40 + R = mul({R - R % 1}, one, 1.0, 2) + _, delta = mul(R, R, -1.0, 2) + local hi = R[2] % 65536 * 65536 + floor(R[1] / 256) + local lo = R[1] % 256 * 16777216 + floor(delta * 2^-17 / root) + local idx = idx % 8 + 1 + sha2_H_ext256[224][idx] = lo + sqrt_hi[idx], sqrt_lo[idx] = hi, lo + hi * hi_factor + if idx > 7 then + sqrt_hi, sqrt_lo = sha2_H_ext512_hi[384], sha2_H_ext512_lo[384] + end + end + idx = idx + 1 + sha2_K_hi[idx], sha2_K_lo[idx] = hi, lo % K_lo_modulo + hi * hi_factor + break + end + until p % d == 0 + until idx > 79 +end + +-- Calculating IVs for SHA512/224 and SHA512/256 +for width = 224, 256, 32 do + local H_lo, H_hi = {} + if HEX64 then + for j = 1, 8 do + H_lo[j] = XORA5(sha2_H_lo[j]) + end + else + H_hi = {} + for j = 1, 8 do + H_lo[j] = XORA5(sha2_H_lo[j]) + H_hi[j] = XORA5(sha2_H_hi[j]) + end + end + sha512_feed_128(H_lo, H_hi, "SHA-512/"..tostring(width).."\128"..string_rep("\0", 115).."\88", 0, 128) + sha2_H_ext512_lo[width] = H_lo + sha2_H_ext512_hi[width] = H_hi +end + +-- Constants for MD5 +do + local sin, abs, modf = math.sin, math.abs, math.modf + for idx = 1, 64 do + -- we can't use formula floor(abs(sin(idx))*2^32) because its result may be beyond integer range on Lua built with 32-bit integers + local hi, lo = modf(abs(sin(idx)) * 2^16) + md5_K[idx] = hi * 65536 + floor(lo * 2^16) + end +end + +-- Constants for SHA-3 +do + local sh_reg = 29 + + local function next_bit() + local r = sh_reg % 2 + sh_reg = XOR_BYTE((sh_reg - r) / 2, 142 * r) + return r + end + + for idx = 1, 24 do + local lo, m = 0 + for _ = 1, 6 do + m = m and m * m * 2 or 1 + lo = lo + next_bit() * m + end + local hi = next_bit() * m + sha3_RC_hi[idx], sha3_RC_lo[idx] = hi, lo + hi * hi_factor_keccak + end +end + +if branch == "FFI" then + sha2_K_hi = ffi.new("uint32_t[?]", #sha2_K_hi + 1, 0, unpack(sha2_K_hi)) + sha2_K_lo = ffi.new("int64_t[?]", #sha2_K_lo + 1, 0, unpack(sha2_K_lo)) + --md5_K = ffi.new("uint32_t[?]", #md5_K + 1, 0, unpack(md5_K)) + if hi_factor_keccak == 0 then + sha3_RC_lo = ffi.new("uint32_t[?]", #sha3_RC_lo + 1, 0, unpack(sha3_RC_lo)) + sha3_RC_hi = ffi.new("uint32_t[?]", #sha3_RC_hi + 1, 0, unpack(sha3_RC_hi)) + else + sha3_RC_lo = ffi.new("int64_t[?]", #sha3_RC_lo + 1, 0, unpack(sha3_RC_lo)) + end +end + + +-------------------------------------------------------------------------------- +-- MAIN FUNCTIONS +-------------------------------------------------------------------------------- + +local function sha256ext(width, message) + -- Create an instance (private objects for current calculation) + local H, length, tail = {unpack(sha2_H_ext256[width])}, 0.0, "" + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 64 then + offs = 64 - #tail + sha256_feed_64(H, tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 64 + sha256_feed_64(H, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64 + 1)} + tail = nil + -- Assuming user data length is shorter than (2^53)-9 bytes + -- Anyway, it looks very unrealistic that someone would spend more than a year of calculations to process 2^53 bytes of data by using this Lua script :-) + -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes + length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move decimal point to the left + for j = 4, 10 do + length = length % 1 * 256 + final_blocks[j] = char(floor(length)) + end + final_blocks = table_concat(final_blocks) + sha256_feed_64(H, final_blocks, 0, #final_blocks) + local max_reg = width / 32 + for j = 1, max_reg do + H[j] = HEX(H[j]) + end + H = table_concat(H, "", 1, max_reg) + end + return H + end + end + + if message then + -- Actually perform calculations and return the SHA256 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA256 digest by invoking this function without an argument + return partial + end +end + + +local function sha512ext(width, message) + -- Create an instance (private objects for current calculation) + local length, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_ext512_lo[width])}, not HEX64 and {unpack(sha2_H_ext512_hi[width])} + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 128 then + offs = 128 - #tail + sha512_feed_128(H_lo, H_hi, tail..sub(message_part, 1, offs), 0, 128) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 128 + sha512_feed_128(H_lo, H_hi, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-17-length) % 128 + 9)} + tail = nil + -- Assuming user data length is shorter than (2^53)-17 bytes + -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes + length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move floating point to the left + for j = 4, 10 do + length = length % 1 * 256 + final_blocks[j] = char(floor(length)) + end + final_blocks = table_concat(final_blocks) + sha512_feed_128(H_lo, H_hi, final_blocks, 0, #final_blocks) + local max_reg = ceil(width / 64) + if HEX64 then + for j = 1, max_reg do + H_lo[j] = HEX64(H_lo[j]) + end + else + for j = 1, max_reg do + H_lo[j] = HEX(H_hi[j])..HEX(H_lo[j]) + end + H_hi = nil + end + H_lo = sub(table_concat(H_lo, "", 1, max_reg), 1, width / 4) + end + return H_lo + end + end + + if message then + -- Actually perform calculations and return the SHA512 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA512 digest by invoking this function without an argument + return partial + end +end + + +local function md5(message) + -- Create an instance (private objects for current calculation) + local H, length, tail = {unpack(md5_sha1_H, 1, 4)}, 0.0, "" + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 64 then + offs = 64 - #tail + md5_feed_64(H, tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 64 + md5_feed_64(H, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64)} + tail = nil + length = length * 8 -- convert "byte-counter" to "bit-counter" + for j = 4, 11 do + local low_byte = length % 256 + final_blocks[j] = char(low_byte) + length = (length - low_byte) / 256 + end + final_blocks = table_concat(final_blocks) + md5_feed_64(H, final_blocks, 0, #final_blocks) + for j = 1, 4 do + H[j] = HEX(H[j]) + end + H = gsub(table_concat(H), "(..)(..)(..)(..)", "%4%3%2%1") + end + return H + end + end + + if message then + -- Actually perform calculations and return the MD5 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get MD5 digest by invoking this function without an argument + return partial + end +end + + +local function sha1(message) + -- Create an instance (private objects for current calculation) + local H, length, tail = {unpack(md5_sha1_H)}, 0.0, "" + + local function partial(message_part) + if message_part then + if tail then + length = length + #message_part + local offs = 0 + if tail ~= "" and #tail + #message_part >= 64 then + offs = 64 - #tail + sha1_feed_64(H, tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size % 64 + sha1_feed_64(H, message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + local final_blocks = {tail, "\128", string_rep("\0", (-9 - length) % 64 + 1)} + tail = nil + -- Assuming user data length is shorter than (2^53)-9 bytes + -- 2^53 bytes = 2^56 bits, so "bit-counter" fits in 7 bytes + length = length * (8 / 256^7) -- convert "byte-counter" to "bit-counter" and move decimal point to the left + for j = 4, 10 do + length = length % 1 * 256 + final_blocks[j] = char(floor(length)) + end + final_blocks = table_concat(final_blocks) + sha1_feed_64(H, final_blocks, 0, #final_blocks) + for j = 1, 5 do + H[j] = HEX(H[j]) + end + H = table_concat(H) + end + return H + end + end + + if message then + -- Actually perform calculations and return the SHA-1 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA-1 digest by invoking this function without an argument + return partial + end +end + + +local function keccak(block_size_in_bytes, digest_size_in_bytes, is_SHAKE, message) + -- "block_size_in_bytes" is multiple of 8 + if type(digest_size_in_bytes) ~= "number" then + -- arguments in SHAKE are swapped: + -- NIST FIPS 202 defines SHAKE(message,num_bits) + -- this module defines SHAKE(num_bytes,message) + -- it's easy to forget about this swap, hence the check + error("Argument 'digest_size_in_bytes' must be a number", 2) + end + -- Create an instance (private objects for current calculation) + local tail, lanes_lo, lanes_hi = "", create_array_of_lanes(), hi_factor_keccak == 0 and create_array_of_lanes() + local result + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part >= block_size_in_bytes then + offs = block_size_in_bytes - #tail + keccak_feed(lanes_lo, lanes_hi, tail..sub(message_part, 1, offs), 0, block_size_in_bytes, block_size_in_bytes) + tail = "" + end + local size = #message_part - offs + local size_tail = size % block_size_in_bytes + keccak_feed(lanes_lo, lanes_hi, message_part, offs, size - size_tail, block_size_in_bytes) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + -- append the following bits to the message: for usual SHA-3: 011(0*)1, for SHAKE: 11111(0*)1 + local gap_start = is_SHAKE and 31 or 6 + tail = tail..(#tail + 1 == block_size_in_bytes and char(gap_start + 128) or char(gap_start)..string_rep("\0", (-2 - #tail) % block_size_in_bytes).."\128") + keccak_feed(lanes_lo, lanes_hi, tail, 0, #tail, block_size_in_bytes) + tail = nil + local lanes_used = 0 + local total_lanes = floor(block_size_in_bytes / 8) + local qwords = {} + + local function get_next_qwords_of_digest(qwords_qty) + -- returns not more than 'qwords_qty' qwords ('qwords_qty' might be non-integer) + -- doesn't go across keccak-buffer boundary + -- block_size_in_bytes is a multiple of 8, so, keccak-buffer contains integer number of qwords + if lanes_used >= total_lanes then + keccak_feed(lanes_lo, lanes_hi, "\0\0\0\0\0\0\0\0", 0, 8, 8) + lanes_used = 0 + end + qwords_qty = floor(math_min(qwords_qty, total_lanes - lanes_used)) + if hi_factor_keccak ~= 0 then + for j = 1, qwords_qty do + qwords[j] = HEX64(lanes_lo[lanes_used + j - 1 + lanes_index_base]) + end + else + for j = 1, qwords_qty do + qwords[j] = HEX(lanes_hi[lanes_used + j])..HEX(lanes_lo[lanes_used + j]) + end + end + lanes_used = lanes_used + qwords_qty + return + gsub(table_concat(qwords, "", 1, qwords_qty), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), + qwords_qty * 8 + end + + local parts = {} -- digest parts + local last_part, last_part_size = "", 0 + + local function get_next_part_of_digest(bytes_needed) + -- returns 'bytes_needed' bytes, for arbitrary integer 'bytes_needed' + bytes_needed = bytes_needed or 1 + if bytes_needed <= last_part_size then + last_part_size = last_part_size - bytes_needed + local part_size_in_nibbles = bytes_needed * 2 + local result = sub(last_part, 1, part_size_in_nibbles) + last_part = sub(last_part, part_size_in_nibbles + 1) + return result + end + local parts_qty = 0 + if last_part_size > 0 then + parts_qty = 1 + parts[parts_qty] = last_part + bytes_needed = bytes_needed - last_part_size + end + -- repeats until the length is enough + while bytes_needed >= 8 do + local next_part, next_part_size = get_next_qwords_of_digest(bytes_needed / 8) + parts_qty = parts_qty + 1 + parts[parts_qty] = next_part + bytes_needed = bytes_needed - next_part_size + end + if bytes_needed > 0 then + last_part, last_part_size = get_next_qwords_of_digest(1) + parts_qty = parts_qty + 1 + parts[parts_qty] = get_next_part_of_digest(bytes_needed) + else + last_part, last_part_size = "", 0 + end + return table_concat(parts, "", 1, parts_qty) + end + + if digest_size_in_bytes < 0 then + result = get_next_part_of_digest + else + result = get_next_part_of_digest(digest_size_in_bytes) + end + end + return result + end + end + + if message then + -- Actually perform calculations and return the SHA-3 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get SHA-3 digest by invoking this function without an argument + return partial + end +end + + +local hex_to_bin, bin_to_hex, bin_to_base64, base64_to_bin +do + function hex_to_bin(hex_string) + return (gsub(hex_string, "%x%x", + function (hh) + return char(tonumber(hh, 16)) + end + )) + end + + function bin_to_hex(binary_string) + return (gsub(binary_string, ".", + function (c) + return string_format("%02x", byte(c)) + end + )) + end + + local base64_symbols = { + ['+'] = 62, ['-'] = 62, [62] = '+', + ['/'] = 63, ['_'] = 63, [63] = '/', + ['='] = -1, ['.'] = -1, [-1] = '=' + } + local symbol_index = 0 + for j, pair in ipairs{'AZ', 'az', '09'} do + for ascii = byte(pair), byte(pair, 2) do + local ch = char(ascii) + base64_symbols[ch] = symbol_index + base64_symbols[symbol_index] = ch + symbol_index = symbol_index + 1 + end + end + + function bin_to_base64(binary_string) + local result = {} + for pos = 1, #binary_string, 3 do + local c1, c2, c3, c4 = byte(sub(binary_string, pos, pos + 2)..'\0', 1, -1) + result[#result + 1] = + base64_symbols[floor(c1 / 4)] + ..base64_symbols[c1 % 4 * 16 + floor(c2 / 16)] + ..base64_symbols[c3 and c2 % 16 * 4 + floor(c3 / 64) or -1] + ..base64_symbols[c4 and c3 % 64 or -1] + end + return table_concat(result) + end + + function base64_to_bin(base64_string) + local result, chars_qty = {}, 3 + for pos, ch in gmatch(gsub(base64_string, '%s+', ''), '()(.)') do + local code = base64_symbols[ch] + if code < 0 then + chars_qty = chars_qty - 1 + code = 0 + end + local idx = pos % 4 + if idx > 0 then + result[-idx] = code + else + local c1 = result[-1] * 4 + floor(result[-2] / 16) + local c2 = (result[-2] % 16) * 16 + floor(result[-3] / 4) + local c3 = (result[-3] % 4) * 64 + code + result[#result + 1] = sub(char(c1, c2, c3), 1, chars_qty) + end + end + return table_concat(result) + end + +end + + +local block_size_for_HMAC -- this table will be initialized at the end of the module + +local function pad_and_xor(str, result_length, byte_for_xor) + return gsub(str, ".", + function(c) + return char(XOR_BYTE(byte(c), byte_for_xor)) + end + )..string_rep(char(byte_for_xor), result_length - #str) +end + +local function hmac(hash_func, key, message) + -- Create an instance (private objects for current calculation) + local block_size = block_size_for_HMAC[hash_func] + if not block_size then + error("Unknown hash function", 2) + end + if #key > block_size then + key = hex_to_bin(hash_func(key)) + end + local append = hash_func()(pad_and_xor(key, block_size, 0x36)) + local result + + local function partial(message_part) + if not message_part then + result = result or hash_func(pad_and_xor(key, block_size, 0x5C)..hex_to_bin(append())) + return result + elseif result then + error("Adding more chunks is not allowed after receiving the result", 2) + else + append(message_part) + return partial + end + end + + if message then + -- Actually perform calculations and return the HMAC of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading of a message + -- User should feed every chunk of the message as single argument to this function and finally get HMAC by invoking this function without an argument + return partial + end +end + + +local function xor_blake2_salt(salt, letter, H_lo, H_hi) + -- salt: concatenation of "Salt"+"Personalization" fields + local max_size = letter == "s" and 16 or 32 + local salt_size = #salt + if salt_size > max_size then + error(string_format("For BLAKE2%s/BLAKE2%sp/BLAKE2X%s the 'salt' parameter length must not exceed %d bytes", letter, letter, letter, max_size), 2) + end + if H_lo then + local offset, blake2_word_size, xor = 0, letter == "s" and 4 or 8, letter == "s" and XOR or XORA5 + for j = 5, 4 + ceil(salt_size / blake2_word_size) do + local prev, last + for _ = 1, blake2_word_size, 4 do + offset = offset + 4 + local a, b, c, d = byte(salt, offset - 3, offset) + local four_bytes = (((d or 0) * 256 + (c or 0)) * 256 + (b or 0)) * 256 + (a or 0) + prev, last = last, four_bytes + end + H_lo[j] = xor(H_lo[j], prev and last * hi_factor + prev or last) + if H_hi then + H_hi[j] = xor(H_hi[j], last) + end + end + end +end + +local function blake2s(message, key, salt, digest_size_in_bytes, XOF_length, B2_offset) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- salt: (optional) binary string up to 16 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 32, by default 32 + -- The last two parameters "XOF_length" and "B2_offset" are for internal use only, user must omit them (or pass nil) + digest_size_in_bytes = digest_size_in_bytes or 32 + if digest_size_in_bytes < 1 or digest_size_in_bytes > 32 then + error("BLAKE2s digest length must be from 1 to 32 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 32 then + error("BLAKE2s key length must not exceed 32 bytes", 2) + end + salt = salt or "" + local bytes_compressed, tail, H = 0.0, "", {unpack(sha2_H_hi)} + if B2_offset then + H[1] = XOR(H[1], digest_size_in_bytes) + H[2] = XOR(H[2], 0x20) + H[3] = XOR(H[3], B2_offset) + H[4] = XOR(H[4], 0x20000000 + XOF_length) + else + H[1] = XOR(H[1], 0x01010000 + key_length * 256 + digest_size_in_bytes) + if XOF_length then + H[4] = XOR(H[4], XOF_length) + end + end + if salt ~= "" then + xor_blake2_salt(salt, "s", H) + end + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part > 64 then + offs = 64 - #tail + bytes_compressed = blake2s_feed_64(H, tail..sub(message_part, 1, offs), 0, 64, bytes_compressed) + tail = "" + end + local size = #message_part - offs + local size_tail = size > 0 and (size - 1) % 64 + 1 or 0 + bytes_compressed = blake2s_feed_64(H, message_part, offs, size - size_tail, bytes_compressed) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + if B2_offset then + blake2s_feed_64(H, nil, 0, 64, 0, 32) + else + blake2s_feed_64(H, tail..string_rep("\0", 64 - #tail), 0, 64, bytes_compressed, #tail) + end + tail = nil + if not XOF_length or B2_offset then + local max_reg = ceil(digest_size_in_bytes / 4) + for j = 1, max_reg do + H[j] = HEX(H[j]) + end + H = sub(gsub(table_concat(H, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + end + return H + end + end + + if key_length > 0 then + partial(key..string_rep("\0", 64 - key_length)) + end + if B2_offset then + return partial() + elseif message then + -- Actually perform calculations and return the BLAKE2s digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2s digest by invoking this function without an argument + return partial + end +end + +local function blake2b(message, key, salt, digest_size_in_bytes, XOF_length, B2_offset) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 64 bytes, by default empty string + -- salt: (optional) binary string up to 32 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 64, by default 64 + -- The last two parameters "XOF_length" and "B2_offset" are for internal use only, user must omit them (or pass nil) + digest_size_in_bytes = floor(digest_size_in_bytes or 64) + if digest_size_in_bytes < 1 or digest_size_in_bytes > 64 then + error("BLAKE2b digest length must be from 1 to 64 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 64 then + error("BLAKE2b key length must not exceed 64 bytes", 2) + end + salt = salt or "" + local bytes_compressed, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)} + if B2_offset then + if H_hi then + H_lo[1] = XORA5(H_lo[1], digest_size_in_bytes) + H_hi[1] = XORA5(H_hi[1], 0x40) + H_lo[2] = XORA5(H_lo[2], B2_offset) + H_hi[2] = XORA5(H_hi[2], XOF_length) + else + H_lo[1] = XORA5(H_lo[1], 0x40 * hi_factor + digest_size_in_bytes) + H_lo[2] = XORA5(H_lo[2], XOF_length * hi_factor + B2_offset) + end + H_lo[3] = XORA5(H_lo[3], 0x4000) + else + H_lo[1] = XORA5(H_lo[1], 0x01010000 + key_length * 256 + digest_size_in_bytes) + if XOF_length then + if H_hi then + H_hi[2] = XORA5(H_hi[2], XOF_length) + else + H_lo[2] = XORA5(H_lo[2], XOF_length * hi_factor) + end + end + end + if salt ~= "" then + xor_blake2_salt(salt, "b", H_lo, H_hi) + end + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part > 128 then + offs = 128 - #tail + bytes_compressed = blake2b_feed_128(H_lo, H_hi, tail..sub(message_part, 1, offs), 0, 128, bytes_compressed) + tail = "" + end + local size = #message_part - offs + local size_tail = size > 0 and (size - 1) % 128 + 1 or 0 + bytes_compressed = blake2b_feed_128(H_lo, H_hi, message_part, offs, size - size_tail, bytes_compressed) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + if B2_offset then + blake2b_feed_128(H_lo, H_hi, nil, 0, 128, 0, 64) + else + blake2b_feed_128(H_lo, H_hi, tail..string_rep("\0", 128 - #tail), 0, 128, bytes_compressed, #tail) + end + tail = nil + if XOF_length and not B2_offset then + if H_hi then + for j = 8, 1, -1 do + H_lo[j*2] = H_hi[j] + H_lo[j*2-1] = H_lo[j] + end + return H_lo, 16 + end + else + local max_reg = ceil(digest_size_in_bytes / 8) + if H_hi then + for j = 1, max_reg do + H_lo[j] = HEX(H_hi[j])..HEX(H_lo[j]) + end + else + for j = 1, max_reg do + H_lo[j] = HEX64(H_lo[j]) + end + end + H_lo = sub(gsub(table_concat(H_lo, "", 1, max_reg), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + H_hi = nil + end + return H_lo + end + end + + if key_length > 0 then + partial(key..string_rep("\0", 128 - key_length)) + end + if B2_offset then + return partial() + elseif message then + -- Actually perform calculations and return the BLAKE2b digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2b digest by invoking this function without an argument + return partial + end +end + +local function blake2sp(message, key, salt, digest_size_in_bytes) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- salt: (optional) binary string up to 16 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 32, by default 32 + digest_size_in_bytes = digest_size_in_bytes or 32 + if digest_size_in_bytes < 1 or digest_size_in_bytes > 32 then + error("BLAKE2sp digest length must be from 1 to 32 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 32 then + error("BLAKE2sp key length must not exceed 32 bytes", 2) + end + salt = salt or "" + local instances, length, first_dword_of_parameter_block, result = {}, 0.0, 0x02080000 + key_length * 256 + digest_size_in_bytes + for j = 1, 8 do + local bytes_compressed, tail, H = 0.0, "", {unpack(sha2_H_hi)} + instances[j] = {bytes_compressed, tail, H} + H[1] = XOR(H[1], first_dword_of_parameter_block) + H[3] = XOR(H[3], j-1) + H[4] = XOR(H[4], 0x20000000) + if salt ~= "" then + xor_blake2_salt(salt, "s", H) + end + end + + local function partial(message_part) + if message_part then + if instances then + local from = 0 + while true do + local to = math_min(from + 64 - length % 64, #message_part) + if to > from then + local inst = instances[floor(length / 64) % 8 + 1] + local part = sub(message_part, from + 1, to) + length, from = length + to - from, to + local bytes_compressed, tail = inst[1], inst[2] + if #tail < 64 then + tail = tail..part + else + local H = inst[3] + bytes_compressed = blake2s_feed_64(H, tail, 0, 64, bytes_compressed) + tail = part + end + inst[1], inst[2] = bytes_compressed, tail + else + break + end + end + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if instances then + local root_H = {unpack(sha2_H_hi)} + root_H[1] = XOR(root_H[1], first_dword_of_parameter_block) + root_H[4] = XOR(root_H[4], 0x20010000) + if salt ~= "" then + xor_blake2_salt(salt, "s", root_H) + end + for j = 1, 8 do + local inst = instances[j] + local bytes_compressed, tail, H = inst[1], inst[2], inst[3] + blake2s_feed_64(H, tail..string_rep("\0", 64 - #tail), 0, 64, bytes_compressed, #tail, j == 8) + if j % 2 == 0 then + local index = 0 + for k = j - 1, j do + local inst = instances[k] + local H = inst[3] + for i = 1, 8 do + index = index + 1 + common_W_blake2s[index] = H[i] + end + end + blake2s_feed_64(root_H, nil, 0, 64, 64 * (j/2 - 1), j == 8 and 64, j == 8) + end + end + instances = nil + local max_reg = ceil(digest_size_in_bytes / 4) + for j = 1, max_reg do + root_H[j] = HEX(root_H[j]) + end + result = sub(gsub(table_concat(root_H, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + return result + end + end + + if key_length > 0 then + key = key..string_rep("\0", 64 - key_length) + for j = 1, 8 do + partial(key) + end + end + if message then + -- Actually perform calculations and return the BLAKE2sp digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2sp digest by invoking this function without an argument + return partial + end + +end + +local function blake2bp(message, key, salt, digest_size_in_bytes) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 64 bytes, by default empty string + -- salt: (optional) binary string up to 32 bytes, by default empty string + -- digest_size_in_bytes: (optional) integer from 1 to 64, by default 64 + digest_size_in_bytes = digest_size_in_bytes or 64 + if digest_size_in_bytes < 1 or digest_size_in_bytes > 64 then + error("BLAKE2bp digest length must be from 1 to 64 bytes", 2) + end + key = key or "" + local key_length = #key + if key_length > 64 then + error("BLAKE2bp key length must not exceed 64 bytes", 2) + end + salt = salt or "" + local instances, length, first_dword_of_parameter_block, result = {}, 0.0, 0x02040000 + key_length * 256 + digest_size_in_bytes + for j = 1, 4 do + local bytes_compressed, tail, H_lo, H_hi = 0.0, "", {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)} + instances[j] = {bytes_compressed, tail, H_lo, H_hi} + H_lo[1] = XORA5(H_lo[1], first_dword_of_parameter_block) + H_lo[2] = XORA5(H_lo[2], j-1) + H_lo[3] = XORA5(H_lo[3], 0x4000) + if salt ~= "" then + xor_blake2_salt(salt, "b", H_lo, H_hi) + end + end + + local function partial(message_part) + if message_part then + if instances then + local from = 0 + while true do + local to = math_min(from + 128 - length % 128, #message_part) + if to > from then + local inst = instances[floor(length / 128) % 4 + 1] + local part = sub(message_part, from + 1, to) + length, from = length + to - from, to + local bytes_compressed, tail = inst[1], inst[2] + if #tail < 128 then + tail = tail..part + else + local H_lo, H_hi = inst[3], inst[4] + bytes_compressed = blake2b_feed_128(H_lo, H_hi, tail, 0, 128, bytes_compressed) + tail = part + end + inst[1], inst[2] = bytes_compressed, tail + else + break + end + end + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if instances then + local root_H_lo, root_H_hi = {unpack(sha2_H_lo)}, not HEX64 and {unpack(sha2_H_hi)} + root_H_lo[1] = XORA5(root_H_lo[1], first_dword_of_parameter_block) + root_H_lo[3] = XORA5(root_H_lo[3], 0x4001) + if salt ~= "" then + xor_blake2_salt(salt, "b", root_H_lo, root_H_hi) + end + for j = 1, 4 do + local inst = instances[j] + local bytes_compressed, tail, H_lo, H_hi = inst[1], inst[2], inst[3], inst[4] + blake2b_feed_128(H_lo, H_hi, tail..string_rep("\0", 128 - #tail), 0, 128, bytes_compressed, #tail, j == 4) + if j % 2 == 0 then + local index = 0 + for k = j - 1, j do + local inst = instances[k] + local H_lo, H_hi = inst[3], inst[4] + for i = 1, 8 do + index = index + 1 + common_W_blake2b[index] = H_lo[i] + if H_hi then + index = index + 1 + common_W_blake2b[index] = H_hi[i] + end + end + end + blake2b_feed_128(root_H_lo, root_H_hi, nil, 0, 128, 128 * (j/2 - 1), j == 4 and 128, j == 4) + end + end + instances = nil + local max_reg = ceil(digest_size_in_bytes / 8) + if HEX64 then + for j = 1, max_reg do + root_H_lo[j] = HEX64(root_H_lo[j]) + end + else + for j = 1, max_reg do + root_H_lo[j] = HEX(root_H_hi[j])..HEX(root_H_lo[j]) + end + end + result = sub(gsub(table_concat(root_H_lo, "", 1, max_reg), "(..)(..)(..)(..)(..)(..)(..)(..)", "%8%7%6%5%4%3%2%1"), 1, digest_size_in_bytes * 2) + end + return result + end + end + + if key_length > 0 then + key = key..string_rep("\0", 128 - key_length) + for j = 1, 4 do + partial(key) + end + end + if message then + -- Actually perform calculations and return the BLAKE2bp digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2bp digest by invoking this function without an argument + return partial + end + +end + +local function blake2x(inner_func, inner_func_letter, common_W_blake2, block_size, digest_size_in_bytes, message, key, salt) + local XOF_digest_length_limit, XOF_digest_length, chunk_by_chunk_output = 2^(block_size / 2) - 1 + if digest_size_in_bytes == -1 then -- infinite digest + digest_size_in_bytes = math_huge + XOF_digest_length = floor(XOF_digest_length_limit) + chunk_by_chunk_output = true + else + if digest_size_in_bytes < 0 then + digest_size_in_bytes = -1.0 * digest_size_in_bytes + chunk_by_chunk_output = true + end + XOF_digest_length = floor(digest_size_in_bytes) + if XOF_digest_length >= XOF_digest_length_limit then + error("Requested digest is too long. BLAKE2X"..inner_func_letter.." finite digest is limited by (2^"..floor(block_size / 2)..")-2 bytes. Hint: you can generate infinite digest.", 2) + end + end + salt = salt or "" + if salt ~= "" then + xor_blake2_salt(salt, inner_func_letter) -- don't xor, only check the size of salt + end + local inner_partial = inner_func(nil, key, salt, nil, XOF_digest_length) + local result + + local function partial(message_part) + if message_part then + if inner_partial then + inner_partial(message_part) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if inner_partial then + local half_W, half_W_size = inner_partial() + half_W_size, inner_partial = half_W_size or 8 + + local function get_hash_block(block_no) + -- block_no = 0...(2^32-1) + local size = math_min(block_size, digest_size_in_bytes - block_no * block_size) + if size <= 0 then + return "" + end + for j = 1, half_W_size do + common_W_blake2[j] = half_W[j] + end + for j = half_W_size + 1, 2 * half_W_size do + common_W_blake2[j] = 0 + end + return inner_func(nil, nil, salt, size, XOF_digest_length, floor(block_no)) + end + + local hash = {} + if chunk_by_chunk_output then + local pos, period, cached_block_no, cached_block = 0, block_size * 2^32 + + local function get_next_part_of_digest(arg1, arg2) + if arg1 == "seek" then + -- Usage #1: get_next_part_of_digest("seek", new_pos) + pos = arg2 % period + else + -- Usage #2: hex_string = get_next_part_of_digest(size) + local size, index = arg1 or 1, 0 + while size > 0 do + local block_offset = pos % block_size + local block_no = (pos - block_offset) / block_size + local part_size = math_min(size, block_size - block_offset) + if cached_block_no ~= block_no then + cached_block_no = block_no + cached_block = get_hash_block(block_no) + end + index = index + 1 + hash[index] = sub(cached_block, block_offset * 2 + 1, (block_offset + part_size) * 2) + size = size - part_size + pos = (pos + part_size) % period + end + return table_concat(hash, "", 1, index) + end + end + + result = get_next_part_of_digest + else + for j = 1.0, ceil(digest_size_in_bytes / block_size) do + hash[j] = get_hash_block(j - 1.0) + end + result = table_concat(hash) + end + end + return result + end + end + + if message then + -- Actually perform calculations and return the BLAKE2X digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE2X digest by invoking this function without an argument + return partial + end +end + +local function blake2xs(digest_size_in_bytes, message, key, salt) + -- digest_size_in_bytes: + -- 0..65534 = get finite digest as single Lua string + -- (-1) = get infinite digest in "chunk-by-chunk" output mode + -- (-2)..(-65534) = get finite digest in "chunk-by-chunk" output mode + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- salt: (optional) binary string up to 16 bytes, by default empty string + return blake2x(blake2s, "s", common_W_blake2s, 32, digest_size_in_bytes, message, key, salt) +end + +local function blake2xb(digest_size_in_bytes, message, key, salt) + -- digest_size_in_bytes: + -- 0..4294967294 = get finite digest as single Lua string + -- (-1) = get infinite digest in "chunk-by-chunk" output mode + -- (-2)..(-4294967294) = get finite digest in "chunk-by-chunk" output mode + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 64 bytes, by default empty string + -- salt: (optional) binary string up to 32 bytes, by default empty string + return blake2x(blake2b, "b", common_W_blake2b, 64, digest_size_in_bytes, message, key, salt) +end + + +local function blake3(message, key, digest_size_in_bytes, message_flags, K, return_array) + -- message: binary string to be hashed (or nil for "chunk-by-chunk" input mode) + -- key: (optional) binary string up to 32 bytes, by default empty string + -- digest_size_in_bytes: (optional) by default 32 + -- 0,1,2,3,4,... = get finite digest as single Lua string + -- (-1) = get infinite digest in "chunk-by-chunk" output mode + -- -2,-3,-4,... = get finite digest in "chunk-by-chunk" output mode + -- The last three parameters "message_flags", "K" and "return_array" are for internal use only, user must omit them (or pass nil) + key = key or "" + digest_size_in_bytes = digest_size_in_bytes or 32 + message_flags = message_flags or 0 + if key == "" then + K = K or sha2_H_hi + else + local key_length = #key + if key_length > 32 then + error("BLAKE3 key length must not exceed 32 bytes", 2) + end + key = key..string_rep("\0", 32 - key_length) + K = {} + for j = 1, 8 do + local a, b, c, d = byte(key, 4*j-3, 4*j) + K[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + message_flags = message_flags + 16 -- flag:KEYED_HASH + end + local tail, H, chunk_index, blocks_in_chunk, stack_size, stack = "", {}, 0, 0, 0, {} + local final_H_in, final_block_length, chunk_by_chunk_output, result, wide_output = K + local final_compression_flags = 3 -- flags:CHUNK_START,CHUNK_END + + local function feed_blocks(str, offs, size) + -- size >= 0, size is multiple of 64 + while size > 0 do + local part_size_in_blocks, block_flags, H_in = 1, 0, H + if blocks_in_chunk == 0 then + block_flags = 1 -- flag:CHUNK_START + H_in, final_H_in = K, H + final_compression_flags = 2 -- flag:CHUNK_END + elseif blocks_in_chunk == 15 then + block_flags = 2 -- flag:CHUNK_END + final_compression_flags = 3 -- flags:CHUNK_START,CHUNK_END + final_H_in = K + else + part_size_in_blocks = math_min(size / 64, 15 - blocks_in_chunk) + end + local part_size = part_size_in_blocks * 64 + blake3_feed_64(str, offs, part_size, message_flags + block_flags, chunk_index, H_in, H) + offs, size = offs + part_size, size - part_size + blocks_in_chunk = (blocks_in_chunk + part_size_in_blocks) % 16 + if blocks_in_chunk == 0 then + -- completing the currect chunk + chunk_index = chunk_index + 1.0 + local divider = 2.0 + while chunk_index % divider == 0 do + divider = divider * 2.0 + stack_size = stack_size - 8 + for j = 1, 8 do + common_W_blake2s[j] = stack[stack_size + j] + end + for j = 1, 8 do + common_W_blake2s[j + 8] = H[j] + end + blake3_feed_64(nil, 0, 64, message_flags + 4, 0, K, H) -- flag:PARENT + end + for j = 1, 8 do + stack[stack_size + j] = H[j] + end + stack_size = stack_size + 8 + end + end + end + + local function get_hash_block(block_no) + local size = math_min(64, digest_size_in_bytes - block_no * 64) + if block_no < 0 or size <= 0 then + return "" + end + if chunk_by_chunk_output then + for j = 1, 16 do + common_W_blake2s[j] = stack[j + 16] + end + end + blake3_feed_64(nil, 0, 64, final_compression_flags, block_no, final_H_in, stack, wide_output, final_block_length) + if return_array then + return stack + end + local max_reg = ceil(size / 4) + for j = 1, max_reg do + stack[j] = HEX(stack[j]) + end + return sub(gsub(table_concat(stack, "", 1, max_reg), "(..)(..)(..)(..)", "%4%3%2%1"), 1, size * 2) + end + + local function partial(message_part) + if message_part then + if tail then + local offs = 0 + if tail ~= "" and #tail + #message_part > 64 then + offs = 64 - #tail + feed_blocks(tail..sub(message_part, 1, offs), 0, 64) + tail = "" + end + local size = #message_part - offs + local size_tail = size > 0 and (size - 1) % 64 + 1 or 0 + feed_blocks(message_part, offs, size - size_tail) + tail = tail..sub(message_part, #message_part + 1 - size_tail) + return partial + else + error("Adding more chunks is not allowed after receiving the result", 2) + end + else + if tail then + final_block_length = #tail + tail = tail..string_rep("\0", 64 - #tail) + if common_W_blake2s[0] then + for j = 1, 16 do + local a, b, c, d = byte(tail, 4*j-3, 4*j) + common_W_blake2s[j] = OR(SHL(d, 24), SHL(c, 16), SHL(b, 8), a) + end + else + for j = 1, 16 do + local a, b, c, d = byte(tail, 4*j-3, 4*j) + common_W_blake2s[j] = ((d * 256 + c) * 256 + b) * 256 + a + end + end + tail = nil + for stack_size = stack_size - 8, 0, -8 do + blake3_feed_64(nil, 0, 64, message_flags + final_compression_flags, chunk_index, final_H_in, H, nil, final_block_length) + chunk_index, final_block_length, final_H_in, final_compression_flags = 0, 64, K, 4 -- flag:PARENT + for j = 1, 8 do + common_W_blake2s[j] = stack[stack_size + j] + end + for j = 1, 8 do + common_W_blake2s[j + 8] = H[j] + end + end + final_compression_flags = message_flags + final_compression_flags + 8 -- flag:ROOT + if digest_size_in_bytes < 0 then + if digest_size_in_bytes == -1 then -- infinite digest + digest_size_in_bytes = math_huge + else + digest_size_in_bytes = -1.0 * digest_size_in_bytes + end + chunk_by_chunk_output = true + for j = 1, 16 do + stack[j + 16] = common_W_blake2s[j] + end + end + digest_size_in_bytes = math_min(2^53, digest_size_in_bytes) + wide_output = digest_size_in_bytes > 32 + if chunk_by_chunk_output then + local pos, cached_block_no, cached_block = 0.0 + + local function get_next_part_of_digest(arg1, arg2) + if arg1 == "seek" then + -- Usage #1: get_next_part_of_digest("seek", new_pos) + pos = arg2 * 1.0 + else + -- Usage #2: hex_string = get_next_part_of_digest(size) + local size, index = arg1 or 1, 32 + while size > 0 do + local block_offset = pos % 64 + local block_no = (pos - block_offset) / 64 + local part_size = math_min(size, 64 - block_offset) + if cached_block_no ~= block_no then + cached_block_no = block_no + cached_block = get_hash_block(block_no) + end + index = index + 1 + stack[index] = sub(cached_block, block_offset * 2 + 1, (block_offset + part_size) * 2) + size = size - part_size + pos = pos + part_size + end + return table_concat(stack, "", 33, index) + end + end + + result = get_next_part_of_digest + elseif digest_size_in_bytes <= 64 then + result = get_hash_block(0) + else + local last_block_no = ceil(digest_size_in_bytes / 64) - 1 + for block_no = 0.0, last_block_no do + stack[33 + block_no] = get_hash_block(block_no) + end + result = table_concat(stack, "", 33, 33 + last_block_no) + end + end + return result + end + end + + if message then + -- Actually perform calculations and return the BLAKE3 digest of a message + return partial(message)() + else + -- Return function for chunk-by-chunk loading + -- User should feed every chunk of input data as single argument to this function and finally get BLAKE3 digest by invoking this function without an argument + return partial + end +end + +local function blake3_derive_key(key_material, context_string, derived_key_size_in_bytes) + -- key_material: (string) your source of entropy to derive a key from (for example, it can be a master password) + -- set to nil for feeding the key material in "chunk-by-chunk" input mode + -- context_string: (string) unique description of the derived key + -- digest_size_in_bytes: (optional) by default 32 + -- 0,1,2,3,4,... = get finite derived key as single Lua string + -- (-1) = get infinite derived key in "chunk-by-chunk" output mode + -- -2,-3,-4,... = get finite derived key in "chunk-by-chunk" output mode + if type(context_string) ~= "string" then + error("'context_string' parameter must be a Lua string", 2) + end + local K = blake3(context_string, nil, nil, 32, nil, true) -- flag:DERIVE_KEY_CONTEXT + return blake3(key_material, nil, derived_key_size_in_bytes, 64, K) -- flag:DERIVE_KEY_MATERIAL +end + + + +local sha = { + md5 = md5, -- MD5 + sha1 = sha1, -- SHA-1 + -- SHA-2 hash functions: + sha224 = function (message) return sha256ext(224, message) end, -- SHA-224 + sha256 = function (message) return sha256ext(256, message) end, -- SHA-256 + sha512_224 = function (message) return sha512ext(224, message) end, -- SHA-512/224 + sha512_256 = function (message) return sha512ext(256, message) end, -- SHA-512/256 + sha384 = function (message) return sha512ext(384, message) end, -- SHA-384 + sha512 = function (message) return sha512ext(512, message) end, -- SHA-512 + -- SHA-3 hash functions: + sha3_224 = function (message) return keccak((1600 - 2 * 224) / 8, 224 / 8, false, message) end, -- SHA3-224 + sha3_256 = function (message) return keccak((1600 - 2 * 256) / 8, 256 / 8, false, message) end, -- SHA3-256 + sha3_384 = function (message) return keccak((1600 - 2 * 384) / 8, 384 / 8, false, message) end, -- SHA3-384 + sha3_512 = function (message) return keccak((1600 - 2 * 512) / 8, 512 / 8, false, message) end, -- SHA3-512 + shake128 = function (digest_size_in_bytes, message) return keccak((1600 - 2 * 128) / 8, digest_size_in_bytes, true, message) end, -- SHAKE128 + shake256 = function (digest_size_in_bytes, message) return keccak((1600 - 2 * 256) / 8, digest_size_in_bytes, true, message) end, -- SHAKE256 + -- HMAC: + hmac = hmac, -- HMAC(hash_func, key, message) is applicable to any hash function from this module except SHAKE* and BLAKE* + -- misc utilities: + hex_to_bin = hex_to_bin, -- converts hexadecimal representation to binary string + bin_to_hex = bin_to_hex, -- converts binary string to hexadecimal representation + base64_to_bin = base64_to_bin, -- converts base64 representation to binary string + bin_to_base64 = bin_to_base64, -- converts binary string to base64 representation + -- old style names for backward compatibility: + hex2bin = hex_to_bin, + bin2hex = bin_to_hex, + base642bin = base64_to_bin, + bin2base64 = bin_to_base64, + -- BLAKE2 hash functions: + blake2b = blake2b, -- BLAKE2b (message, key, salt, digest_size_in_bytes) + blake2s = blake2s, -- BLAKE2s (message, key, salt, digest_size_in_bytes) + blake2bp = blake2bp, -- BLAKE2bp(message, key, salt, digest_size_in_bytes) + blake2sp = blake2sp, -- BLAKE2sp(message, key, salt, digest_size_in_bytes) + blake2xb = blake2xb, -- BLAKE2Xb(digest_size_in_bytes, message, key, salt) + blake2xs = blake2xs, -- BLAKE2Xs(digest_size_in_bytes, message, key, salt) + -- BLAKE2 aliases: + blake2 = blake2b, + blake2b_160 = function (message, key, salt) return blake2b(message, key, salt, 20) end, -- BLAKE2b-160 + blake2b_256 = function (message, key, salt) return blake2b(message, key, salt, 32) end, -- BLAKE2b-256 + blake2b_384 = function (message, key, salt) return blake2b(message, key, salt, 48) end, -- BLAKE2b-384 + blake2b_512 = blake2b, -- 64 -- BLAKE2b-512 + blake2s_128 = function (message, key, salt) return blake2s(message, key, salt, 16) end, -- BLAKE2s-128 + blake2s_160 = function (message, key, salt) return blake2s(message, key, salt, 20) end, -- BLAKE2s-160 + blake2s_224 = function (message, key, salt) return blake2s(message, key, salt, 28) end, -- BLAKE2s-224 + blake2s_256 = blake2s, -- 32 -- BLAKE2s-256 + -- BLAKE3 hash function + blake3 = blake3, -- BLAKE3 (message, key, digest_size_in_bytes) + blake3_derive_key = blake3_derive_key, -- BLAKE3_KDF(key_material, context_string, derived_key_size_in_bytes) +} + + +block_size_for_HMAC = { + [sha.md5] = 64, + [sha.sha1] = 64, + [sha.sha224] = 64, + [sha.sha256] = 64, + [sha.sha512_224] = 128, + [sha.sha512_256] = 128, + [sha.sha384] = 128, + [sha.sha512] = 128, + [sha.sha3_224] = 144, -- (1600 - 2 * 224) / 8 + [sha.sha3_256] = 136, -- (1600 - 2 * 256) / 8 + [sha.sha3_384] = 104, -- (1600 - 2 * 384) / 8 + [sha.sha3_512] = 72, -- (1600 - 2 * 512) / 8 +} + + +return sha diff --git a/suricata/lua/tcpstore.lua b/suricata/lua/tcpstore.lua new file mode 100644 index 0000000..cb077c9 --- /dev/null +++ b/suricata/lua/tcpstore.lua @@ -0,0 +1,62 @@ +-- Copyright (C) 2023 ANSSI +-- SPDX-License-Identifier: GPL-3.0-only + +function init (args) + local needs = {} + needs["type"] = "streaming" + needs["filter"] = "tcp" + return needs +end + +function setup (args) + -- tcpstore.log contains (flow_id, direction, sha256) tuples + local logfilename = SCLogPath() .. "/tcpstore.log" + logfile = assert(io.open(logfilename, "a")) + + -- tcpstore folder contains raw data + foldername = SCLogPath() .. "/tcpstore/" + for i=0,255 do + istr = string.format("%02x", i) + os.execute("mkdir -p " .. foldername .. istr) + end + + -- packer counter for each flow + flow_pkt_count = {} + flow_pkt_count_total = 0 +end + +function log (args) + local sha = require("suricata/lua/sha2") + + -- create log entry + local flow_id = SCFlowId() + local flow_id_str = string.format("%.0f", flow_id) + if flow_pkt_count[flow_id] == nil then + flow_pkt_count[flow_id] = 0 + else + flow_pkt_count[flow_id] = flow_pkt_count[flow_id] + 1 + end + local count = flow_pkt_count[flow_id] + flow_pkt_count_total = flow_pkt_count_total + 1 + local data, sb_open, sb_close, sb_ts, sb_tc = SCStreamingBuffer() + if #data == 0 then + return + end + local direction = "0" + if sb_tc then + direction = "1" + end + local hash = sha.sha256(data) + logfile:write(flow_id_str .. "," .. count .. "," .. direction .. "," .. hash .. "\n") + + -- save data + local filename = foldername .. string.sub(hash, 1, 2) .. "/" .. hash + local datafile = assert(io.open(filename, "w")) + datafile:write(data) + datafile:close() +end + +function deinit (args) + SCLogNotice("TCP flow logged: " .. flow_pkt_count_total) + logfile:close() +end diff --git a/suricata/lua/udpstore.lua b/suricata/lua/udpstore.lua new file mode 100644 index 0000000..f4b35b3 --- /dev/null +++ b/suricata/lua/udpstore.lua @@ -0,0 +1,71 @@ +-- Copyright (C) 2023 ANSSI +-- SPDX-License-Identifier: GPL-3.0-only + +function init (args) + local needs = {} + needs["type"] = "packet" + return needs +end + +function setup (args) + -- udpstore.log contains (flow_id, direction, sha256) tuples + local logfilename = SCLogPath() .. "/udpstore.log" + logfile = assert(io.open(logfilename, "a")) + + -- udpstore folder contains raw data + foldername = SCLogPath() .. "/udpstore/" + for i=0,255 do + istr = string.format("%02x", i) + os.execute("mkdir -p " .. foldername .. istr) + end + + -- packer counter for each flow + flow_pkt_count = {} + flow_pkt_count_total = 0 +end + +function log (args) + local sha = require("suricata/lua/sha2") + + -- drop if not UDP (17) + -- https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml + local ipver, srcip, dstip, proto, sp, dp = SCPacketTuple() + if proto ~= 17 then + return + end + + -- get packet direction + local ipver, srcip_flow, dstip_flow, proto, sp_flow, dp_flow = SCFlowTuple() + local direction = "1" + if srcip == srcip_flow and dstip == dstip_flow and sp == sp_flow and dp == dp_flow then + direction = "0" + end + + -- create log entry + local flow_id = SCFlowId() + local flow_id_str = string.format("%.0f", flow_id) + if flow_pkt_count[flow_id] == nil then + flow_pkt_count[flow_id] = 0 + else + flow_pkt_count[flow_id] = flow_pkt_count[flow_id] + 1 + end + local count = flow_pkt_count[flow_id] + flow_pkt_count_total = flow_pkt_count_total + 1 + local data = SCPacketPayload() + if #data == 0 then + return + end + local hash = sha.sha256(data) + logfile:write(flow_id_str .. "," .. count .. "," .. direction .. "," .. hash .. "\n") + + -- save data + local filename = foldername .. string.sub(hash, 1, 2) .. "/" .. hash + local datafile = assert(io.open(filename, "w")) + datafile:write(data) + datafile:close() +end + +function deinit (args) + SCLogNotice("UDP flow logged: " .. flow_pkt_count_total) + logfile:close() +end diff --git a/suricata/output/.gitkeep b/suricata/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/suricata/suricata.yaml b/suricata/suricata.yaml new file mode 100644 index 0000000..0f28495 --- /dev/null +++ b/suricata/suricata.yaml @@ -0,0 +1,2168 @@ +%YAML 1.1 +--- + +# Suricata configuration file. In addition to the comments describing all +# options in this file, full documentation can be found at: +# https://docs.suricata.io/en/latest/configuration/suricata-yaml.html + +# This configuration file generated by Suricata 7.0.0. +suricata-version: "7.0" + +## +## Step 1: Inform Suricata about your network +## + +vars: + # more specific is better for alert accuracy and performance + address-groups: + HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]" + #HOME_NET: "[192.168.0.0/16]" + #HOME_NET: "[10.0.0.0/8]" + #HOME_NET: "[172.16.0.0/12]" + #HOME_NET: "any" + + EXTERNAL_NET: "!$HOME_NET" + #EXTERNAL_NET: "any" + + HTTP_SERVERS: "$HOME_NET" + SMTP_SERVERS: "$HOME_NET" + SQL_SERVERS: "$HOME_NET" + DNS_SERVERS: "$HOME_NET" + TELNET_SERVERS: "$HOME_NET" + AIM_SERVERS: "$EXTERNAL_NET" + DC_SERVERS: "$HOME_NET" + DNP3_SERVER: "$HOME_NET" + DNP3_CLIENT: "$HOME_NET" + MODBUS_CLIENT: "$HOME_NET" + MODBUS_SERVER: "$HOME_NET" + ENIP_CLIENT: "$HOME_NET" + ENIP_SERVER: "$HOME_NET" + + port-groups: + HTTP_PORTS: "80" + SHELLCODE_PORTS: "!80" + ORACLE_PORTS: 1521 + SSH_PORTS: 22 + DNP3_PORTS: 20000 + MODBUS_PORTS: 502 + FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]" + FTP_PORTS: 21 + GENEVE_PORTS: 6081 + VXLAN_PORTS: 4789 + TEREDO_PORTS: 3544 + +## +## Step 2: Select outputs to enable +## + +# The default logging directory. Any log or output file will be +# placed here if it's not specified with a full path name. This can be +# overridden with the -l command line parameter. +default-log-dir: /var/log/suricata/ + +# Global stats configuration +stats: + enabled: no + # The interval field (in seconds) controls the interval at + # which stats are updated in the log. + interval: 8 + # Add decode events to stats. + #decoder-events: true + # Decoder event prefix in stats. Has been 'decoder' before, but that leads + # to missing events in the eve.stats records. See issue #2225. + #decoder-events-prefix: "decoder.event" + # Add stream events as stats. + #stream-events: false + +# Plugins -- Experimental -- specify the filename for each plugin shared object +plugins: +# - /path/to/plugin.so + +# Configure the type of alert (and other) logging you would like. +outputs: + # a line based alerts log similar to Snort's fast.log + - fast: + enabled: no + filename: fast.log + append: yes + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + + # Extensible Event Format (nicknamed EVE) event log in JSON format + - eve-log: + enabled: yes + filetype: regular #regular|syslog|unix_dgram|unix_stream|redis + filename: eve.json + # Enable for multi-threaded eve.json output; output files are amended with + # an identifier, e.g., eve.9.json + #threaded: false + #prefix: "@cee: " # prefix to prepend to each log entry + # the following are valid when type: syslog above + #identity: "suricata" + #facility: local5 + #level: Info ## possible levels: Emergency, Alert, Critical, + ## Error, Warning, Notice, Info, Debug + #ethernet: no # log ethernet header in events when available + #redis: + # server: 127.0.0.1 + # port: 6379 + # async: true ## if redis replies are read asynchronously + # mode: list ## possible values: list|lpush (default), rpush, channel|publish + # ## lpush and rpush are using a Redis list. "list" is an alias for lpush + # ## publish is using a Redis channel. "channel" is an alias for publish + # key: suricata ## key or channel to use (default to suricata) + # Redis pipelining set up. This will enable to only do a query every + # 'batch-size' events. This should lower the latency induced by network + # connection at the cost of some memory. There is no flushing implemented + # so this setting should be reserved to high traffic Suricata deployments. + # pipelining: + # enabled: yes ## set enable to yes to enable query pipelining + # batch-size: 10 ## number of entries to keep in buffer + + # Include top level metadata. Default yes. + #metadata: no + + # include the name of the input pcap file in pcap file processing mode + pcap-file: true + + # Community Flow ID + # Adds a 'community_id' field to EVE records. These are meant to give + # records a predictable flow ID that can be used to match records to + # output of other tools such as Zeek (Bro). + # + # Takes a 'seed' that needs to be same across sensors and tools + # to make the id less predictable. + + # enable/disable the community id feature. + community-id: false + # Seed value for the ID output. Valid values are 0-65535. + community-id-seed: 0 + + # HTTP X-Forwarded-For support by adding an extra field or overwriting + # the source or destination IP address (depending on flow direction) + # with the one reported in the X-Forwarded-For HTTP header. This is + # helpful when reviewing alerts for traffic that is being reverse + # or forward proxied. + xff: + enabled: no + # Two operation modes are available: "extra-data" and "overwrite". + mode: extra-data + # Two proxy deployments are supported: "reverse" and "forward". In + # a "reverse" deployment the IP address used is the last one, in a + # "forward" deployment the first IP address is used. + deployment: reverse + # Header name where the actual IP address will be reported. If more + # than one IP address is present, the last IP address will be the + # one taken into consideration. + header: X-Forwarded-For + + types: + - alert: + # payload: yes # enable dumping payload in Base64 + # payload-buffer-size: 4kb # max size of payload buffer to output in eve-log + # payload-printable: yes # enable dumping payload in printable (lossy) format + # packet: yes # enable dumping of packet (without stream segments) + # metadata: no # enable inclusion of app layer metadata with alert. Default yes + # http-body: yes # Requires metadata; enable dumping of HTTP body in Base64 + # http-body-printable: yes # Requires metadata; enable dumping of HTTP body in printable format + + # Enable the logging of tagged packets for rules using the + # "tag" keyword. + tagged-packets: yes + # Enable logging the final action taken on a packet by the engine + # (e.g: the alert may have action 'allowed' but the verdict be + # 'drop' due to another alert. That's the engine's verdict) + # verdict: yes + # app layer frames + - frame: + # disabled by default as this is very verbose. + enabled: no + - anomaly: + # Anomaly log records describe unexpected conditions such + # as truncated packets, packets with invalid IP/UDP/TCP + # length values, and other events that render the packet + # invalid for further processing or describe unexpected + # behavior on an established stream. Networks which + # experience high occurrences of anomalies may experience + # packet processing degradation. + # + # Anomalies are reported for the following: + # 1. Decode: Values and conditions that are detected while + # decoding individual packets. This includes invalid or + # unexpected values for low-level protocol lengths as well + # as stream related events (TCP 3-way handshake issues, + # unexpected sequence number, etc). + # 2. Stream: This includes stream related events (TCP + # 3-way handshake issues, unexpected sequence number, + # etc). + # 3. Application layer: These denote application layer + # specific conditions that are unexpected, invalid or are + # unexpected given the application monitoring state. + # + # By default, anomaly logging is enabled. When anomaly + # logging is enabled, applayer anomaly reporting is + # also enabled. + enabled: yes + # + # Choose one or more types of anomaly logging and whether to enable + # logging of the packet header for packet anomalies. + types: + # decode: no + # stream: no + # applayer: yes + #packethdr: no + - http: + extended: yes # enable this for extended logging information + # custom allows additional HTTP fields to be included in eve-log. + # the example below adds three additional fields when uncommented + #custom: [Accept-Encoding, Accept-Language, Authorization] + # set this value to one and only one from {both, request, response} + # to dump all HTTP headers for every HTTP request and/or response + dump-all-headers: both + - dns: + # This configuration uses the new DNS logging format, + # the old configuration is still available: + # https://docs.suricata.io/en/latest/output/eve/eve-json-output.html#dns-v1-format + + # As of Suricata 5.0, version 2 of the eve dns output + # format is the default. + #version: 2 + + # Enable/disable this logger. Default: enabled. + #enabled: yes + + # Control logging of requests and responses: + # - requests: enable logging of DNS queries + # - responses: enable logging of DNS answers + # By default both requests and responses are logged. + #requests: no + #responses: no + + # Format of answer logging: + # - detailed: array item per answer + # - grouped: answers aggregated by type + # Default: all + #formats: [detailed, grouped] + + # DNS record types to log, based on the query type. + # Default: all. + #types: [a, aaaa, cname, mx, ns, ptr, txt] + - tls: + extended: yes # enable this for extended logging information + # output TLS transaction where the session is resumed using a + # session id + #session-resumption: no + # custom controls which TLS fields that are included in eve-log + #custom: [subject, issuer, session_resumed, serial, fingerprint, sni, version, not_before, not_after, certificate, chain, ja3, ja3s] + - files: + force-magic: yes # force logging magic on all logged files + # force logging of checksums, available hash functions are md5, + # sha1 and sha256 + force-hash: [sha256] + #- drop: + # alerts: yes # log alerts that caused drops + # flows: all # start or all: 'start' logs only a single drop + # # per flow direction. All logs each dropped pkt. + # Enable logging the final action taken on a packet by the engine + # (will show more information in case of a drop caused by 'reject') + # verdict: yes + - smtp: + #extended: yes # enable this for extended logging information + # this includes: bcc, message-id, subject, x_mailer, user-agent + # custom fields logging from the list: + # reply-to, bcc, message-id, subject, x-mailer, user-agent, received, + # x-originating-ip, in-reply-to, references, importance, priority, + # sensitivity, organization, content-md5, date + #custom: [received, x-mailer, x-originating-ip, relays, reply-to, bcc] + # output md5 of fields: body, subject + # for the body you need to set app-layer.protocols.smtp.mime.body-md5 + # to yes + #md5: [body, subject] + + #- dnp3 + - ftp + - rdp + - nfs + - smb + - tftp + - ike + - dcerpc + - krb5 + - bittorrent-dht + - snmp + - rfb + - sip + - quic + - dhcp: + enabled: yes + # When extended mode is on, all DHCP messages are logged + # with full detail. When extended mode is off (the + # default), just enough information to map a MAC address + # to an IP address is logged. + extended: yes + - ssh + - mqtt: + passwords: yes # enable output of passwords + - http2 + - pgsql: + enabled: yes + passwords: yes # enable output of passwords. Disabled by default + #- stats: + # totals: yes # stats for all threads merged together + # threads: no # per thread stats + # deltas: no # include delta values + # bi-directional flows + - flow + # uni-directional flows + #- netflow + + # Metadata event type. Triggered whenever a pktvar is saved + # and will include the pktvars, flowvars, flowbits and + # flowints. + #- metadata + + # EXPERIMENTAL per packet output giving TCP state tracking details + # including internal state, flags, etc. + # This output is experimental, meant for debugging and subject to + # change in both config and output without any notice. + #- stream: + # all: false # log all TCP packets + # event-set: false # log packets that have a decoder/stream event + # state-update: false # log packets triggering a TCP state update + # spurious-retransmission: false # log spurious retransmission packets + + # a line based log of HTTP requests (no alerts) + - http-log: + enabled: no + filename: http.log + append: yes + #extended: yes # enable this for extended logging information + #custom: yes # enable the custom logging format (defined by customformat) + #customformat: "%{%D-%H:%M:%S}t.%z %{X-Forwarded-For}i %H %m %h %u %s %B %a:%p -> %A:%P" + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + + # a line based log of TLS handshake parameters (no alerts) + - tls-log: + enabled: no # Log TLS connections. + filename: tls.log # File to store TLS logs. + append: yes + #extended: yes # Log extended information like fingerprint + #custom: yes # enabled the custom logging format (defined by customformat) + #customformat: "%{%D-%H:%M:%S}t.%z %a:%p -> %A:%P %v %n %d %D" + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + # output TLS transaction where the session is resumed using a + # session id + #session-resumption: no + + # output module to store certificates chain to disk + - tls-store: + enabled: no + #certs-log-dir: certs # directory to store the certificates files + + # Packet log... log packets in pcap format. 3 modes of operation: "normal" + # "multi" and "sguil". + # + # In normal mode a pcap file "filename" is created in the default-log-dir, + # or as specified by "dir". + # In multi mode, a file is created per thread. This will perform much + # better, but will create multiple files where 'normal' would create one. + # In multi mode the filename takes a few special variables: + # - %n -- thread number + # - %i -- thread id + # - %t -- timestamp (secs or secs.usecs based on 'ts-format' + # E.g. filename: pcap.%n.%t + # + # Note that it's possible to use directories, but the directories are not + # created by Suricata. E.g. filename: pcaps/%n/log.%s will log into the + # per thread directory. + # + # Also note that the limit and max-files settings are enforced per thread. + # So the size limit when using 8 threads with 1000mb files and 2000 files + # is: 8*1000*2000 ~ 16TiB. + # + # In Sguil mode "dir" indicates the base directory. In this base dir the + # pcaps are created in the directory structure Sguil expects: + # + # $sguil-base-dir/YYYY-MM-DD/$filename. + # + # By default all packets are logged except: + # - TCP streams beyond stream.reassembly.depth + # - encrypted streams after the key exchange + # + - pcap-log: + enabled: no + filename: log.pcap + + # File size limit. Can be specified in kb, mb, gb. Just a number + # is parsed as bytes. + limit: 1000mb + + # If set to a value, ring buffer mode is enabled. Will keep maximum of + # "max-files" of size "limit" + max-files: 2000 + + # Compression algorithm for pcap files. Possible values: none, lz4. + # Enabling compression is incompatible with the sguil mode. Note also + # that on Windows, enabling compression will *increase* disk I/O. + compression: none + + # Further options for lz4 compression. The compression level can be set + # to a value between 0 and 16, where higher values result in higher + # compression. + #lz4-checksum: no + #lz4-level: 0 + + mode: normal # normal, multi or sguil. + + # Directory to place pcap files. If not provided the default log + # directory will be used. Required for "sguil" mode. + #dir: /nsm_data/ + + #ts-format: usec # sec or usec second format (default) is filename.sec usec is filename.sec.usec + use-stream-depth: no #If set to "yes" packets seen after reaching stream inspection depth are ignored. "no" logs all packets + honor-pass-rules: no # If set to "yes", flows in which a pass rule matched will stop being logged. + # Use "all" to log all packets or use "alerts" to log only alerted packets and flows or "tag" + # to log only flow tagged via the "tag" keyword + #conditional: all + + # a full alert log containing much information for signature writers + # or for investigating suspected false positives. + - alert-debug: + enabled: no + filename: alert-debug.log + append: yes + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + + # Stats.log contains data from various counters of the Suricata engine. + - stats: + enabled: no + filename: stats.log + append: yes # append to file (yes) or overwrite it (no) + totals: yes # stats for all threads merged together + threads: no # per thread stats + #null-values: yes # print counters that have value 0. Default: no + + # a line based alerts log similar to fast.log into syslog + - syslog: + enabled: no + # reported identity to syslog. If omitted the program name (usually + # suricata) will be used. + #identity: "suricata" + facility: local5 + #level: Info ## possible levels: Emergency, Alert, Critical, + ## Error, Warning, Notice, Info, Debug + + # Output module for storing files on disk. Files are stored in + # directory names consisting of the first 2 characters of the + # SHA256 of the file. Each file is given its SHA256 as a filename. + # + # When a duplicate file is found, the timestamps on the existing file + # are updated. + # + # Unlike the older filestore, metadata is not written by default + # as each file should already have a "fileinfo" record in the + # eve-log. If write-fileinfo is set to yes, then each file will have + # one more associated .json files that consist of the fileinfo + # record. A fileinfo file will be written for each occurrence of the + # file seen using a filename suffix to ensure uniqueness. + # + # To prune the filestore directory see the "suricatactl filestore + # prune" command which can delete files over a certain age. + - file-store: + version: 2 + enabled: yes + + # Set the directory for the filestore. Relative pathnames + # are contained within the "default-log-dir". + #dir: filestore + + # Write out a fileinfo record for each occurrence of a file. + # Disabled by default as each occurrence is already logged + # as a fileinfo record to the main eve-log. + #write-fileinfo: yes + + # Force storing of all files. Default: no. + force-filestore: yes + + # Override the global stream-depth for sessions in which we want + # to perform file extraction. Set to 0 for unlimited; otherwise, + # must be greater than the global stream-depth value to be used. + #stream-depth: 0 + + # Uncomment the following variable to define how many files can + # remain open for filestore by Suricata. Default value is 0 which + # means files get closed after each write to the file. + #max-open-files: 1000 + + # Force logging of checksums: available hash functions are md5, + # sha1 and sha256. Note that SHA256 is automatically forced by + # the use of this output module as it uses the SHA256 as the + # file naming scheme. + #force-hash: [sha1, md5] + # NOTE: X-Forwarded configuration is ignored if write-fileinfo is disabled + # HTTP X-Forwarded-For support by adding an extra field or overwriting + # the source or destination IP address (depending on flow direction) + # with the one reported in the X-Forwarded-For HTTP header. This is + # helpful when reviewing alerts for traffic that is being reverse + # or forward proxied. + xff: + enabled: no + # Two operation modes are available, "extra-data" and "overwrite". + mode: extra-data + # Two proxy deployments are supported, "reverse" and "forward". In + # a "reverse" deployment the IP address used is the last one, in a + # "forward" deployment the first IP address is used. + deployment: reverse + # Header name where the actual IP address will be reported. If more + # than one IP address is present, the last IP address will be the + # one taken into consideration. + header: X-Forwarded-For + + # Log TCP data after stream normalization + # Two types: file or dir: + # - file logs into a single logfile. + # - dir creates 2 files per TCP session and stores the raw TCP + # data into them. + # Use 'both' to enable both file and dir modes. + # + # Note: limited by "stream.reassembly.depth" + - tcp-data: + enabled: no + type: file + filename: tcp-data.log + + # Log HTTP body data after normalization, de-chunking and unzipping. + # Two types: file or dir. + # - file logs into a single logfile. + # - dir creates 2 files per HTTP session and stores the + # normalized data into them. + # Use 'both' to enable both file and dir modes. + # + # Note: limited by the body limit settings + - http-body-data: + enabled: no + type: file + filename: http-data.log + + # Lua Output Support - execute lua script to generate alert and event + # output. + # Documented at: + # https://docs.suricata.io/en/latest/output/lua-output.html + - lua: + enabled: yes + #scripts-dir: /etc/suricata/lua-output/ + scripts: + - suricata/lua/tcpstore.lua + - suricata/lua/udpstore.lua + +# Logging configuration. This is not about logging IDS alerts/events, but +# output about what Suricata is doing, like startup messages, errors, etc. +logging: + # The default log level: can be overridden in an output section. + # Note that debug level logging will only be emitted if Suricata was + # compiled with the --enable-debug configure option. + # + # This value is overridden by the SC_LOG_LEVEL env var. + default-log-level: notice + + # The default output format. Optional parameter, should default to + # something reasonable if not provided. Can be overridden in an + # output section. You can leave this out to get the default. + # + # This console log format value can be overridden by the SC_LOG_FORMAT env var. + #default-log-format: "%D: %S: %M" + # + # For the pre-7.0 log format use: + #default-log-format: "[%i] %t [%S] - (%f:%l) <%d> (%n) -- " + + # A regex to filter output. Can be overridden in an output section. + # Defaults to empty (no filter). + # + # This value is overridden by the SC_LOG_OP_FILTER env var. + default-output-filter: + + # Requires libunwind to be available when Suricata is configured and built. + # If a signal unexpectedly terminates Suricata, displays a brief diagnostic + # message with the offending stacktrace if enabled. + #stacktrace-on-signal: on + + # Define your logging outputs. If none are defined, or they are all + # disabled you will get the default: console output. + outputs: + - console: + enabled: yes + # type: json + - file: + enabled: yes + level: info + filename: suricata.log + # format: "[%i - %m] %z %d: %S: %M" + # type: json + - syslog: + enabled: no + facility: local5 + format: "[%i] <%d> -- " + # type: json + + +## +## Step 3: Configure common capture settings +## +## See "Advanced Capture Options" below for more options, including Netmap +## and PF_RING. +## + +# Linux high speed capture support +af-packet: + - interface: eth0 + # Number of receive threads. "auto" uses the number of cores + #threads: auto + # Default clusterid. AF_PACKET will load balance packets based on flow. + cluster-id: 99 + # Default AF_PACKET cluster type. AF_PACKET can load balance per flow or per hash. + # This is only supported for Linux kernel > 3.1 + # possible value are: + # * cluster_flow: all packets of a given flow are sent to the same socket + # * cluster_cpu: all packets treated in kernel by a CPU are sent to the same socket + # * cluster_qm: all packets linked by network card to a RSS queue are sent to the same + # socket. Requires at least Linux 3.14. + # * cluster_ebpf: eBPF file load balancing. See doc/userguide/capture-hardware/ebpf-xdp.rst for + # more info. + # Recommended modes are cluster_flow on most boxes and cluster_cpu or cluster_qm on system + # with capture card using RSS (requires cpu affinity tuning and system IRQ tuning) + # cluster_rollover has been deprecated; if used, it'll be replaced with cluster_flow. + cluster-type: cluster_flow + # In some fragmentation cases, the hash can not be computed. If "defrag" is set + # to yes, the kernel will do the needed defragmentation before sending the packets. + defrag: yes + # To use the ring feature of AF_PACKET, set 'use-mmap' to yes + #use-mmap: yes + # Lock memory map to avoid it being swapped. Be careful that over + # subscribing could lock your system + #mmap-locked: yes + # Use tpacket_v3 capture mode, only active if use-mmap is true + # Don't use it in IPS or TAP mode as it causes severe latency + #tpacket-v3: yes + # Ring size will be computed with respect to "max-pending-packets" and number + # of threads. You can set manually the ring size in number of packets by setting + # the following value. If you are using flow "cluster-type" and have really network + # intensive single-flow you may want to set the "ring-size" independently of the number + # of threads: + #ring-size: 2048 + # Block size is used by tpacket_v3 only. It should set to a value high enough to contain + # a decent number of packets. Size is in bytes so please consider your MTU. It should be + # a power of 2 and it must be multiple of page size (usually 4096). + #block-size: 32768 + # tpacket_v3 block timeout: an open block is passed to userspace if it is not + # filled after block-timeout milliseconds. + #block-timeout: 10 + # On busy systems, set it to yes to help recover from a packet drop + # phase. This will result in some packets (at max a ring flush) not being inspected. + #use-emergency-flush: yes + # recv buffer size, increased value could improve performance + # buffer-size: 32768 + # Set to yes to disable promiscuous mode + # disable-promisc: no + # Choose checksum verification mode for the interface. At the moment + # of the capture, some packets may have an invalid checksum due to + # the checksum computation being offloaded to the network card. + # Possible values are: + # - kernel: use indication sent by kernel for each packet (default) + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: Suricata uses a statistical approach to detect when + # checksum off-loading is used. + # Warning: 'capture.checksum-validation' must be set to yes to have any validation + #checksum-checks: kernel + # BPF filter to apply to this interface. The pcap filter syntax applies here. + #bpf-filter: port 80 or udp + # You can use the following variables to activate AF_PACKET tap or IPS mode. + # If copy-mode is set to ips or tap, the traffic coming to the current + # interface will be copied to the copy-iface interface. If 'tap' is set, the + # copy is complete. If 'ips' is set, the packet matching a 'drop' action + # will not be copied. + #copy-mode: ips + #copy-iface: eth1 + # For eBPF and XDP setup including bypass, filter and load balancing, please + # see doc/userguide/capture-hardware/ebpf-xdp.rst for more info. + + # Put default values here. These will be used for an interface that is not + # in the list above. + - interface: default + #threads: auto + #use-mmap: no + #tpacket-v3: yes + +# Linux high speed af-xdp capture support +af-xdp: + - interface: default + # Number of receive threads. "auto" uses least between the number + # of cores and RX queues + #threads: auto + #disable-promisc: false + # XDP_DRV mode can be chosen when the driver supports XDP + # XDP_SKB mode can be chosen when the driver does not support XDP + # Possible values are: + # - drv: enable XDP_DRV mode + # - skb: enable XDP_SKB mode + # - none: disable (kernel in charge of applying mode) + #force-xdp-mode: none + # During socket binding the kernel will attempt zero-copy, if this + # fails it will fallback to copy. If this fails, the bind fails. + # The bind can be explicitly configured using the option below. + # If configured, the bind will fail if not successful (no fallback). + # Possible values are: + # - zero: enable zero-copy mode + # - copy: enable copy mode + # - none: disable (kernel in charge of applying mode) + #force-bind-mode: none + # Memory alignment mode can vary between two modes, aligned and + # unaligned chunk modes. By default, aligned chunk mode is selected. + # select 'yes' to enable unaligned chunk mode. + # Note: unaligned chunk mode uses hugepages, so the required number + # of pages must be available. + #mem-unaligned: no + # The following options configure the prefer-busy-polling socket + # options. The polling time and budget can be edited here. + # Possible values are: + # - yes: enable (default) + # - no: disable + #enable-busy-poll: yes + # busy-poll-time sets the approximate time in microseconds to busy + # poll on a blocking receive when there is no data. + #busy-poll-time: 20 + # busy-poll-budget is the budget allowed for packet batches + #busy-poll-budget: 64 + # These two tunables are used to configure the Linux OS's NAPI + # context. Their purpose is to defer enabling of interrupts and + # instead schedule the NAPI context from a watchdog timer. + # The softirq NAPI will exit early, allowing busy polling to be + # performed. Successfully setting these tunables alongside busy-polling + # should improve performance. + # Defaults are: + #gro-flush-timeout: 2000000 + #napi-defer-hard-irq: 2 + +dpdk: + eal-params: + proc-type: primary + + # DPDK capture support + # RX queues (and TX queues in IPS mode) are assigned to cores in 1:1 ratio + interfaces: + - interface: 0000:3b:00.0 # PCIe address of the NIC port + # Threading: possible values are either "auto" or number of threads + # - auto takes all cores + # in IPS mode it is required to specify the number of cores and the numbers on both interfaces must match + threads: auto + promisc: true # promiscuous mode - capture all packets + multicast: true # enables also detection on multicast packets + checksum-checks: true # if Suricata should validate checksums + checksum-checks-offload: true # if possible offload checksum validation to the NIC (saves Suricata resources) + mtu: 1500 # Set MTU of the device in bytes + # rss-hash-functions: 0x0 # advanced configuration option, use only if you use untested NIC card and experience RSS warnings, + # For `rss-hash-functions` use hexadecimal 0x01ab format to specify RSS hash function flags - DumpRssFlags can help (you can see output if you use -vvv option during Suri startup) + # setting auto to rss_hf sets the default RSS hash functions (based on IP addresses) + + # To approximately calculate required amount of space (in bytes) for interface's mempool: mempool-size * mtu + # Make sure you have enough allocated hugepages. + # The optimum size for the packet memory pool (in terms of memory usage) is power of two minus one: n = (2^q - 1) + mempool-size: 65535 # The number of elements in the mbuf pool + + # Mempool cache size must be lower or equal to: + # - RTE_MEMPOOL_CACHE_MAX_SIZE (by default 512) and + # - "mempool-size / 1.5" + # It is advised to choose cache_size to have "mempool-size modulo cache_size == 0". + # If this is not the case, some elements will always stay in the pool and will never be used. + # The cache can be disabled if the cache_size argument is set to 0, can be useful to avoid losing objects in cache + # If the value is empty or set to "auto", Suricata will attempt to set cache size of the mempool to a value + # that matches the previously mentioned recommendations + mempool-cache-size: 257 + rx-descriptors: 1024 + tx-descriptors: 1024 + # + # IPS mode for Suricata works in 3 modes - none, tap, ips + # - none: IDS mode only - disables IPS functionality (does not further forward packets) + # - tap: forwards all packets and generates alerts (omits DROP action) This is not DPDK TAP + # - ips: the same as tap mode but it also drops packets that are flagged by rules to be dropped + copy-mode: none + copy-iface: none # or PCIe address of the second interface + + - interface: default + threads: auto + promisc: true + multicast: true + checksum-checks: true + checksum-checks-offload: true + mtu: 1500 + rss-hash-functions: auto + mempool-size: 65535 + mempool-cache-size: 257 + rx-descriptors: 1024 + tx-descriptors: 1024 + copy-mode: none + copy-iface: none + + +# Cross platform libpcap capture support +pcap: + - interface: eth0 + # On Linux, pcap will try to use mmap'ed capture and will use "buffer-size" + # as total memory used by the ring. So set this to something bigger + # than 1% of your bandwidth. + #buffer-size: 16777216 + #bpf-filter: "tcp and port 25" + # Choose checksum verification mode for the interface. At the moment + # of the capture, some packets may have an invalid checksum due to + # the checksum computation being offloaded to the network card. + # Possible values are: + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: Suricata uses a statistical approach to detect when + # checksum off-loading is used. (default) + # Warning: 'capture.checksum-validation' must be set to yes to have any validation + #checksum-checks: auto + # With some accelerator cards using a modified libpcap (like Myricom), you + # may want to have the same number of capture threads as the number of capture + # rings. In this case, set up the threads variable to N to start N threads + # listening on the same interface. + #threads: 16 + # set to no to disable promiscuous mode: + #promisc: no + # set snaplen, if not set it defaults to MTU if MTU can be known + # via ioctl call and to full capture if not. + #snaplen: 1518 + # Put default values here + - interface: default + #checksum-checks: auto + +# Settings for reading pcap files +pcap-file: + # Possible values are: + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: Suricata uses a statistical approach to detect when + # checksum off-loading is used. (default) + # Warning: 'checksum-validation' must be set to yes to have checksum tested + checksum-checks: no + +# See "Advanced Capture Options" below for more options, including Netmap +# and PF_RING. + + +## +## Step 4: App Layer Protocol configuration +## + +# Configure the app-layer parsers. +# +# The error-policy setting applies to all app-layer parsers. Values can be +# "drop-flow", "pass-flow", "bypass", "drop-packet", "pass-packet", "reject" or +# "ignore" (the default). +# +# The protocol's section details each protocol. +# +# The option "enabled" takes 3 values - "yes", "no", "detection-only". +# "yes" enables both detection and the parser, "no" disables both, and +# "detection-only" enables protocol detection only (parser disabled). +app-layer: + # error-policy: ignore + protocols: + telnet: + enabled: yes + rfb: + enabled: yes + detection-ports: + dp: 5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909 + mqtt: + enabled: yes + # max-msg-length: 1mb + # subscribe-topic-match-limit: 100 + # unsubscribe-topic-match-limit: 100 + # Maximum number of live MQTT transactions per flow + # max-tx: 4096 + krb5: + enabled: yes + bittorrent-dht: + enabled: yes + snmp: + enabled: yes + ike: + enabled: yes + tls: + enabled: yes + detection-ports: + dp: 443 + + # Generate JA3 fingerprint from client hello. If not specified it + # will be disabled by default, but enabled if rules require it. + #ja3-fingerprints: auto + + # What to do when the encrypted communications start: + # - default: keep tracking TLS session, check for protocol anomalies, + # inspect tls_* keywords. Disables inspection of unmodified + # 'content' signatures. + # - bypass: stop processing this flow as much as possible. No further + # TLS parsing and inspection. Offload flow bypass to kernel + # or hardware if possible. + # - full: keep tracking and inspection as normal. Unmodified content + # keyword signatures are inspected as well. + # + # For best performance, select 'bypass'. + # + #encryption-handling: default + + pgsql: + enabled: yes + # Stream reassembly size for PostgreSQL. By default, track it completely. + stream-depth: 0 + # Maximum number of live PostgreSQL transactions per flow + # max-tx: 1024 + dcerpc: + enabled: yes + # Maximum number of live DCERPC transactions per flow + # max-tx: 1024 + ftp: + enabled: yes + # memcap: 64mb + rdp: + #enabled: yes + ssh: + enabled: yes + #hassh: yes + http2: + enabled: yes + # Maximum number of live HTTP2 streams in a flow + #max-streams: 4096 + # Maximum headers table size + #max-table-size: 65536 + smtp: + enabled: yes + raw-extraction: no + # Configure SMTP-MIME Decoder + mime: + # Decode MIME messages from SMTP transactions + # (may be resource intensive) + # This field supersedes all others because it turns the entire + # process on or off + decode-mime: yes + + # Decode MIME entity bodies (ie. Base64, quoted-printable, etc.) + decode-base64: yes + decode-quoted-printable: yes + + # Maximum bytes per header data value stored in the data structure + # (default is 2000) + header-value-depth: 2000 + + # Extract URLs and save in state data structure + extract-urls: yes + # Scheme of URLs to extract + # (default is [http]) + #extract-urls-schemes: [http, https, ftp, mailto] + # Log the scheme of URLs that are extracted + # (default is no) + #log-url-scheme: yes + # Set to yes to compute the md5 of the mail body. You will then + # be able to journalize it. + body-md5: no + # Configure inspected-tracker for file_data keyword + inspected-tracker: + content-limit: 100000 + content-inspect-min-size: 32768 + content-inspect-window: 4096 + imap: + enabled: detection-only + smb: + enabled: yes + detection-ports: + dp: 139, 445 + # Maximum number of live SMB transactions per flow + # max-tx: 1024 + + # Stream reassembly size for SMB streams. By default track it completely. + #stream-depth: 0 + + nfs: + enabled: yes + # max-tx: 1024 + tftp: + enabled: yes + dns: + tcp: + enabled: yes + detection-ports: + dp: 53 + udp: + enabled: yes + detection-ports: + dp: 53 + http: + enabled: yes + + # Byte Range Containers default settings + # byterange: + # memcap: 100mb + # timeout: 60 + + # memcap: Maximum memory capacity for HTTP + # Default is unlimited, values can be 64mb, e.g. + + # default-config: Used when no server-config matches + # personality: List of personalities used by default + # request-body-limit: Limit reassembly of request body for inspection + # by http_client_body & pcre /P option. + # response-body-limit: Limit reassembly of response body for inspection + # by file_data, http_server_body & pcre /Q option. + # + # For advanced options, see the user guide + + + # server-config: List of server configurations to use if address matches + # address: List of IP addresses or networks for this block + # personality: List of personalities used by this block + # + # Then, all the fields from default-config can be overloaded + # + # Currently Available Personalities: + # Minimal, Generic, IDS (default), IIS_4_0, IIS_5_0, IIS_5_1, IIS_6_0, + # IIS_7_0, IIS_7_5, Apache_2 + libhtp: + default-config: + personality: IDS + + # Can be specified in kb, mb, gb. Just a number indicates + # it's in bytes. + request-body-limit: 100kb + response-body-limit: 100kb + + # inspection limits + request-body-minimal-inspect-size: 32kb + request-body-inspect-window: 4kb + response-body-minimal-inspect-size: 40kb + response-body-inspect-window: 16kb + + # response body decompression (0 disables) + response-body-decompress-layer-limit: 2 + + # auto will use http-body-inline mode in IPS mode, yes or no set it statically + http-body-inline: auto + + # Decompress SWF files. Disabled by default. + # Two types: 'deflate', 'lzma', 'both' will decompress deflate and lzma + # compress-depth: + # Specifies the maximum amount of data to decompress, + # set 0 for unlimited. + # decompress-depth: + # Specifies the maximum amount of decompressed data to obtain, + # set 0 for unlimited. + swf-decompression: + enabled: no + type: both + compress-depth: 100kb + decompress-depth: 100kb + + # Use a random value for inspection sizes around the specified value. + # This lowers the risk of some evasion techniques but could lead + # to detection change between runs. It is set to 'yes' by default. + #randomize-inspection-sizes: yes + # If "randomize-inspection-sizes" is active, the value of various + # inspection size will be chosen from the [1 - range%, 1 + range%] + # range + # Default value of "randomize-inspection-range" is 10. + #randomize-inspection-range: 10 + + # decoding + double-decode-path: no + double-decode-query: no + + # Can enable LZMA decompression + #lzma-enabled: false + # Memory limit usage for LZMA decompression dictionary + # Data is decompressed until dictionary reaches this size + #lzma-memlimit: 1mb + # Maximum decompressed size with a compression ratio + # above 2048 (only LZMA can reach this ratio, deflate cannot) + #compression-bomb-limit: 1mb + # Maximum time spent decompressing a single transaction in usec + #decompression-time-limit: 100000 + + server-config: + + #- apache: + # address: [192.168.1.0/24, 127.0.0.0/8, "::1"] + # personality: Apache_2 + # # Can be specified in kb, mb, gb. Just a number indicates + # # it's in bytes. + # request-body-limit: 4096 + # response-body-limit: 4096 + # double-decode-path: no + # double-decode-query: no + + #- iis7: + # address: + # - 192.168.0.0/24 + # - 192.168.10.0/24 + # personality: IIS_7_0 + # # Can be specified in kb, mb, gb. Just a number indicates + # # it's in bytes. + # request-body-limit: 4096 + # response-body-limit: 4096 + # double-decode-path: no + # double-decode-query: no + + # Note: Modbus probe parser is minimalist due to the limited usage in the field. + # Only Modbus message length (greater than Modbus header length) + # and protocol ID (equal to 0) are checked in probing parser + # It is important to enable detection port and define Modbus port + # to avoid false positives + modbus: + # How many unanswered Modbus requests are considered a flood. + # If the limit is reached, the app-layer-event:modbus.flooded; will match. + #request-flood: 500 + + enabled: yes + detection-ports: + dp: 502 + # According to MODBUS Messaging on TCP/IP Implementation Guide V1.0b, it + # is recommended to keep the TCP connection opened with a remote device + # and not to open and close it for each MODBUS/TCP transaction. In that + # case, it is important to set the depth of the stream reassembling as + # unlimited (stream.reassembly.depth: 0) + + # Stream reassembly size for modbus. By default track it completely. + stream-depth: 0 + + # DNP3 + dnp3: + enabled: yes + detection-ports: + dp: 20000 + + # SCADA EtherNet/IP and CIP protocol support + enip: + enabled: yes + detection-ports: + dp: 44818 + sp: 44818 + + ntp: + enabled: yes + + quic: + enabled: yes + + dhcp: + enabled: yes + + sip: + enabled: yes + +# Limit for the maximum number of asn1 frames to decode (default 256) +asn1-max-frames: 256 + +# Datasets default settings +datasets: + # Default fallback memcap and hashsize values for datasets in case these + # were not explicitly defined. + defaults: + #memcap: 100mb + #hashsize: 2048 + + rules: + # Set to true to allow absolute filenames and filenames that use + # ".." components to reference parent directories in rules that specify + # their filenames. + #allow-absolute-filenames: false + + # Allow datasets in rules write access for "save" and + # "state". This is enabled by default, however write access is + # limited to the data directory. + #allow-write: true + +############################################################################## +## +## Advanced settings below +## +############################################################################## + +## +## Run Options +## + +# Run Suricata with a specific user-id and group-id: +#run-as: +# user: suri +# group: suri + +security: + # if true, prevents process creation from Suricata by calling + # setrlimit(RLIMIT_NPROC, 0) + limit-noproc: true + # Use landlock security module under Linux + landlock: + enabled: no + directories: + #write: + # - /var/run/ + # /usr and /etc folders are added to read list to allow + # file magic to be used. + read: + - /usr/ + - /etc/ + - /nix/store/9zi80g57g091a5qky6x3cmvmmb9zcfvq-suricata-7.0.0/etc/suricata/ + + lua: + # Allow Lua rules. Disabled by default. + #allow-rules: false + +# Some logging modules will use that name in event as identifier. The default +# value is the hostname +#sensor-name: suricata + +# Default location of the pid file. The pid file is only used in +# daemon mode (start Suricata with -D). If not running in daemon mode +# the --pidfile command line option must be used to create a pid file. +#pid-file: /var/run/suricata.pid + +# Daemon working directory +# Suricata will change directory to this one if provided +# Default: "/" +#daemon-directory: "/" + +# Umask. +# Suricata will use this umask if it is provided. By default it will use the +# umask passed on by the shell. +#umask: 022 + +# Suricata core dump configuration. Limits the size of the core dump file to +# approximately max-dump. The actual core dump size will be a multiple of the +# page size. Core dumps that would be larger than max-dump are truncated. On +# Linux, the actual core dump size may be a few pages larger than max-dump. +# Setting max-dump to 0 disables core dumping. +# Setting max-dump to 'unlimited' will give the full core dump file. +# On 32-bit Linux, a max-dump value >= ULONG_MAX may cause the core dump size +# to be 'unlimited'. + +coredump: + max-dump: unlimited + +# If the Suricata box is a router for the sniffed networks, set it to 'router'. If +# it is a pure sniffing setup, set it to 'sniffer-only'. +# If set to auto, the variable is internally switched to 'router' in IPS mode +# and 'sniffer-only' in IDS mode. +# This feature is currently only used by the reject* keywords. +host-mode: auto + +# Number of packets preallocated per thread. The default is 1024. A higher number +# will make sure each CPU will be more easily kept busy, but may negatively +# impact caching. +#max-pending-packets: 1024 + +# Runmode the engine should use. Please check --list-runmodes to get the available +# runmodes for each packet acquisition method. Default depends on selected capture +# method. 'workers' generally gives best performance. +#runmode: autofp + +# Specifies the kind of flow load balancer used by the flow pinned autofp mode. +# +# Supported schedulers are: +# +# hash - Flow assigned to threads using the 5-7 tuple hash. +# ippair - Flow assigned to threads using addresses only. +# ftp-hash - Flow assigned to threads using the hash, except for FTP, so that +# ftp-data flows will be handled by the same thread +# +#autofp-scheduler: hash + +# Preallocated size for each packet. Default is 1514 which is the classical +# size for pcap on Ethernet. You should adjust this value to the highest +# packet size (MTU + hardware header) on your system. +#default-packet-size: 1514 + +# Unix command socket that can be used to pass commands to Suricata. +# An external tool can then connect to get information from Suricata +# or trigger some modifications of the engine. Set enabled to yes +# to activate the feature. In auto mode, the feature will only be +# activated in live capture mode. You can use the filename variable to set +# the file name of the socket. +unix-command: + enabled: auto + #filename: custom.socket + +# Magic file. The extension .mgc is added to the value here. +#magic-file: /usr/share/file/magic +#magic-file: + +# GeoIP2 database file. Specify path and filename of GeoIP2 database +# if using rules with "geoip" rule option. +#geoip-database: /usr/local/share/GeoLite2/GeoLite2-Country.mmdb + +legacy: + uricontent: enabled + +## +## Detection settings +## + +# Set the order of alerts based on actions +# The default order is pass, drop, reject, alert +# action-order: +# - pass +# - drop +# - reject +# - alert + +# Define maximum number of possible alerts that can be triggered for the same +# packet. Default is 15 +#packet-alert-max: 15 + +# Exception Policies +# +# Define a common behavior for all exception policies. +# In IPS mode, the default is drop-flow. For cases when that's not possible, the +# engine will fall to drop-packet. To fallback to old behavior (setting each of +# them individually, or ignoring all), set this to ignore. +# All values available for exception policies can be used, and there is one +# extra option: auto - which means drop-flow or drop-packet (as explained above) +# in IPS mode, and ignore in IDS mode. Exception policy values are: drop-packet, +# drop-flow, reject, bypass, pass-packet, pass-flow, ignore (disable). +exception-policy: auto + +# IP Reputation +#reputation-categories-file: /etc/suricata/iprep/categories.txt +#default-reputation-path: /etc/suricata/iprep +#reputation-files: +# - reputation.list + +# When run with the option --engine-analysis, the engine will read each of +# the parameters below, and print reports for each of the enabled sections +# and exit. The reports are printed to a file in the default log dir +# given by the parameter "default-log-dir", with engine reporting +# subsection below printing reports in its own report file. +engine-analysis: + # enables printing reports for fast-pattern for every rule. + rules-fast-pattern: yes + # enables printing reports for each rule + rules: yes + +#recursion and match limits for PCRE where supported +pcre: + match-limit: 3500 + match-limit-recursion: 1500 + +## +## Advanced Traffic Tracking and Reconstruction Settings +## + +# Host specific policies for defragmentation and TCP stream +# reassembly. The host OS lookup is done using a radix tree, just +# like a routing table so the most specific entry matches. +host-os-policy: + # Make the default policy windows. + windows: [0.0.0.0/0] + bsd: [] + bsd-right: [] + old-linux: [] + linux: [] + old-solaris: [] + solaris: [] + hpux10: [] + hpux11: [] + irix: [] + macos: [] + vista: [] + windows2k3: [] + +# Defrag settings: + +# The memcap-policy value can be "drop-packet", "pass-packet", "reject" or +# "ignore" (which is the default). +defrag: + memcap: 32mb + # memcap-policy: ignore + hash-size: 65536 + trackers: 65535 # number of defragmented flows to follow + max-frags: 65535 # number of fragments to keep (higher than trackers) + prealloc: yes + timeout: 60 + +# Enable defrag per host settings +# host-config: +# +# - dmz: +# timeout: 30 +# address: [192.168.1.0/24, 127.0.0.0/8, 1.1.1.0/24, 2.2.2.0/24, "1.1.1.1", "2.2.2.2", "::1"] +# +# - lan: +# timeout: 45 +# address: +# - 192.168.0.0/24 +# - 192.168.10.0/24 +# - 172.16.14.0/24 + +# Flow settings: +# By default, the reserved memory (memcap) for flows is 32MB. This is the limit +# for flow allocation inside the engine. You can change this value to allow +# more memory usage for flows. +# The hash-size determines the size of the hash used to identify flows inside +# the engine, and by default the value is 65536. +# At startup, the engine can preallocate a number of flows, to get better +# performance. The number of flows preallocated is 10000 by default. +# emergency-recovery is the percentage of flows that the engine needs to +# prune before clearing the emergency state. The emergency state is activated +# when the memcap limit is reached, allowing new flows to be created, but +# pruning them with the emergency timeouts (they are defined below). +# If the memcap is reached, the engine will try to prune flows +# with the default timeouts. If it doesn't find a flow to prune, it will set +# the emergency bit and it will try again with more aggressive timeouts. +# If that doesn't work, then it will try to kill the oldest flows using +# last time seen flows. +# The memcap can be specified in kb, mb, gb. Just a number indicates it's +# in bytes. +# The memcap-policy can be "drop-packet", "pass-packet", "reject" or "ignore" +# (which is the default). + +flow: + memcap: 128mb + #memcap-policy: ignore + hash-size: 65536 + prealloc: 10000 + emergency-recovery: 30 + #managers: 1 # default to one flow manager + #recyclers: 1 # default to one flow recycler thread + +# This option controls the use of VLAN ids in the flow (and defrag) +# hashing. Normally this should be enabled, but in some (broken) +# setups where both sides of a flow are not tagged with the same VLAN +# tag, we can ignore the VLAN id's in the flow hashing. +vlan: + use-for-tracking: true + +# This option controls the use of livedev ids in the flow (and defrag) +# hashing. This is enabled by default and should be disabled if +# multiple live devices are used to capture traffic from the same network +livedev: + use-for-tracking: true + +# Specific timeouts for flows. Here you can specify the timeouts that the +# active flows will wait to transit from the current state to another, on each +# protocol. The value of "new" determines the seconds to wait after a handshake or +# stream startup before the engine frees the data of that flow it doesn't +# change the state to established (usually if we don't receive more packets +# of that flow). The value of "established" is the amount of +# seconds that the engine will wait to free the flow if that time elapses +# without receiving new packets or closing the connection. "closed" is the +# amount of time to wait after a flow is closed (usually zero). "bypassed" +# timeout controls locally bypassed flows. For these flows we don't do any other +# tracking. If no packets have been seen after this timeout, the flow is discarded. +# +# There's an emergency mode that will become active under attack circumstances, +# making the engine to check flow status faster. This configuration variables +# use the prefix "emergency-" and work similar as the normal ones. +# Some timeouts doesn't apply to all the protocols, like "closed", for udp and +# icmp. + +flow-timeouts: + + default: + new: 30 + established: 300 + closed: 0 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-closed: 0 + emergency-bypassed: 50 + tcp: + new: 60 + established: 600 + closed: 60 + bypassed: 100 + emergency-new: 5 + emergency-established: 100 + emergency-closed: 10 + emergency-bypassed: 50 + udp: + new: 30 + established: 300 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-bypassed: 50 + icmp: + new: 30 + established: 300 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-bypassed: 50 + +# Stream engine settings. Here the TCP stream tracking and reassembly +# engine is configured. +# +# stream: +# memcap: 64mb # Can be specified in kb, mb, gb. Just a +# # number indicates it's in bytes. +# memcap-policy: ignore # Can be "drop-flow", "pass-flow", "bypass", +# # "drop-packet", "pass-packet", "reject" or +# # "ignore" default is "ignore" +# checksum-validation: yes # To validate the checksum of received +# # packet. If csum validation is specified as +# # "yes", then packets with invalid csum values will not +# # be processed by the engine stream/app layer. +# # Warning: locally generated traffic can be +# # generated without checksum due to hardware offload +# # of checksum. You can control the handling of checksum +# # on a per-interface basis via the 'checksum-checks' +# # option +# prealloc-sessions: 2048 # 2k sessions prealloc'd per stream thread +# midstream: false # don't allow midstream session pickups +# midstream-policy: ignore # Can be "drop-flow", "pass-flow", "bypass", +# # "drop-packet", "pass-packet", "reject" or +# # "ignore" default is "ignore" +# async-oneside: false # don't enable async stream handling +# inline: no # stream inline mode +# drop-invalid: yes # in inline mode, drop packets that are invalid with regards to streaming engine +# max-syn-queued: 10 # Max different SYNs to queue +# max-synack-queued: 5 # Max different SYN/ACKs to queue +# bypass: no # Bypass packets when stream.reassembly.depth is reached. +# # Warning: first side to reach this triggers +# # the bypass. +# liberal-timestamps: false # Treat all timestamps as if the Linux policy applies. This +# # means it's slightly more permissive. Enabled by default. +# +# reassembly: +# memcap: 256mb # Can be specified in kb, mb, gb. Just a number +# # indicates it's in bytes. +# memcap-policy: ignore # Can be "drop-flow", "pass-flow", "bypass", +# # "drop-packet", "pass-packet", "reject" or +# # "ignore" default is "ignore" +# depth: 1mb # Can be specified in kb, mb, gb. Just a number +# # indicates it's in bytes. +# toserver-chunk-size: 2560 # inspect raw stream in chunks of at least +# # this size. Can be specified in kb, mb, +# # gb. Just a number indicates it's in bytes. +# toclient-chunk-size: 2560 # inspect raw stream in chunks of at least +# # this size. Can be specified in kb, mb, +# # gb. Just a number indicates it's in bytes. +# randomize-chunk-size: yes # Take a random value for chunk size around the specified value. +# # This lowers the risk of some evasion techniques but could lead +# # to detection change between runs. It is set to 'yes' by default. +# randomize-chunk-range: 10 # If randomize-chunk-size is active, the value of chunk-size is +# # a random value between (1 - randomize-chunk-range/100)*toserver-chunk-size +# # and (1 + randomize-chunk-range/100)*toserver-chunk-size and the same +# # calculation for toclient-chunk-size. +# # Default value of randomize-chunk-range is 10. +# +# raw: yes # 'Raw' reassembly enabled or disabled. +# # raw is for content inspection by detection +# # engine. +# +# segment-prealloc: 2048 # number of segments preallocated per thread +# +# check-overlap-different-data: true|false +# # check if a segment contains different data +# # than what we've already seen for that +# # position in the stream. +# # This is enabled automatically if inline mode +# # is used or when stream-event:reassembly_overlap_different_data; +# # is used in a rule. +# +stream: + memcap: 64mb + #memcap-policy: ignore + checksum-validation: yes # reject incorrect csums + #midstream: false + #midstream-policy: ignore + inline: auto # auto will use inline mode in IPS mode, yes or no set it statically + reassembly: + memcap: 256mb + #memcap-policy: ignore + depth: 50mb # reassemble 1mb into a stream + toserver-chunk-size: 2560 + toclient-chunk-size: 2560 + randomize-chunk-size: yes + #randomize-chunk-range: 10 + #raw: yes + #segment-prealloc: 2048 + #check-overlap-different-data: true + +# Host table: +# +# Host table is used by the tagging and per host thresholding subsystems. +# +host: + hash-size: 4096 + prealloc: 1000 + memcap: 32mb + +# IP Pair table: +# +# Used by xbits 'ippair' tracking. +# +#ippair: +# hash-size: 4096 +# prealloc: 1000 +# memcap: 32mb + +# Decoder settings + +decoder: + # Teredo decoder is known to not be completely accurate + # as it will sometimes detect non-teredo as teredo. + teredo: + enabled: true + # ports to look for Teredo. Max 4 ports. If no ports are given, or + # the value is set to 'any', Teredo detection runs on _all_ UDP packets. + ports: $TEREDO_PORTS # syntax: '[3544, 1234]' or '3533' or 'any'. + + # VXLAN decoder is assigned to up to 4 UDP ports. By default only the + # IANA assigned port 4789 is enabled. + vxlan: + enabled: true + ports: $VXLAN_PORTS # syntax: '[8472, 4789]' or '4789'. + + # Geneve decoder is assigned to up to 4 UDP ports. By default only the + # IANA assigned port 6081 is enabled. + geneve: + enabled: true + ports: $GENEVE_PORTS # syntax: '[6081, 1234]' or '6081'. + + # maximum number of decoder layers for a packet + # max-layers: 16 + +## +## Performance tuning and profiling +## + +# The detection engine builds internal groups of signatures. The engine +# allows us to specify the profile to use for them, to manage memory in an +# efficient way keeping good performance. For the profile keyword you +# can use the words "low", "medium", "high" or "custom". If you use custom, +# make sure to define the values in the "custom-values" section. +# Usually you would prefer medium/high/low. +# +# "sgh mpm-context", indicates how the staging should allot mpm contexts for +# the signature groups. "single" indicates the use of a single context for +# all the signature group heads. "full" indicates a mpm-context for each +# group head. "auto" lets the engine decide the distribution of contexts +# based on the information the engine gathers on the patterns from each +# group head. +# +# The option inspection-recursion-limit is used to limit the recursive calls +# in the content inspection code. For certain payload-sig combinations, we +# might end up taking too much time in the content inspection code. +# If the argument specified is 0, the engine uses an internally defined +# default limit. When a value is not specified, there are no limits on the recursion. +detect: + profile: medium + custom-values: + toclient-groups: 3 + toserver-groups: 25 + sgh-mpm-context: auto + inspection-recursion-limit: 3000 + # If set to yes, the loading of signatures will be made after the capture + # is started. This will limit the downtime in IPS mode. + #delayed-detect: yes + + prefilter: + # default prefiltering setting. "mpm" only creates MPM/fast_pattern + # engines. "auto" also sets up prefilter engines for other keywords. + # Use --list-keywords=all to see which keywords support prefiltering. + default: mpm + + # the grouping values above control how many groups are created per + # direction. Port whitelisting forces that port to get its own group. + # Very common ports will benefit, as well as ports with many expensive + # rules. + grouping: + #tcp-whitelist: 53, 80, 139, 443, 445, 1433, 3306, 3389, 6666, 6667, 8080 + #udp-whitelist: 53, 135, 5060 + + profiling: + # Log the rules that made it past the prefilter stage, per packet + # default is off. The threshold setting determines how many rules + # must have made it past pre-filter for that rule to trigger the + # logging. + #inspect-logging-threshold: 200 + grouping: + dump-to-disk: false + include-rules: false # very verbose + include-mpm-stats: false + +# Select the multi pattern algorithm you want to run for scan/search the +# in the engine. +# +# The supported algorithms are: +# "ac" - Aho-Corasick, default implementation +# "ac-bs" - Aho-Corasick, reduced memory implementation +# "ac-ks" - Aho-Corasick, "Ken Steele" variant +# "hs" - Hyperscan, available when built with Hyperscan support +# +# The default mpm-algo value of "auto" will use "hs" if Hyperscan is +# available, "ac" otherwise. +# +# The mpm you choose also decides the distribution of mpm contexts for +# signature groups, specified by the conf - "detect.sgh-mpm-context". +# Selecting "ac" as the mpm would require "detect.sgh-mpm-context" +# to be set to "single", because of ac's memory requirements, unless the +# ruleset is small enough to fit in memory, in which case one can +# use "full" with "ac". The rest of the mpms can be run in "full" mode. + +mpm-algo: auto + +# Select the matching algorithm you want to use for single-pattern searches. +# +# Supported algorithms are "bm" (Boyer-Moore) and "hs" (Hyperscan, only +# available if Suricata has been built with Hyperscan support). +# +# The default of "auto" will use "hs" if available, otherwise "bm". + +spm-algo: auto + +# Suricata is multi-threaded. Here the threading can be influenced. +threading: + set-cpu-affinity: no + # Tune cpu affinity of threads. Each family of threads can be bound + # to specific CPUs. + # + # These 2 apply to the all runmodes: + # management-cpu-set is used for flow timeout handling, counters + # worker-cpu-set is used for 'worker' threads + # + # Additionally, for autofp these apply: + # receive-cpu-set is used for capture threads + # verdict-cpu-set is used for IPS verdict threads + # + cpu-affinity: + - management-cpu-set: + cpu: [ 0 ] # include only these CPUs in affinity settings + - receive-cpu-set: + cpu: [ 0 ] # include only these CPUs in affinity settings + - worker-cpu-set: + cpu: [ "all" ] + mode: "exclusive" + # Use explicitly 3 threads and don't compute number by using + # detect-thread-ratio variable: + # threads: 3 + prio: + low: [ 0 ] + medium: [ "1-2" ] + high: [ 3 ] + default: "medium" + #- verdict-cpu-set: + # cpu: [ 0 ] + # prio: + # default: "high" + # + # By default Suricata creates one "detect" thread per available CPU/CPU core. + # This setting allows controlling this behaviour. A ratio setting of 2 will + # create 2 detect threads for each CPU/CPU core. So for a dual core CPU this + # will result in 4 detect threads. If values below 1 are used, less threads + # are created. So on a dual core CPU a setting of 0.5 results in 1 detect + # thread being created. Regardless of the setting at a minimum 1 detect + # thread will always be created. + # + detect-thread-ratio: 1.0 + # + # By default, the per-thread stack size is left to its default setting. If + # the default thread stack size is too small, use the following configuration + # setting to change the size. Note that if any thread's stack size cannot be + # set to this value, a fatal error occurs. + # + # Generally, the per-thread stack-size should not exceed 8MB. + #stack-size: 8mb + +# Luajit has a strange memory requirement, its 'states' need to be in the +# first 2G of the process' memory. +# +# 'luajit.states' is used to control how many states are preallocated. +# State use: per detect script: 1 per detect thread. Per output script: 1 per +# script. +luajit: + states: 128 + +# Profiling settings. Only effective if Suricata has been built with +# the --enable-profiling configure flag. +# +profiling: + # Run profiling for every X-th packet. The default is 1, which means we + # profile every packet. If set to 1024, one packet is profiled for every + # 1024 received. The sample rate must be a power of 2. + #sample-rate: 1024 + + # rule profiling + rules: + + # Profiling can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: yes + filename: rule_perf.log + append: yes + + # Sort options: ticks, avgticks, checks, matches, maxticks + # If commented out all the sort options will be used. + #sort: avgticks + + # Limit the number of sids for which stats are shown at exit (per sort). + limit: 10 + + # output to json + json: yes + + # per keyword profiling + keywords: + enabled: yes + filename: keyword_perf.log + append: yes + + prefilter: + enabled: yes + filename: prefilter_perf.log + append: yes + + # per rulegroup profiling + rulegroups: + enabled: yes + filename: rule_group_perf.log + append: yes + + # packet profiling + packets: + + # Profiling can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: yes + filename: packet_stats.log + append: yes + + # per packet csv output + csv: + + # Output can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: no + filename: packet_stats.csv + + # profiling of locking. Only available when Suricata was built with + # --enable-profiling-locks. + locks: + enabled: no + filename: lock_stats.log + append: yes + + pcap-log: + enabled: no + filename: pcaplog_stats.log + append: yes + +## +## Netfilter integration +## + +# When running in NFQ inline mode, it is possible to use a simulated +# non-terminal NFQUEUE verdict. +# This permits sending all needed packet to Suricata via this rule: +# iptables -I FORWARD -m mark ! --mark $MARK/$MASK -j NFQUEUE +# And below, you can have your standard filtering ruleset. To activate +# this mode, you need to set mode to 'repeat' +# If you want a packet to be sent to another queue after an ACCEPT decision +# set the mode to 'route' and set next-queue value. +# On Linux >= 3.1, you can set batchcount to a value > 1 to improve performance +# by processing several packets before sending a verdict (worker runmode only). +# On Linux >= 3.6, you can set the fail-open option to yes to have the kernel +# accept the packet if Suricata is not able to keep pace. +# bypass mark and mask can be used to implement NFQ bypass. If bypass mark is +# set then the NFQ bypass is activated. Suricata will set the bypass mark/mask +# on packet of a flow that need to be bypassed. The Netfilter ruleset has to +# directly accept all packets of a flow once a packet has been marked. +nfq: +# mode: accept +# repeat-mark: 1 +# repeat-mask: 1 +# bypass-mark: 1 +# bypass-mask: 1 +# route-queue: 2 +# batchcount: 20 +# fail-open: yes + +#nflog support +nflog: + # netlink multicast group + # (the same as the iptables --nflog-group param) + # Group 0 is used by the kernel, so you can't use it + - group: 2 + # netlink buffer size + buffer-size: 18432 + # put default value here + - group: default + # set number of packets to queue inside kernel + qthreshold: 1 + # set the delay before flushing packet in the kernel's queue + qtimeout: 100 + # netlink max buffer size + max-size: 20000 + +## +## Advanced Capture Options +## + +# General settings affecting packet capture +capture: + # disable NIC offloading. It's restored when Suricata exits. + # Enabled by default. + #disable-offloading: false + # + # disable checksum validation. Same as setting '-k none' on the + # command-line. + #checksum-validation: none + +# Netmap support +# +# Netmap operates with NIC directly in driver, so you need FreeBSD 11+ which has +# built-in Netmap support or compile and install the Netmap module and appropriate +# NIC driver for your Linux system. +# To reach maximum throughput disable all receive-, segmentation-, +# checksum- offloading on your NIC (using ethtool or similar). +# Disabling TX checksum offloading is *required* for connecting OS endpoint +# with NIC endpoint. +# You can find more information at https://github.com/luigirizzo/netmap +# +netmap: + # To specify OS endpoint add plus sign at the end (e.g. "eth0+") + - interface: eth2 + # Number of capture threads. "auto" uses number of RSS queues on interface. + # Warning: unless the RSS hashing is symmetrical, this will lead to + # accuracy issues. + #threads: auto + # You can use the following variables to activate netmap tap or IPS mode. + # If copy-mode is set to ips or tap, the traffic coming to the current + # interface will be copied to the copy-iface interface. If 'tap' is set, the + # copy is complete. If 'ips' is set, the packet matching a 'drop' action + # will not be copied. + # To specify the OS as the copy-iface (so the OS can route packets, or forward + # to a service running on the same machine) add a plus sign at the end + # (e.g. "copy-iface: eth0+"). Don't forget to set up a symmetrical eth0+ -> eth0 + # for return packets. Hardware checksumming must be *off* on the interface if + # using an OS endpoint (e.g. 'ifconfig eth0 -rxcsum -txcsum -rxcsum6 -txcsum6' for FreeBSD + # or 'ethtool -K eth0 tx off rx off' for Linux). + #copy-mode: tap + #copy-iface: eth3 + # Set to yes to disable promiscuous mode + # disable-promisc: no + # Choose checksum verification mode for the interface. At the moment + # of the capture, some packets may have an invalid checksum due to + # the checksum computation being offloaded to the network card. + # Possible values are: + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: Suricata uses a statistical approach to detect when + # checksum off-loading is used. + # Warning: 'checksum-validation' must be set to yes to have any validation + #checksum-checks: auto + # BPF filter to apply to this interface. The pcap filter syntax apply here. + #bpf-filter: port 80 or udp + #- interface: eth3 + #threads: auto + #copy-mode: tap + #copy-iface: eth2 + # Put default values here + - interface: default + +# PF_RING configuration: for use with native PF_RING support +# for more info see http://www.ntop.org/products/pf_ring/ +pfring: + - interface: eth0 + # Number of receive threads. If set to 'auto' Suricata will first try + # to use CPU (core) count and otherwise RSS queue count. + threads: auto + + # Default clusterid. PF_RING will load balance packets based on flow. + # All threads/processes that will participate need to have the same + # clusterid. + cluster-id: 99 + + # Default PF_RING cluster type. PF_RING can load balance per flow. + # Possible values are: + # - cluster_flow: 6-tuple: + # - cluster_inner_flow: 6-tuple: + # - cluster_inner_flow_2_tuple: 2-tuple: + # - cluster_inner_flow_4_tuple: 4-tuple: + # - cluster_inner_flow_5_tuple: 5-tuple: + # - cluster_round_robin (NOT RECOMMENDED) + cluster-type: cluster_flow + + # bpf filter for this interface + #bpf-filter: tcp + + # If bypass is set then the PF_RING hw bypass is activated, when supported + # by the network interface. Suricata will instruct the interface to bypass + # all future packets for a flow that need to be bypassed. + #bypass: yes + + # Choose checksum verification mode for the interface. At the moment + # of the capture, some packets may have an invalid checksum due to + # the checksum computation being offloaded to the network card. + # Possible values are: + # - rxonly: only compute checksum for packets received by network card. + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: Suricata uses a statistical approach to detect when + # checksum off-loading is used. (default) + # Warning: 'checksum-validation' must be set to yes to have any validation + #checksum-checks: auto + # Second interface + #- interface: eth1 + # threads: 3 + # cluster-id: 93 + # cluster-type: cluster_flow + # Put default values here + - interface: default + #threads: 2 + +# For FreeBSD ipfw(8) divert(4) support. +# Please make sure you have ipfw_load="YES" and ipdivert_load="YES" +# in /etc/loader.conf or kldload'ing the appropriate kernel modules. +# Additionally, you need to have an ipfw rule for the engine to see +# the packets from ipfw. For Example: +# +# ipfw add 100 divert 8000 ip from any to any +# +# N.B. This example uses "8000" -- this number must mach the values +# you passed on the command line, i.e., -d 8000 +# +ipfw: + + # Reinject packets at the specified ipfw rule number. This config + # option is the ipfw rule number AT WHICH rule processing continues + # in the ipfw processing system after the engine has finished + # inspecting the packet for acceptance. If no rule number is specified, + # accepted packets are reinjected at the divert rule which they entered + # and IPFW rule processing continues. No check is done to verify + # this will rule makes sense so care must be taken to avoid loops in ipfw. + # + ## The following example tells the engine to reinject packets + # back into the ipfw firewall AT rule number 5500: + # + # ipfw-reinjection-rule-number: 5500 + + +napatech: + # When use_all_streams is set to "yes" the initialization code will query + # the Napatech service for all configured streams and listen on all of them. + # When set to "no" the streams config array will be used. + # + # This option necessitates running the appropriate NTPL commands to create + # the desired streams prior to running Suricata. + #use-all-streams: no + + # The streams to listen on when auto-config is disabled or when and threading + # cpu-affinity is disabled. This can be either: + # an individual stream (e.g. streams: [0]) + # or + # a range of streams (e.g. streams: ["0-3"]) + # + streams: ["0-3"] + + # Stream stats can be enabled to provide fine grain packet and byte counters + # for each thread/stream that is configured. + # + enable-stream-stats: no + + # When auto-config is enabled the streams will be created and assigned + # automatically to the NUMA node where the thread resides. If cpu-affinity + # is enabled in the threading section. Then the streams will be created + # according to the number of worker threads specified in the worker-cpu-set. + # Otherwise, the streams array is used to define the streams. + # + # This option is intended primarily to support legacy configurations. + # + # This option cannot be used simultaneously with either "use-all-streams" + # or "hardware-bypass". + # + auto-config: yes + + # Enable hardware level flow bypass. + # + hardware-bypass: yes + + # Enable inline operation. When enabled traffic arriving on a given port is + # automatically forwarded out its peer port after analysis by Suricata. + # + inline: no + + # Ports indicates which Napatech ports are to be used in auto-config mode. + # these are the port IDs of the ports that will be merged prior to the + # traffic being distributed to the streams. + # + # When hardware-bypass is enabled the ports must be configured as a segment. + # specify the port(s) on which upstream and downstream traffic will arrive. + # This information is necessary for the hardware to properly process flows. + # + # When using a tap configuration one of the ports will receive inbound traffic + # for the network and the other will receive outbound traffic. The two ports on a + # given segment must reside on the same network adapter. + # + # When using a SPAN-port configuration the upstream and downstream traffic + # arrives on a single port. This is configured by setting the two sides of the + # segment to reference the same port. (e.g. 0-0 to configure a SPAN port on + # port 0). + # + # port segments are specified in the form: + # ports: [0-1,2-3,4-5,6-6,7-7] + # + # For legacy systems when hardware-bypass is disabled this can be specified in any + # of the following ways: + # + # a list of individual ports (e.g. ports: [0,1,2,3]) + # + # a range of ports (e.g. ports: [0-3]) + # + # "all" to indicate that all ports are to be merged together + # (e.g. ports: [all]) + # + # This parameter has no effect if auto-config is disabled. + # + ports: [0-1,2-3] + + # When auto-config is enabled the hashmode specifies the algorithm for + # determining to which stream a given packet is to be delivered. + # This can be any valid Napatech NTPL hashmode command. + # + # The most common hashmode commands are: hash2tuple, hash2tuplesorted, + # hash5tuple, hash5tuplesorted and roundrobin. + # + # See Napatech NTPL documentation other hashmodes and details on their use. + # + # This parameter has no effect if auto-config is disabled. + # + hashmode: hash5tuplesorted + +## +## Configure Suricata to load Suricata-Update managed rules. +## + +default-rule-path: /var/lib/suricata/rules + +rule-files: + - suricata.rules + +## +## Auxiliary configuration files. +## + +classification-file: /dev/null +reference-config-file: /dev/null +threshold-file: /dev/null + +## +## Include other configs +## + +# Includes: Files included here will be handled as if they were in-lined +# in this configuration file. Files with relative pathnames will be +# searched for in the same directory as this configuration file. You may +# use absolute pathnames too. +#include: +# - include1.yaml +# - include2.yaml diff --git a/webapp/.dockerignore b/webapp/.dockerignore new file mode 100644 index 0000000..aa06303 --- /dev/null +++ b/webapp/.dockerignore @@ -0,0 +1,5 @@ +__pycache__ +.env +.ruff_cache +database +grafana diff --git a/webapp/Dockerfile b/webapp/Dockerfile new file mode 100644 index 0000000..73f8299 --- /dev/null +++ b/webapp/Dockerfile @@ -0,0 +1,9 @@ +# Copyright (C) 2023 ANSSI +# SPDX-License-Identifier: GPL-3.0-only + +FROM alpine:20230329 +RUN apk add --no-cache py3-jinja2 py3-uvloop && \ + apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing py3-aiosqlite py3-starlette uvicorn +COPY . /webapp +WORKDIR /webapp +CMD ["uvicorn", "--host", "0.0.0.0", "main:app"] diff --git a/webapp/database.py b/webapp/database.py new file mode 100644 index 0000000..de0c77f --- /dev/null +++ b/webapp/database.py @@ -0,0 +1,314 @@ +# Copyright (C) 2023 ANSSI +# SPDX-License-Identifier: GPL-3.0-only + +import asyncio +import ipaddress +import json +import re +import traceback +from functools import lru_cache + +import aiosqlite + +# List of possible application-layer protocols in Suricata +# suricata -c suricata.yaml --list-app-layer-protos +SUPPORTED_PROTOCOLS = [ + "bittorrent-dht", + "dcerpc", + "dhcp", + "dnp3", + "dns", + "enip", + "ftp", + "ftp-data", + "http", + "http2", + "ike", + "ikev2", + "imap", + "krb5", + "modbus", + "mqtt", + "nfs", + "ntp", + "pgsql", + "quic", + "rdp", + "rfb", + "sip", + "smb", + "smtp", + "snmp", + "ssh", + "telnet", + "tftp", + "tls", +] + +# Collect pcap filename for each flow +# This is an ugly hack to circumvent an upstream issue in Suricata +flow_pcap: dict = {} + + +@lru_cache +def sc_ip_format(sc_ipaddr: str) -> str: + ip = ipaddress.ip_address(sc_ipaddr) + if ip.version == 6: + return f"[{ip.compressed}]" + else: + return f"{ip.compressed}" + + +async def load_event(con, line: bytes) -> None: + """ + Add one event from eve.json to the SQL database + Use regex rather than JSON parsing for performance reasons. + """ + event_type = re.search(rb"\"event_type\":\"([^\"]+)\"", line).group(1).decode() + if event_type == "flow": + src_ip = re.search(rb"\"src_ip\":\"([^\"]+)\"", line).group(1).decode() + dest_ip = re.search(rb"\"dest_ip\":\"([^\"]+)\"", line).group(1).decode() + app_proto_m = re.search(rb"\"app_proto\":\"([^\"]+)\"", line) + app_proto = app_proto_m.group(1).decode() if app_proto_m else None + flow_id = re.search(rb"\"flow_id\":(\d+)", line).group(1).decode() + pcap_filename = re.search(rb"\"pcap_filename\":\"([^\"]+)\"", line).group(1) + assert not app_proto or app_proto in SUPPORTED_PROTOCOLS + [ + "failed" + ], f"app_proto refers to an unsupported protocol: {app_proto}" + pcap_filename = flow_pcap.pop(flow_id, pcap_filename) + await con.execute( + "INSERT OR IGNORE INTO flow (id, src_ip, src_port, " + "dest_ip, dest_port, pcap_filename, proto, app_proto, " + "extra_data) " + "values(?1->>'flow_id', ?2, ?1->>'src_port', ?3, " + "?1->>'dest_port', ?4, ?1->>'proto', ?1->>'app_proto', ?1->'flow')", + ( + line, + sc_ip_format(src_ip), + sc_ip_format(dest_ip), + pcap_filename.decode(), + ), + ) + elif event_type in ["alert", "anomaly", "fileinfo"] + SUPPORTED_PROTOCOLS: + # Collect pcap_filename + flow_id = re.search(rb"\"flow_id\":(\d+)", line).group(1).decode() + pcap_filename = re.search(rb"\"pcap_filename\":\"([^\"]+)\"", line).group(1) + flow_pcap[flow_id] = pcap_filename + + # Insert event + await con.execute( + f"INSERT OR IGNORE INTO '{event_type}' (flow_id, extra_data) " + f"values(?1->>'flow_id', ?1->'{event_type}')", + (line,), + ) + + +class Database: + def __init__(self, database_uri: str) -> None: + self.database_uri = database_uri + self.con = None + + async def connect(self): + self.con = await aiosqlite.connect(self.database_uri, uri=True) + self.con.row_factory = aiosqlite.Row + # WAL journal mode allows multiple concurrent readers + await self.con.execute("PRAGMA journal_mode=wal") + await self.con.execute("PRAGMA synchronous=normal") + await self.init_database_structure() + + async def close(self): + assert self.con is not None, "database connection closed" + await self.con.close() + + async def execute(self, *args, **kwargs): + assert self.con is not None, "database connection closed" + return await self.con.execute(*args, **kwargs) + + async def init_database_structure(self): + assert self.con is not None, "database connection closed" + # TODO: when SQLite 3.42 is broadly available, use UNIXEPOCH('subsec') + await self.con.executescript( + """ + CREATE TABLE IF NOT EXISTS ctf_config ( + id INTEGER PRIMARY KEY, + start_date TEXT, + ts_start INTEGER GENERATED ALWAYS + AS ((JULIANDAY(start_date) - 2440587.5) * 86400000), + tick_length INTEGER, + services TEXT + ); + CREATE TABLE IF NOT EXISTS checkpoint ( + id INTEGER PRIMARY KEY, + eve_idx INTEGER, + tcp_idx INTEGER, + udp_idx INTEGER + ); + CREATE TABLE IF NOT EXISTS flow ( + id INTEGER NOT NULL PRIMARY KEY, + ts_start INTEGER GENERATED ALWAYS + AS ((JULIANDAY(SUBSTR((extra_data->>'start'), 1, 26)) + - 2440587.5) * 86400000) STORED, + ts_end INTEGER GENERATED ALWAYS + AS ((JULIANDAY(SUBSTR((extra_data->>'end'), 1, 26)) + - 2440587.5) * 86400000) STORED, + src_ip TEXT NOT NULL, + src_port INTEGER, + src_ipport TEXT GENERATED ALWAYS + AS (src_ip || ':' || IFNULL(src_port, 'None')), + dest_ip TEXT NOT NULL, + dest_port INTEGER, + dest_ipport TEXT GENERATED ALWAYS + AS (dest_ip || ':' || IFNULL(dest_port, 'None')), + pcap_filename TEXT, + proto TEXT NOT NULL, + app_proto TEXT, + extra_data TEXT + ); + CREATE TABLE IF NOT EXISTS alert ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + flow_id INTEGER NOT NULL, + tag TEXT GENERATED ALWAYS + AS (extra_data->>'metadata.tag[0]') STORED, + color TEXT GENERATED ALWAYS + AS (extra_data->>'metadata.color[0]') STORED, + extra_data TEXT, + FOREIGN KEY(flow_id) REFERENCES flow (id), + UNIQUE(flow_id, tag) + ); + CREATE TABLE IF NOT EXISTS raw ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + flow_id INTEGER NOT NULL, + count INTEGER, + server_to_client INTEGER, + sha256 TEXT, + FOREIGN KEY(flow_id) REFERENCES flow (id), + UNIQUE(flow_id, count) + ); + """ + ) + for e in ["anomaly", "fileinfo"] + SUPPORTED_PROTOCOLS: + await self.con.execute( + f""" + CREATE TABLE IF NOT EXISTS "{e}" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + flow_id INTEGER NOT NULL, + timestamp INTEGER GENERATED ALWAYS + AS ((JULIANDAY(SUBSTR((extra_data->>'timestamp'), 1, 26)) + - 2440587.5) * 86400000) STORED, + extra_data TEXT, + FOREIGN KEY(flow_id) REFERENCES flow (id), + UNIQUE(flow_id, timestamp) + ); + """ + ) + + # Create indexes for non-unique values + await self.con.executescript( + 'CREATE INDEX IF NOT EXISTS "flow_ts_start_idx" ON flow(ts_start);' + 'CREATE INDEX IF NOT EXISTS "flow_app_proto_idx" ON flow(app_proto);' + 'CREATE INDEX IF NOT EXISTS "flow_src_ipport_idx" ON flow(src_ipport);' + 'CREATE INDEX IF NOT EXISTS "flow_dest_ipport_idx" ON flow(dest_ipport);' + 'CREATE INDEX IF NOT EXISTS "alert_tag_idx" ON alert(tag);' + ) + for e in ["alert", "anomaly", "fileinfo", "raw"] + SUPPORTED_PROTOCOLS: + await self.con.execute( + f'CREATE INDEX IF NOT EXISTS "{e}_flow_id_idx" ON "{e}"(flow_id);' + ) + + async def update_ctf_config(self, ctf_config: dict): + """ + Update database with configuration given through env vars (for Grafana). + """ + assert self.con is not None, "database connection closed" + await self.con.execute("BEGIN TRANSACTION") + services = json.dumps(ctf_config["services"]) + await self.con.execute( + "INSERT OR REPLACE INTO ctf_config (id, start_date, tick_length, services) " + "values(1, ?, ?, ?)", + (ctf_config["start_date"], ctf_config["tick_length"], services), + ) + await self.con.execute("COMMIT") + + async def fill_database(self): + assert self.con is not None, "database connection closed" + await self.con.execute("BEGIN TRANSACTION") + cursor = await self.con.execute( + "SELECT eve_idx, tcp_idx, udp_idx FROM checkpoint" + ) + eve_idx, tcp_idx, udp_idx = await cursor.fetchone() or [0, 0, 0] + if eve_idx == 0: + print("Starting initial eve.json import, please be patient...") + + # eve.json contains one event per line + with open("../suricata/output/eve.json", "rb") as f: + f.seek(eve_idx) + line_count = 0 + for line in f: + if not line: + break + try: + await load_event(self.con, line) + except (AttributeError, aiosqlite.OperationalError): + break # eve.json ends with a partial JSON + eve_idx += len(line) + line_count += 1 + if line_count: + print(f"{line_count} events loaded from eve.json", flush=True) + + with open("../suricata/output/tcpstore.log", "r") as f: + f.seek(tcp_idx) + line_count = 0 + for line in f: + if not line or not line.endswith("\n"): + break + flow_id, count, server_to_client, h = line.strip().split(",") + await self.con.execute( + "INSERT OR IGNORE INTO raw (flow_id, count, server_to_client, " + "sha256) values(?, ?, ?, ?)", + (flow_id, int(count), int(server_to_client), h), + ) + tcp_idx += len(line) + line_count += 1 + if line_count: + print(f"{line_count} chunks loaded from tcpstore.log", flush=True) + + with open("../suricata/output/udpstore.log", "r") as f: + f.seek(udp_idx) + line_count = 0 + for line in f: + if not line or not line.endswith("\n"): + break + flow_id, count, server_to_client, h = line.strip().split(",") + await self.con.execute( + "INSERT OR IGNORE INTO raw (flow_id, count, server_to_client, " + "sha256) values(?, ?, ?, ?)", + (flow_id, int(count), int(server_to_client), h), + ) + udp_idx += len(line) + line_count += 1 + if line_count: + print(f"{line_count} chunks loaded from udpstore.log", flush=True) + + await self.con.execute( + "INSERT OR REPLACE INTO checkpoint (id, eve_idx, tcp_idx, udp_idx) " + "values(1, ?, ?, ?)", + (eve_idx, tcp_idx, udp_idx), + ) + await self.con.execute("COMMIT") + + async def importer_task(self): + """ + Load events from eve.json and fill SQL database + """ + while True: + try: + await self.fill_database() + except FileNotFoundError: + await self.con.execute("ROLLBACK") + print("Suricata output not found, retrying in 1s") + except Exception: + print(traceback.format_exc(), flush=True) + return + + # Sleeping 1 second before trying to pull new data again + await asyncio.sleep(1) diff --git a/webapp/main.py b/webapp/main.py new file mode 100644 index 0000000..db77330 --- /dev/null +++ b/webapp/main.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +# Copyright (C) 2023 ANSSI +# SPDX-License-Identifier: GPL-3.0-only + +import asyncio +import contextlib +import json + +from starlette.applications import Starlette +from starlette.config import Config +from starlette.datastructures import CommaSeparatedStrings +from starlette.exceptions import HTTPException +from starlette.responses import JSONResponse +from starlette.routing import Mount, Route +from starlette.staticfiles import StaticFiles +from starlette.templating import Jinja2Templates + +from database import Database + + +def row_to_dict(row) -> dict: + row = dict(row) + extra_data = json.loads(row.pop("extra_data")) + row.update(extra_data) + return row + + +async def index(request): + context = { + "request": request, + "ctf_config": CTF_CONFIG, + } + return templates.TemplateResponse("index.html", context) + + +async def api_flow_list(request): + # Parse GET arguments + ts_to = request.query_params.get("to", str(int(1e10))) + services = request.query_params.getlist("service") + app_proto = request.query_params.get("app_proto") + tags = request.query_params.getlist("tag") + if not ts_to.isnumeric(): + raise HTTPException(400) + + # Query flows and associated tags using filters + query = """ + WITH fsrvs AS (SELECT value FROM json_each(?1)), + ftags AS (SELECT value FROM json_each(?2)) + SELECT id, ts_start, ts_end, dest_ipport, app_proto, + (SELECT GROUP_CONCAT(tag) FROM alert WHERE flow_id = flow.id) AS tags + FROM flow WHERE ts_start <= ?3 AND (?4 IS NULL OR app_proto = ?4) + """ + if services == ["!"]: + # Filter flows related to no services + query += "AND NOT (src_ipport IN fsrvs OR dest_ipport IN fsrvs)" + services = sum(CTF_CONFIG["services"].values(), []) + elif services: + query += "AND (src_ipport IN fsrvs OR dest_ipport IN fsrvs)" + if tags: + # Relational division to get all flow_id matching all chosen tags + query += """ + AND flow.id IN ( + SELECT flow_id FROM alert WHERE tag IN ftags GROUP BY flow_id + HAVING COUNT(*) = (SELECT COUNT(*) FROM ftags) + ) + """ + query += " ORDER BY ts_start DESC LIMIT 100" + + cursor = await database.execute( + query, (json.dumps(services), json.dumps(tags), int(ts_to) * 1000, app_proto) + ) + rows = await cursor.fetchall() + flows = [dict(row) for row in rows] + + # Fetch application protocols + cursor = await database.execute("SELECT DISTINCT app_proto FROM flow") + rows = await cursor.fetchall() + prs = [r["app_proto"] for r in rows if r["app_proto"] not in [None, "failed"]] + + # Fetch tags + cursor = await database.execute( + "SELECT tag, color FROM alert GROUP BY tag ORDER BY color" + ) + rows = await cursor.fetchall() + tags = [dict(row) for row in rows] + + return JSONResponse( + { + "flows": flows, + "appProto": prs, + "tags": tags, + } + ) + + +async def api_flow_get(request): + flow_id = request.path_params["flow_id"] + + # Query flow from database + cursor = await database.execute( + ( + "SELECT id, ts_start, ts_end, src_ipport, dest_ipport, dest_port, " + "pcap_filename, proto, app_proto, extra_data " + "FROM flow WHERE id = ?" + ), + [flow_id], + ) + flow = await cursor.fetchone() + if not flow: + raise HTTPException(404) + result = {"flow": row_to_dict(flow)} + app_proto = result["flow"].get("app_proto") + + # Get associated fileinfo + # See https://docs.suricata.io/en/suricata-6.0.9/file-extraction/file-extraction.html + if app_proto in ["http", "http2", "smtp", "ftp", "nfs", "smb"]: + cursor = await database.execute( + "SELECT extra_data FROM fileinfo WHERE flow_id = ? ORDER BY id", [flow_id] + ) + rows = await cursor.fetchall() + result["fileinfo"] = [row_to_dict(f) for f in rows] + + # Get associated protocol metadata + if app_proto and app_proto != "failed": + q_proto = app_proto if app_proto != "http2" else "http" + cursor = await database.execute( + f"SELECT extra_data FROM {q_proto} WHERE flow_id = ? ORDER BY id", + [flow_id], + ) + rows = await cursor.fetchall() + result[app_proto] = [row_to_dict(f) for f in rows] + + # Get associated alert + if result["flow"]["alerted"]: + cursor = await database.execute( + "SELECT extra_data, color FROM alert WHERE flow_id = ? ORDER BY id", + [flow_id], + ) + rows = await cursor.fetchall() + result["alert"] = [row_to_dict(f) for f in rows] + + # Get associated TCP/UDP raw data + cursor = await database.execute( + "SELECT server_to_client, sha256 FROM raw WHERE flow_id = ? ORDER BY count", + [flow_id], + ) + rows = await cursor.fetchall() + result["raw"] = [dict(f) for f in rows] + + return JSONResponse(result) + + +async def api_replay_http(request): + flow_id = request.path_params["flow_id"] + + # Get HTTP events + cursor = await database.execute( + "SELECT flow_id, extra_data FROM http WHERE flow_id = ? ORDER BY id", + [flow_id], + ) + rows = await cursor.fetchall() + + # For each HTTP request, load client payload if it exists + data = [] + for row in rows: + req = row_to_dict(row) + req["rq_content"] = None + if req["http_method"] in ["POST"]: + # Get associated fileinfo + cursor = await database.execute( + "SELECT extra_data FROM fileinfo WHERE flow_id = ? ORDER BY id", + [flow_id], + ) + fileinfo_first_event = await cursor.fetchone() + if not fileinfo_first_event: + raise HTTPException(404) + sha256 = json.loads(fileinfo_first_event["extra_data"]).get("sha256") + if not sha256: + raise HTTPException(500) + + # Load file + path = f"static/filestore/{sha256[:2]}/{sha256}" + with open(path, "rb") as f: + req["rq_content"] = f.read() + data.append(req) + + context = {"request": request, "data": data, "services": CTF_CONFIG["services"]} + return templates.TemplateResponse( + "http-replay.py.jinja2", context, media_type="text/plain" + ) + + +async def api_replay_tcp(request): + flow_id = request.path_params["flow_id"] + + # Get flow event + cursor = await database.execute( + "SELECT dest_ipport FROM flow WHERE id = ?", + [flow_id], + ) + flow_event = await cursor.fetchone() + if not flow_event: + raise HTTPException(404) + ip, port = flow_event["dest_ipport"].rsplit(":", 1) + data = { + "flow_id": flow_id, + "ip": ip, + "port": port, + "dest_ipport": flow_event["dest_ipport"], + } + + # Get associated TCP data + cursor = await database.execute( + "SELECT server_to_client, sha256 FROM raw WHERE flow_id = ? ORDER BY count", + [flow_id], + ) + rows = await cursor.fetchall() + if not rows: + raise HTTPException(404) + + # Load files + data["tcp_data"] = [] + for row in rows: + sc, sha256 = row["server_to_client"], row["sha256"] + path = f"static/tcpstore/{sha256[:2]}/{sha256}" + with open(path, "rb") as f: + tcp_data = f.read() + if data["tcp_data"] and data["tcp_data"][-1][1] == sc and sc == 1: + # Concat servers messages together + data["tcp_data"][-1][0] += tcp_data + else: + data["tcp_data"].append([tcp_data, sc]) + + context = {"request": request, "data": data, "services": CTF_CONFIG["services"]} + return templates.TemplateResponse( + "tcp-replay.py.jinja2", context, media_type="text/plain" + ) + + +@contextlib.asynccontextmanager +async def lifespan(app): + """ + Open database on startup and launch importer in background. + Close database on exit. + """ + await database.connect() + await database.update_ctf_config(CTF_CONFIG) + db_task = asyncio.create_task(database.importer_task()) + yield + db_task.cancel() + await database.close() + + +# Load configuration from environment variables, then .env file +config = Config(".env") +DEBUG = config("DEBUG", cast=bool, default=False) +DATABASE_URL = config("DATABASE_URL", cast=str, default="file:database/database.db") +CTF_CONFIG = { + "start_date": config("CTF_START_DATE", cast=str), + "tick_length": config("CTF_TICK_LENGTH", cast=int), + "services": {}, +} +service_names = config("CTF_SERVICES", cast=CommaSeparatedStrings) +for name in service_names: + ipports = config(f"CTF_SERVICE_{name.upper()}", cast=CommaSeparatedStrings) + CTF_CONFIG["services"][name] = list(ipports) + +# Define web application +database = Database(DATABASE_URL) +templates = Jinja2Templates(directory="templates") +app = Starlette( + debug=DEBUG, + routes=[ + Route("/", index), + Route("/api/flow", api_flow_list), + Route("/api/flow/{flow_id:int}", api_flow_get), + Route("/api/replay-http/{flow_id:int}", api_replay_http), + Route("/api/replay-tcp/{flow_id:int}", api_replay_tcp), + Mount( + "/static", + StaticFiles(directory="static", follow_symlink=True), + name="static", + ), + ], + lifespan=lifespan, +) diff --git a/webapp/static/assets/css/bootstrap.min.css b/webapp/static/assets/css/bootstrap.min.css new file mode 100644 index 0000000..f5910ac --- /dev/null +++ b/webapp/static/assets/css/bootstrap.min.css @@ -0,0 +1,6 @@ +@charset "UTF-8";/*! + * Bootstrap v5.3.2 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.66666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.66666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.66666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.66666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.66666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.66666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a6b5cc;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b5b6b7;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a6c3ca;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control-plaintext~label::after,.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#6c757d}.form-floating>.form-control:disabled~label::after,.form-floating>:disabled~label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color:#86b7fe;--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button,.accordion-flush .accordion-item .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(10,88,202,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(86,94,100,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(20,108,67,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(61,213,243,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(255,205,57,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(176,42,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(249,250,251,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/webapp/static/assets/css/style.css b/webapp/static/assets/css/style.css new file mode 100644 index 0000000..076b4a8 --- /dev/null +++ b/webapp/static/assets/css/style.css @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 ANSSI + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* Use Bootstrap icons */ +.bi { + fill: currentColor; + vertical-align: -.125em; +} + +/* Disable Bootstrap blue box-shadow on open + + + {%- for name, ipaddr_ports in ctf_config.services.items() %} + + {%- if ipaddr_ports|length > 1 %} + + {%- endif %} + {%- for ipaddr_port in ipaddr_ports %} + + {%- endfor %} + + {%- endfor %} + + + + + + + + + +
+ +

+ Shovel +

+
    +
  • Use Left, Right keys to quickly navigate flows.
  • +
  • Use V key to quickly switch raw data view.
  • +
+
+ + +
+
+
+ + Download pcap +
+

+        
+
+
+
+ + + + + + + + + Generate script +
+
+

+            
+
+
+
+
+ + + + + + + Raw data + + + Generate script +
+
+

+          
+
+
+
+ + + + \ No newline at end of file diff --git a/webapp/templates/tcp-replay.py.jinja2 b/webapp/templates/tcp-replay.py.jinja2 new file mode 100644 index 0000000..c9df1e6 --- /dev/null +++ b/webapp/templates/tcp-replay.py.jinja2 @@ -0,0 +1,49 @@ +{# Get service name from dest_ipport #} +{%- set ns = namespace(service_name="unknown") %} +{%- for name, ipaddr_ports in services.items() %} +{%- if data.dest_ipport in ipaddr_ports %} +{%- set ns.service_name = name + "-" + (data.port | string) %} +{%- endif %} +{%- endfor -%} +#!/usr/bin/env python3 +# Filename: replay-{{ ns.service_name }}-{{ data.flow_id }}.py +import json +import os +import sys +import socket + +""" +This file was generated from network capture towards {{ data.ip }}. +Corresponding flow id: {{ data.flow_id }} +Service: {{ ns.service_name }} +""" + +# Load environment variables +# EXTRA is an array of the flagids for current service and team +HOST = os.getenv("TARGET_IP") +EXTRA = json.loads(os.getenv("TARGET_EXTRA", "[]")) + +# Timeout is important to prevent stall +socket.setdefaulttimeout(2) + +# Run the actual exploit +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect((HOST, {{ data.port }})) + +# FIXME: You should identify if a flag_id was used in the following +# payload. If it is the case, then you should loop using EXTRA. +#for flag_id in EXTRA: +{%- for payload, server_to_client in data.tcp_data %} +{%- if server_to_client == 0 %} + +s.sendall({{ payload | safe }}) +{%- else %} + +data = b"" +while not data.endswith({{ payload[-16:] | safe }}): + data += s.recv(1024) +print(data) +{%- endif %} +{%- endfor %} + +s.close()