diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..cf044f7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '0 5 * * *' + workflow_call: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + - name: Check formatting + run: cargo fmt --all -- --check + - name: Run clippy + run: cargo clippy -- -D clippy::all diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9a2ddc3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,19 @@ +name: Publish + +on: + push: + tags: + - v*.*.* + +jobs: + build: + uses: ZenGo-X/multi-party-ecdsa/.github/workflows/build.yml@master + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Publish crate + env: + TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: | + cargo publish --token "$TOKEN" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3063c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +.idea +.DS_Store + +keys*.store +signature + diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..32a9786 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +edition = "2018" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..05a8900 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +Contributing to the Multi-party ECDSA project +===================================== + +Pull requests are always welcome, and the KZen dev team appreciates any help the community can +give to help make Multi-party ECDSA project better. + +Contributor Agreement (CA) +---------------- + +Any contributor must sign the Contributor Agreement (CA). + +### How to sign the Contributor Agreement (CA)? + +Please send an email to [github@kzencorp.com](mailto:github@kzencorp.com) containing your github username, the CA will be send to you by email. +After signature you will be added to the team as a contributor. + +Communication Channels +---------------- + +* Most communication about KZen cryptography happens on Telegram, feel free to send us an email with your contact details. + +* Discussion about code base improvements happens in GitHub issues and on pull requests. + +Contributor Workflow +---------------- + +The codebase is maintained using the "contributor workflow" where everyone contributes patch proposals using "pull requests". This facilitates social contribution, easy testing and peer review. + +To contribute a patch, the workflow is as follows: + +* Fork repository +* Create topic branch +* Commit patches +* Push changes to your fork +* Create pull request + +Make sure to provide a clear description in your Pull Request (PR). + +### Header + +Make sure to include the following header (by configuring your IDE) in all files: + +```rust +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ +``` diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7b04624 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,114 @@ +[package] +name = "multi-party-ecdsa" +version = "0.8.1" +edition = "2018" +authors = [ + "Gary ", + "Omer " +] +keywords = [ + "ecdsa", + "multi-party-ecdsa", + "signature", + "rust", + "secret-shares", + "blockchain", + "cryptography", + "cryptocurrency" +] + +homepage = "https://github.com/KZen-networks/multi-party-ecdsa" +repository = "https://github.com/KZen-networks/multi-party-ecdsa" +license = "GPL-3.0-or-later" +categories = ["cryptography"] + +[lib] +crate-type = ["lib"] + +[features] +default = ["curv-kzen/rust-gmp-kzen"] +cclst = ["class_group"] + +[dependencies] +subtle = { version = "2" } +serde = { version = "1.0", features = ["derive"] } +zeroize = "1" +curv-kzen = { version = "0.9", default-features = false } +centipede = { version = "0.3", default-features = false } +zk-paillier = { version = "0.4.3", default-features = false } +round-based = { version = "0.1.4", features = [] } +thiserror = "1.0.23" +derivative = "2" +sha2 = "0.9" + +[dependencies.paillier] +version = "0.4.2" +package = "kzen-paillier" +default-features = false + +[dependencies.class_group] +version = "0.6" +default-features = false +optional = true + +[dev-dependencies] +criterion = "0.3" +aes-gcm = "0.9.4" +hex = "0.4" +tokio = { version = "1", default-features = false, features = ["macros"] } +futures = "0.3" +rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] } +reqwest = { version = "0.9", default-features = false } +uuid = { version = "0.8", features = ["v4"] } +serde_json = "1.0" +rand = "0.8" +surf = "2" +async-sse = "5" +anyhow = "1" +structopt = "0.3" +secp256k1 = { version = "0.20", features = ["global-context"]} + +thiserror = "1.0.23" +round-based = { version = "0.1.4", features = ["dev"] } + +[[example]] +name = "gg18_sm_manager" + +[[example]] +name = "gg18_sign_client" + +[[example]] +name = "gg18_keygen_client" + +[[example]] +name = "common" +crate-type = ["lib"] + +[[bench]] +name = "cclst_keygen" +path = "benches/two_party_ecdsa/cclst_2019/keygen.rs" +required-features = ["cclst"] +harness = false + +[[bench]] +name = "cclst_sign" +path = "benches/two_party_ecdsa/cclst_2019/sign.rs" +required-features = ["cclst"] +harness = false + + +[[bench]] +name = "gg18" +path = "benches/multi_party_ecdsa/gg18/keygen.rs" +harness = false + +[[bench]] +name = "lindel2017_keygen" +path = "benches/two_party_ecdsa/lindell_2017/keygen.rs" +harness = false + + +[[bench]] +name = "lindel2017_sign" +path = "benches/two_party_ecdsa/lindell_2017/sign.rs" +harness = false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a5368b --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +# Multi-party ECDSA + +[![Build Status](https://travis-ci.com/ZenGo-X/multi-party-ecdsa.svg?branch=master)](https://travis-ci.com/zengo-x/multi-party-ecdsa) +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +This project is a Rust implementation of {t,n}-threshold ECDSA (elliptic curve digital signature algorithm). + +Threshold ECDSA includes two protocols: + +- Key Generation for creating secret shares. +- Signing for using the secret shares to generate a signature. + +ECDSA is used extensively for crypto-currencies such as Bitcoin, Ethereum (secp256k1 curve), NEO (NIST P-256 curve) and much more. +This library can be used to create MultiSig and ThresholdSig crypto wallet. For a full background on threshold signatures please read our Binance academy article [Threshold Signatures Explained](https://www.binance.vision/security/threshold-signatures-explained). + +## Library Introduction +The library was built with four core design principles in mind: +1. Multi-protocol support +2. Built for cryptography engineers +3. Foolproof +4. Black box use of cryptographic primitives + +To learn about the core principles as well as on the [audit](https://github.com/KZen-networks/multi-party-ecdsa/tree/master/audits) process and security of the library, please read our [Intro to multiparty ecdsa library](https://zengo.com/introducing-multi-party-ecdsa-library/) blog post. + +## Use It + + +The library implements four different protocols for threshold ECDSA. The protocols presents different tradeoffs in terms of parameters, security assumptions and efficiency. + +| Protocol | High Level code | +| -------------------------------------------- | -------------------------------------------- | +| Lindell 17 [1] | [Gotham-city](https://github.com/KZen-networks/gotham-city) (accepted to [CIW19](https://ifca.ai/fc19/ciw/program.html)) is a two party bitcoin wallet, including benchmarks. [KMS](https://github.com/KZen-networks/kms-secp256k1) is a Rust wrapper library that implements a general purpose two party key management system. [thresh-sig-js](https://github.com/KZen-networks/thresh-sig-js) is a Javascript SDK | +| Gennaro, Goldfeder 19 [2] ([video](https://www.youtube.com/watch?v=PdfDZIwuZm0)) | [tss-ecdsa-cli](https://github.com/cryptochill/tss-ecdsa-cli) is a wrapper CLI for full threshold access structure, including network and threshold HD keys ([BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). See [Demo](https://github.com/KZen-networks/multi-party-ecdsa#run-demo) in this library to get better low level understanding| +|Castagnos et. al. 19 [3]| Currently enabled as a feature in this library. To Enable, build with `--features=cclst`. to Test, use `cargo test --features=cclst -- --test-threads=1` | +| Gennaro, Goldfeder 20 [4] | A full threshold protocol that supports identifying malicious parties. If signing fails - a list of malicious parties is returned. The protocol requires only a broadcast channel (all messages are broadcasted)| + +## Run GG20 Demo + +In the following steps we will generate 2-of-3 threshold signing key and sign a message with 2 parties. + +### Setup + +1. You need [Rust](https://rustup.rs/) and [GMP library](https://gmplib.org) (optionally) to be installed on your computer. +2. - Run `cargo build --release --examples` + - Don't have GMP installed? Use this command instead: + ```bash + cargo build --release --examples --no-default-features --features curv-kzen/num-bigint + ``` + But keep in mind that it will be less efficient. + + Either of commands will produce binaries into `./target/release/examples/` folder. +3. `cd ./target/release/examples/` + +### Start an SM server + +`./gg20_sm_manager` + +That will start an HTTP server on `http://127.0.0.1:8000`. Other parties will use that server in order to communicate with +each other. Note that communication channels are neither encrypted nor authenticated. In production, you must encrypt and +authenticate parties messages. + +### Run Keygen + +Open 3 terminal tabs for each party. Run: + +1. `./gg20_keygen -t 1 -n 3 -i 1 --output local-share1.json` +2. `./gg20_keygen -t 1 -n 3 -i 2 --output local-share2.json` +3. `./gg20_keygen -t 1 -n 3 -i 3 --output local-share3.json` + +Each command corresponds to one party. Once keygen is completed, you'll have 3 new files: +`local-share1.json`, `local-share2.json`, `local-share3.json` corresponding to local secret +share of each party. + +### Run Signing + +Since we use 2-of-3 scheme (`t=1 n=3`), any two parties can sign a message. Run: + +1. `./gg20_signing -p 1,2 -d "hello" -l local-share1.json` +2. `./gg20_signing -p 1,2 -d "hello" -l local-share2.json` + +Each party will produce a resulting signature. `-p 1,2` specifies indexes of parties +who attends in signing (each party has an associated index given at keygen, see argument +`-i`), `-l file.json` sets a path to a file with secret local share, and `-d "hello"` +is a message being signed. + +### Running Demo on different computers + +While previous steps show how to run keygen & signing on local computer, you actually can +run each party on dedicated machine. To do this, you should ensure that parties can reach +SM Server, and specify its address via command line argument, eg: + +`./gg20_keygen --address http://10.0.1.9:8000/ ...` + +## Run GG18 Demo + +The following steps are for setup, key generation with `n` parties and signing with `t+1` parties. + +### Setup + +1. We use shared state machine architecture (see [white city](https://github.com/KZen-networks/white-city)). The parameters `parties` and `threshold` can be configured by changing the file: `param`. a keygen will run with `parties` parties and signing will run with any subset of `threshold + 1` parties. `param` file should be located in the same path of the client software. + +2. Install [Rust](https://rustup.rs/). Run `cargo build --release --examples` (it will build into `/target/release/examples/`) + +3. Run the shared state machine: `./gg18_sm_manager`. By default, it's configured to be in `127.0.0.1:8000`, this can be changed in `Rocket.toml` file. The `Rocket.toml` file should be in the same folder you run `sm_manager` from. + +### KeyGen + +run `gg18_keygen_client` as follows: `./gg18_keygen_client http://127.0.0.1:8000 keys.store`. Replace IP and port with the ones configured in setup. Once `n` parties join the application will run till finish. At the end each party will get a local keys file `keys.store` (change filename in command line). This contains secret and public data of the party after keygen. The file therefore should remain private. + +### Sign + +Run `./gg18_sign_client`. The application should be in the same folder as the `keys.store` file (or custom filename generated in keygen). the application takes three arguments: `IP:port` as in keygen, `filename` and message to be signed: `./gg18_sign_client http://127.0.0.1:8001 keys.store "KZen Networks"`. The same message should be used by all signers. Once `t+1` parties join the protocol will run and will output to screen signature (R,s). + +The `./gg18_sign_client` executable initially tries to unhex its input message (the third parameter). Before running ensure two things: + +1. If you want to pass a binary message to be signed - hex it. +2. If you want to pass a textual message in a non-hex form, make sure it can't be unhexed. +Simply put, the safest way to use the signing binary is to just always hex your messages before passing them to the `./gg18_sign_client` executable. + +#### Example +To sign the message `hello world`, first calculate its hexadecimal representation. This yields the `68656c6c6f20776f726c64`. +Then, run: +```bash +./gg18_sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64" +``` + +### GG18 demo + +Run `./run.sh` (located in `/demo` folder) in the main folder. Move `params` file to the same folder as the executables (usually `/target/release/examples`). The script will spawn a shared state machine, clients in the number of parties and signing requests for the `threshold + 1` first parties. + +`gg18_sm_manager` rocket server runs in _production_ mode by default. You may modify the `./run.sh` to config it to run in different environments. For example, to run rocket server in _development_: +``` +ROCKET_ENV=development ./target/release/examples/sm_manager +``` + +## Contributions & Development Process + +The contribution workflow is described in [CONTRIBUTING.md](CONTRIBUTING.md), in addition **the [Rust utilities wiki](https://github.com/KZen-networks/rust-utils/wiki) contains information on workflow and environment set-up**. + +## License + +Multi-party ECDSA is released under the terms of the GPL-3.0 license. See [LICENSE](LICENSE) for more information. + +## Contact + +Feel free to [reach out](mailto:github@kzencorp.com) or join ZenGo X [Telegram](https://t.me/joinchat/ET1mddGXRoyCxZ-7) for discussions on code and research. + +## References + +[1] + +[2] + +[3] + +[4] diff --git a/audits/REPORT_final_2019-10-22.pdf b/audits/REPORT_final_2019-10-22.pdf new file mode 100644 index 0000000..9e7c2f8 Binary files /dev/null and b/audits/REPORT_final_2019-10-22.pdf differ diff --git a/benches/multi_party_ecdsa/gg18/keygen.rs b/benches/multi_party_ecdsa/gg18/keygen.rs new file mode 100644 index 0000000..5c4b0c7 --- /dev/null +++ b/benches/multi_party_ecdsa/gg18/keygen.rs @@ -0,0 +1,135 @@ +use criterion::criterion_main; + +mod bench { + use criterion::{criterion_group, Criterion}; + use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; + use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; + use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2018::party_i::*; + pub fn bench_full_keygen_party_one_two(c: &mut Criterion) { + c.bench_function("keygen t=1 n=2", move |b| { + b.iter(|| { + keygen_t_n_parties(1, 2); + }) + }); + } + pub fn bench_full_keygen_party_two_three(c: &mut Criterion) { + c.bench_function("keygen t=2 n=3", move |b| { + b.iter(|| { + keygen_t_n_parties(2, 3); + }) + }); + } + pub fn keygen_t_n_parties( + t: u16, + n: u16, + ) -> ( + Vec, + Vec, + Vec>, + Point, + VerifiableSS, + ) { + let parames = Parameters { + threshold: t, + share_count: n, + }; + let (t, n) = (t as usize, n as usize); + let party_keys_vec = (0..n) + .map(|i| Keys::create(i as u16)) + .collect::>(); + + let mut bc1_vec = Vec::new(); + let mut decom_vec = Vec::new(); + + for key in &party_keys_vec { + let (bc1, decom1) = key.phase1_broadcast_phase3_proof_of_correct_key(); + bc1_vec.push(bc1); + decom_vec.push(decom1); + } + + let y_vec = (0..n) + .map(|i| decom_vec[i].y_i.clone()) + .collect::>>(); + let mut y_vec_iter = y_vec.iter(); + let head = y_vec_iter.next().unwrap(); + let tail = y_vec_iter; + let y_sum = tail.fold(head.clone(), |acc, x| acc + x); + let mut vss_scheme_vec = Vec::new(); + let mut secret_shares_vec = Vec::new(); + let mut index_vec = Vec::new(); + for key in &party_keys_vec { + let (vss_scheme, secret_shares, index) = key + .phase1_verify_com_phase3_verify_correct_key_phase2_distribute( + ¶mes, &decom_vec, &bc1_vec, + ) + .expect("invalid key"); + vss_scheme_vec.push(vss_scheme); + secret_shares_vec.push(secret_shares); + index_vec.push(index as u16); + } + let vss_scheme_for_test = vss_scheme_vec.clone(); + + let party_shares = (0..n) + .map(|i| { + (0..n) + .map(|j| { + let vec_j = &secret_shares_vec[j]; + vec_j[i].clone() + }) + .collect::>>() + }) + .collect::>>>(); + + let mut shared_keys_vec = Vec::new(); + let mut dlog_proof_vec = Vec::new(); + for i in 0..n { + let (shared_keys, dlog_proof) = party_keys_vec[i] + .phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶mes, + &y_vec, + &party_shares[i], + &vss_scheme_vec, + (&index_vec[i] + 1).into(), + ) + .expect("invalid vss"); + shared_keys_vec.push(shared_keys); + dlog_proof_vec.push(dlog_proof); + } + + let pk_vec = (0..n) + .map(|i| dlog_proof_vec[i].pk.clone()) + .collect::>>(); + + //both parties run: + Keys::verify_dlog_proofs(¶mes, &dlog_proof_vec, &y_vec).expect("bad dlog proof"); + + //test + let xi_vec = (0..=t) + .map(|i| shared_keys_vec[i].x_i.clone()) + .collect::>>(); + let x = vss_scheme_for_test[0] + .clone() + .reconstruct(&index_vec[0..=t], &xi_vec); + let sum_u_i = party_keys_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + &x.u_i); + assert_eq!(x, sum_u_i); + + ( + party_keys_vec, + shared_keys_vec, + pk_vec, + y_sum, + vss_scheme_for_test[0].clone(), + ) + } + + criterion_group! { + name = keygen; + config = Criterion::default().sample_size(10); + targets = + self::bench_full_keygen_party_one_two, + self::bench_full_keygen_party_two_three} +} + +criterion_main!(bench::keygen); diff --git a/benches/two_party_ecdsa/cclst_2019/keygen.rs b/benches/two_party_ecdsa/cclst_2019/keygen.rs new file mode 100644 index 0000000..35c89c4 --- /dev/null +++ b/benches/two_party_ecdsa/cclst_2019/keygen.rs @@ -0,0 +1,64 @@ +use criterion::criterion_main; + +mod bench { + use criterion::{criterion_group, Criterion}; + use curv::arithmetic::traits::Samplable; + use curv::elliptic::curves::traits::*; + use curv::BigInt; + use multi_party_ecdsa::protocols::two_party_ecdsa::cclst_2019::{party_one, party_two}; + + pub fn bench_full_keygen_party_one_two(c: &mut Criterion) { + c.bench_function("keygen", move |b| { + b.iter(|| { + + let (party_one_first_message, comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments_with_fixed_secret_share( + Scalar::::random(), + ); + let (party_two_first_message, _ec_key_pair_party2) = + party_two::KeyGenFirstMsg::create_with_fixed_secret_share(Scalar::::from(&BigInt::from( + 10, + ))); + let party_one_second_message = party_one::KeyGenSecondMsg::verify_and_decommit( + comm_witness, + &party_two_first_message.d_log_proof, + ) + .expect("failed to verify and decommit"); + + let _party_two_second_message = party_two::KeyGenSecondMsg::verify_commitments_and_dlog_proof( + &party_one_first_message, + &party_one_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + + // init HSMCL keypair: + let seed: BigInt = str::parse( + "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848" + ).unwrap(); + let (hsmcl, hsmcl_public) = party_one::HSMCL::generate_keypair_and_encrypted_share_and_proof( + &ec_key_pair_party1, + &seed, + ); + + //P1 sends P2 hsmcl_public + let _party_one_private = party_one::Party1Private::set_private_key(&ec_key_pair_party1, &hsmcl); + + let _party_two_hsmcl_pub = party_two::Party2Public::verify_setup_and_zkcldl_proof( + &hsmcl_public, + &seed, + &party_one_second_message.comm_witness.public_share, + ) + .expect("proof error"); + + + }) + }); + } + + criterion_group! { + name = keygen; + config = Criterion::default().sample_size(10); + targets =self::bench_full_keygen_party_one_two} +} + +criterion_main!(bench::keygen); diff --git a/benches/two_party_ecdsa/cclst_2019/sign.rs b/benches/two_party_ecdsa/cclst_2019/sign.rs new file mode 100644 index 0000000..c8f5998 --- /dev/null +++ b/benches/two_party_ecdsa/cclst_2019/sign.rs @@ -0,0 +1,92 @@ +use criterion::criterion_main; + +mod bench { + use criterion::{criterion_group, Criterion}; + use curv::BigInt; + use multi_party_ecdsa::protocols::two_party_ecdsa::cclst_2019::party_two::HSMCLPublic; + use multi_party_ecdsa::protocols::two_party_ecdsa::cclst_2019::*; + + pub fn bench_full_sign_party_one_two(c: &mut Criterion) { + c.bench_function("sign", move |b| { + b.iter(|| { + ////////// Simulate KeyGen ///////////////// + // assume party1 and party2 engaged with KeyGen in the past resulting in + // party1 owning private share and HSMCL key-pair + // party2 owning private share and HSMCL encryption of party1 share + let (_party_one_private_share_gen, comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments(); + let (party_two_private_share_gen, ec_key_pair_party2) = party_two::KeyGenFirstMsg::create(); + + //pi (nothing up my sleeve) + let seed: BigInt = str::parse( + "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848" + ).unwrap(); + + let (party_one_hsmcl, hsmcl_public) = + party_one::HSMCL::generate_keypair_and_encrypted_share_and_proof( + &ec_key_pair_party1, + &seed, + ); + + let party1_private = + party_one::Party1Private::set_private_key(&ec_key_pair_party1, &party_one_hsmcl); + + let party_two_hsmcl_pub = party_two::Party2Public::verify_setup_and_zkcldl_proof( + &hsmcl_public, + &seed, + &comm_witness.public_share, + ) + .expect("proof error"); + + ////////// Start Signing ///////////////// + // creating the ephemeral private shares: + + let (eph_party_two_first_message, eph_comm_witness, eph_ec_key_pair_party2) = + party_two::EphKeyGenFirstMsg::create_commitments(); + let (eph_party_one_first_message, eph_ec_key_pair_party1) = + party_one::EphKeyGenFirstMsg::create(); + let eph_party_two_second_message = party_two::EphKeyGenSecondMsg::verify_and_decommit( + eph_comm_witness, + &eph_party_one_first_message, + ) + .expect("party1 DLog proof failed"); + + let _eph_party_one_second_message = + party_one::EphKeyGenSecondMsg::verify_commitments_and_dlog_proof( + &eph_party_two_first_message, + &eph_party_two_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + let party2_private = party_two::Party2Private::set_private_key(&ec_key_pair_party2); + let message = BigInt::from(1234); + + let partial_sig = party_two::PartialSig::compute( + party_two_hsmcl_pub, + &party2_private, + &eph_ec_key_pair_party2, + &eph_party_one_first_message.public_share, + &message, + ); + + let signature = party_one::Signature::compute( + &party_one_hsmcl, + &party1_private, + partial_sig.c3, + &eph_ec_key_pair_party1, + &eph_party_two_second_message.comm_witness.public_share, + ); + + let pubkey = + party_one::compute_pubkey(&party1_private, &party_two_private_share_gen.public_share); + party_one::verify(&signature, &pubkey, &message).expect("Invalid signature") + }) + }); + } + + criterion_group! { + name = sign; + config = Criterion::default().sample_size(10); + targets =self::bench_full_sign_party_one_two} +} + +criterion_main!(bench::sign); diff --git a/benches/two_party_ecdsa/lindell_2017/keygen.rs b/benches/two_party_ecdsa/lindell_2017/keygen.rs new file mode 100644 index 0000000..412ff75 --- /dev/null +++ b/benches/two_party_ecdsa/lindell_2017/keygen.rs @@ -0,0 +1,81 @@ +use criterion::criterion_main; + +mod bench { + use criterion::{criterion_group, Criterion}; + use curv::arithmetic::traits::Samplable; + use curv::elliptic::curves::{secp256_k1::Secp256k1, Scalar}; + use curv::BigInt; + use multi_party_ecdsa::protocols::two_party_ecdsa::lindell_2017::*; + + pub fn bench_full_keygen_party_one_two(c: &mut Criterion) { + c.bench_function("keygen", move |b| { + b.iter(|| { + let (party_one_first_message, comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments_with_fixed_secret_share( + Scalar::::from(&BigInt::sample(253)), + ); + let (party_two_first_message, _ec_key_pair_party2) = + party_two::KeyGenFirstMsg::create_with_fixed_secret_share( + Scalar::::from(&BigInt::from(10)), + ); + let party_one_second_message = party_one::KeyGenSecondMsg::verify_and_decommit( + comm_witness, + &party_two_first_message.d_log_proof, + ) + .expect("failed to verify and decommit"); + + let _party_two_second_message = + party_two::KeyGenSecondMsg::verify_commitments_and_dlog_proof( + &party_one_first_message, + &party_one_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + + // init paillier keypair: + let paillier_key_pair = + party_one::PaillierKeyPair::generate_keypair_and_encrypted_share( + &ec_key_pair_party1, + ); + + let party_one_private = party_one::Party1Private::set_private_key( + &ec_key_pair_party1, + &paillier_key_pair, + ); + + let party_two_paillier = party_two::PaillierPublic { + ek: paillier_key_pair.ek.clone(), + encrypted_secret_share: paillier_key_pair.encrypted_share.clone(), + }; + + // zk proof of correct paillier key + let correct_key_proof = + party_one::PaillierKeyPair::generate_ni_proof_correct_key(&paillier_key_pair); + party_two::PaillierPublic::verify_ni_proof_correct_key( + correct_key_proof, + &party_two_paillier.ek, + ) + .expect("bad paillier key"); + + //zk_pdl + + let (pdl_statement, pdl_proof, composite_dlog_proof) = + party_one::PaillierKeyPair::pdl_proof(&party_one_private, &paillier_key_pair); + party_two::PaillierPublic::pdl_verify( + &composite_dlog_proof, + &pdl_statement, + &pdl_proof, + &party_two_paillier, + &party_one_second_message.comm_witness.public_share, + ) + .expect("PDL error"); + }) + }); + } + + criterion_group! { + name = keygen; + config = Criterion::default().sample_size(10); + targets =self::bench_full_keygen_party_one_two} +} + +criterion_main!(bench::keygen); diff --git a/benches/two_party_ecdsa/lindell_2017/sign.rs b/benches/two_party_ecdsa/lindell_2017/sign.rs new file mode 100644 index 0000000..d93eb9a --- /dev/null +++ b/benches/two_party_ecdsa/lindell_2017/sign.rs @@ -0,0 +1,75 @@ +use criterion::criterion_main; + +mod bench { + use criterion::{criterion_group, Criterion}; + use curv::BigInt; + use multi_party_ecdsa::protocols::two_party_ecdsa::lindell_2017::*; + + pub fn bench_full_sign_party_one_two(c: &mut Criterion) { + c.bench_function("sign", move |b| { + b.iter(|| { + let (_party_one_private_share_gen, _comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments(); + let (party_two_private_share_gen, ec_key_pair_party2) = + party_two::KeyGenFirstMsg::create(); + + let keypair = party_one::PaillierKeyPair::generate_keypair_and_encrypted_share( + &ec_key_pair_party1, + ); + + // creating the ephemeral private shares: + + let (eph_party_two_first_message, eph_comm_witness, eph_ec_key_pair_party2) = + party_two::EphKeyGenFirstMsg::create_commitments(); + let (eph_party_one_first_message, eph_ec_key_pair_party1) = + party_one::EphKeyGenFirstMsg::create(); + let eph_party_two_second_message = + party_two::EphKeyGenSecondMsg::verify_and_decommit( + eph_comm_witness, + &eph_party_one_first_message, + ) + .expect("party1 DLog proof failed"); + + let _eph_party_one_second_message = + party_one::EphKeyGenSecondMsg::verify_commitments_and_dlog_proof( + &eph_party_two_first_message, + &eph_party_two_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + let party2_private = party_two::Party2Private::set_private_key(&ec_key_pair_party2); + let message = BigInt::from(1234); + let partial_sig = party_two::PartialSig::compute( + &keypair.ek, + &keypair.encrypted_share, + &party2_private, + &eph_ec_key_pair_party2, + &eph_party_one_first_message.public_share, + &message, + ); + + let party1_private = + party_one::Party1Private::set_private_key(&ec_key_pair_party1, &keypair); + + let signature = party_one::Signature::compute( + &party1_private, + &partial_sig.c3, + &eph_ec_key_pair_party1, + &eph_party_two_second_message.comm_witness.public_share, + ); + + let pubkey = party_one::compute_pubkey( + &party1_private, + &party_two_private_share_gen.public_share, + ); + party_one::verify(&signature, &pubkey, &message).expect("Invalid signature") + }) + }); + } + + criterion_group! { + name = sign; + config = Criterion::default().sample_size(10); + targets =self::bench_full_sign_party_one_two} +} + +criterion_main!(bench::sign); diff --git a/demo/MP-ECDSA demo.gif b/demo/MP-ECDSA demo.gif new file mode 100644 index 0000000..10cf226 Binary files /dev/null and b/demo/MP-ECDSA demo.gif differ diff --git a/demo/run.sh b/demo/run.sh new file mode 100644 index 0000000..b608be1 --- /dev/null +++ b/demo/run.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +cargo +nightly build --examples --release + +file_as_string=`cat params.json` + +n=`echo "$file_as_string" | cut -d "\"" -f 4 ` +t=`echo "$file_as_string" | cut -d "\"" -f 8 ` + +echo "Multi-party ECDSA parties:$n threshold:$t" +#clean +sleep 1 + +rm keys?.store +killall gg18_sm_manager gg18_keygen_client gg18_sign_client 2> /dev/null + +./target/release/examples/gg18_sm_manager & + +sleep 2 +echo "keygen part" + +for i in $(seq 1 $n) +do + echo "key gen for client $i out of $n" + ./target/release/examples/gg18_keygen_client http://127.0.0.1:8001 keys$i.store & + sleep 3 +done + + + +sleep 5 +echo "sign" + +for i in $(seq 1 $((t+1))); +do + echo "signing for client $i out of $((t+1))" + ./target/release/examples/gg18_sign_client http://127.0.0.1:8001 keys$i.store "KZen Networks" & + sleep 3 +done + +killall gg18_sm_manager 2> /dev/null diff --git a/docs/diagrams/keygen1.dot b/docs/diagrams/keygen1.dot new file mode 100644 index 0000000..cea99d9 --- /dev/null +++ b/docs/diagrams/keygen1.dot @@ -0,0 +1,23 @@ +// Lindell 2party ECDSA keygen party 1 +digraph { + start [shape=doublecircle] + stop [shape=doublecircle] + A + B + C + D + E + start -> A [label="create commitments, ID"] + "check1" [shape=diamond] + "check2" [shape=diamond] + A -> "check1" [label="verify_and_decommit, ID"] + "check1" -> B [label="yes,ID"] + "check1" -> stop [label=no] + B -> C [label="generate_keypair_and_encrypted_share,ID"] + C -> "check2" [label="generate_proof_correct_key, ID"] + "check2" -> D [label="yes,ID"] + "check2" -> stop [label=no] + D -> E [label="generate_range_proof,ID"] + E -> stop [label="write_to_db,ID"] + +} diff --git a/docs/diagrams/keygen2.dot b/docs/diagrams/keygen2.dot new file mode 100644 index 0000000..ee3a495 --- /dev/null +++ b/docs/diagrams/keygen2.dot @@ -0,0 +1,27 @@ +// Lindell 2party ECDSA keygen party 2 +digraph { + start [shape=doublecircle] + stop [shape=doublecircle] + A + B + C + D + E + start -> A [label="begin session"] + "check1" [shape=diamond] + "check2" [shape=diamond] + "check3" [shape=diamond] + A -> B [label="create, ID"] + B -> "check1" [label="verify_commitments_and_dlog_proof, ID"] + "check1" -> C [label="yes,ID"] + "check1" -> stop [label=no] + C -> D [label="generate_correct_key_challenge,ID"] + D -> "check2" [label="verify_correct_key, ID"] + "check2" -> E [label="yes,ID"] + "check2" -> stop [label=no] + E -> "check3" [label="verify_range_proof, ID"] + "check3" -> F [label="yes,ID"] + "check3" -> stop [label=no] + F -> stop [label="write_to_local_storage,ID"] + +} diff --git a/docs/gg19.pdf b/docs/gg19.pdf new file mode 100644 index 0000000..413ba89 Binary files /dev/null and b/docs/gg19.pdf differ diff --git a/examples/common.rs b/examples/common.rs new file mode 100644 index 0000000..1fbacbc --- /dev/null +++ b/examples/common.rs @@ -0,0 +1,225 @@ +#![allow(dead_code)] + +use std::{env, thread, time, time::Duration}; + +use aes_gcm::aead::{Aead, NewAead}; +use aes_gcm::{Aes256Gcm, Nonce}; +use rand::{rngs::OsRng, RngCore}; + +use curv::{ + arithmetic::traits::Converter, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; + +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +pub type Key = String; + +#[allow(dead_code)] +pub const AES_KEY_BYTES_LEN: usize = 32; + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct AEAD { + pub ciphertext: Vec, + pub tag: Vec, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct PartySignup { + pub number: u16, + pub uuid: String, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Index { + pub key: Key, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Entry { + pub key: Key, + pub value: String, +} + +#[derive(Serialize, Deserialize)] +pub struct Params { + pub parties: String, + pub threshold: String, +} + +#[allow(dead_code)] +pub fn aes_encrypt(key: &[u8], plaintext: &[u8]) -> AEAD { + let aes_key = aes_gcm::Key::from_slice(key); + let cipher = Aes256Gcm::new(aes_key); + + let mut nonce = [0u8; 12]; + OsRng.fill_bytes(&mut nonce); + let nonce = Nonce::from_slice(&nonce); + + let ciphertext = cipher + .encrypt(nonce, plaintext) + .expect("encryption failure!"); + + AEAD { + ciphertext: ciphertext, + tag: nonce.to_vec(), + } +} + +#[allow(dead_code)] +pub fn aes_decrypt(key: &[u8], aead_pack: AEAD) -> Vec { + let aes_key = aes_gcm::Key::from_slice(key); + let nonce = Nonce::from_slice(&aead_pack.tag); + let gcm = Aes256Gcm::new(aes_key); + + let out = gcm.decrypt(nonce, aead_pack.ciphertext.as_slice()); + out.unwrap() +} + +pub fn postb(client: &Client, path: &str, body: T) -> Option +where + T: serde::ser::Serialize, +{ + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "http://127.0.0.1:8001".to_string()); + let retries = 3; + let retry_delay = time::Duration::from_millis(250); + for _i in 1..retries { + let res = client + .post(&format!("{}/{}", addr, path)) + .json(&body) + .send(); + + if let Ok(mut res) = res { + return Some(res.text().unwrap()); + } + thread::sleep(retry_delay); + } + None +} + +pub fn broadcast( + client: &Client, + party_num: u16, + round: &str, + data: String, + sender_uuid: String, +) -> Result<(), ()> { + let key = format!("{}-{}-{}", party_num, round, sender_uuid); + let entry = Entry { key, value: data }; + + let res_body = postb(client, "set", entry).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + +pub fn sendp2p( + client: &Client, + party_from: u16, + party_to: u16, + round: &str, + data: String, + sender_uuid: String, +) -> Result<(), ()> { + let key = format!("{}-{}-{}-{}", party_from, party_to, round, sender_uuid); + + let entry = Entry { key, value: data }; + + let res_body = postb(client, "set", entry).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + +pub fn poll_for_broadcasts( + client: &Client, + party_num: u16, + n: u16, + delay: Duration, + round: &str, + sender_uuid: String, +) -> Vec { + let mut ans_vec = Vec::new(); + for i in 1..=n { + if i != party_num { + let key = format!("{}-{}-{}", i, round, sender_uuid); + let index = Index { key }; + loop { + // add delay to allow the server to process request: + thread::sleep(delay); + let res_body = postb(client, "get", index.clone()).unwrap(); + let answer: Result = serde_json::from_str(&res_body).unwrap(); + if let Ok(answer) = answer { + ans_vec.push(answer.value); + println!("[{:?}] party {:?} => party {:?}", round, i, party_num); + break; + } + } + } + } + ans_vec +} + +pub fn poll_for_p2p( + client: &Client, + party_num: u16, + n: u16, + delay: Duration, + round: &str, + sender_uuid: String, +) -> Vec { + let mut ans_vec = Vec::new(); + for i in 1..=n { + if i != party_num { + let key = format!("{}-{}-{}-{}", i, party_num, round, sender_uuid); + let index = Index { key }; + loop { + // add delay to allow the server to process request: + thread::sleep(delay); + let res_body = postb(client, "get", index.clone()).unwrap(); + let answer: Result = serde_json::from_str(&res_body).unwrap(); + if let Ok(answer) = answer { + ans_vec.push(answer.value); + println!("[{:?}] party {:?} => party {:?}", round, i, party_num); + break; + } + } + } + } + ans_vec +} + +pub fn check_sig( + r: &Scalar, + s: &Scalar, + msg: &BigInt, + pk: &Point, +) { + use secp256k1::{Message, PublicKey, Signature, SECP256K1}; + + let raw_msg = BigInt::to_bytes(msg); + let mut msg: Vec = Vec::new(); // padding + msg.extend(vec![0u8; 32 - raw_msg.len()]); + msg.extend(raw_msg.iter()); + + let msg = Message::from_slice(msg.as_slice()).unwrap(); + let mut raw_pk = pk.to_bytes(false).to_vec(); + if raw_pk.len() == 64 { + raw_pk.insert(0, 4u8); + } + let pk = PublicKey::from_slice(&raw_pk).unwrap(); + + let mut compact: Vec = Vec::new(); + let bytes_r = &r.to_bytes().to_vec(); + compact.extend(vec![0u8; 32 - bytes_r.len()]); + compact.extend(bytes_r.iter()); + + let bytes_s = &s.to_bytes().to_vec(); + compact.extend(vec![0u8; 32 - bytes_s.len()]); + compact.extend(bytes_s.iter()); + + let secp_sig = Signature::from_compact(compact.as_slice()).unwrap(); + + let is_correct = SECP256K1.verify(&msg, &secp_sig, &pk).is_ok(); + assert!(is_correct); +} diff --git a/examples/gg18_keygen_client.rs b/examples/gg18_keygen_client.rs new file mode 100644 index 0000000..bc5b8f0 --- /dev/null +++ b/examples/gg18_keygen_client.rs @@ -0,0 +1,271 @@ +#![allow(non_snake_case)] +/// to run: +/// 1: go to rocket_server -> cargo run +/// 2: cargo run from PARTIES number of terminals +use curv::{ + arithmetic::traits::Converter, + cryptographic_primitives::{ + proofs::sigma_dlog::DLogProof, secret_sharing::feldman_vss::VerifiableSS, + }, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2018::party_i::{ + KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, Parameters, +}; +use paillier::EncryptionKey; +use reqwest::Client; +use sha2::Sha256; +use std::{env, fs, time}; + +mod common; +use common::{ + aes_decrypt, aes_encrypt, broadcast, poll_for_broadcasts, poll_for_p2p, postb, sendp2p, Params, + PartySignup, AEAD, AES_KEY_BYTES_LEN, +}; + +fn main() { + if env::args().nth(3).is_some() { + panic!("too many arguments") + } + if env::args().nth(2).is_none() { + panic!("too few arguments") + } + //read parameters: + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + let PARTIES: u16 = params.parties.parse::().unwrap(); + let THRESHOLD: u16 = params.threshold.parse::().unwrap(); + + let client = Client::new(); + + // delay: + let delay = time::Duration::from_millis(25); + let params = Parameters { + threshold: THRESHOLD, + share_count: PARTIES, + }; + + //signup: + let (party_num_int, uuid) = match signup(&client).unwrap() { + PartySignup { number, uuid } => (number, uuid), + }; + println!("number: {:?}, uuid: {:?}", party_num_int, uuid); + + let party_keys = Keys::create(party_num_int); + let (bc_i, decom_i) = party_keys.phase1_broadcast_phase3_proof_of_correct_key(); + + // send commitment to ephemeral public keys, get round 1 commitments of other parties + assert!(broadcast( + &client, + party_num_int, + "round1", + serde_json::to_string(&bc_i).unwrap(), + uuid.clone() + ) + .is_ok()); + let round1_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round1", + uuid.clone(), + ); + + let mut bc1_vec = round1_ans_vec + .iter() + .map(|m| serde_json::from_str::(m).unwrap()) + .collect::>(); + + bc1_vec.insert(party_num_int as usize - 1, bc_i); + + // send ephemeral public keys and check commitments correctness + assert!(broadcast( + &client, + party_num_int, + "round2", + serde_json::to_string(&decom_i).unwrap(), + uuid.clone() + ) + .is_ok()); + let round2_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round2", + uuid.clone(), + ); + + let mut j = 0; + let mut point_vec: Vec> = Vec::new(); + let mut decom_vec: Vec = Vec::new(); + let mut enc_keys: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + point_vec.push(decom_i.y_i.clone()); + decom_vec.push(decom_i.clone()); + } else { + let decom_j: KeyGenDecommitMessage1 = serde_json::from_str(&round2_ans_vec[j]).unwrap(); + point_vec.push(decom_j.y_i.clone()); + decom_vec.push(decom_j.clone()); + let key_bn: BigInt = (decom_j.y_i.clone() * party_keys.u_i.clone()) + .x_coord() + .unwrap(); + let key_bytes = BigInt::to_bytes(&key_bn); + let mut template: Vec = vec![0u8; AES_KEY_BYTES_LEN - key_bytes.len()]; + template.extend_from_slice(&key_bytes[..]); + enc_keys.push(template); + j += 1; + } + } + + let (head, tail) = point_vec.split_at(1); + let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + + let (vss_scheme, secret_shares, _index) = party_keys + .phase1_verify_com_phase3_verify_correct_key_phase2_distribute( + ¶ms, &decom_vec, &bc1_vec, + ) + .expect("invalid key"); + + ////////////////////////////////////////////////////////////////////////////// + + let mut j = 0; + for (k, i) in (1..=PARTIES).enumerate() { + if i != party_num_int { + // prepare encrypted ss for party i: + let key_i = &enc_keys[j]; + let plaintext = BigInt::to_bytes(&secret_shares[k].to_bigint()); + let aead_pack_i = aes_encrypt(key_i, &plaintext); + assert!(sendp2p( + &client, + party_num_int, + i, + "round3", + serde_json::to_string(&aead_pack_i).unwrap(), + uuid.clone() + ) + .is_ok()); + j += 1; + } + } + + let round3_ans_vec = poll_for_p2p( + &client, + party_num_int, + PARTIES, + delay, + "round3", + uuid.clone(), + ); + + let mut j = 0; + let mut party_shares: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + party_shares.push(secret_shares[(i - 1) as usize].clone()); + } else { + let aead_pack: AEAD = serde_json::from_str(&round3_ans_vec[j]).unwrap(); + let key_i = &enc_keys[j]; + let out = aes_decrypt(key_i, aead_pack); + let out_bn = BigInt::from_bytes(&out[..]); + let out_fe = Scalar::::from(&out_bn); + party_shares.push(out_fe); + + j += 1; + } + } + + // round 4: send vss commitments + assert!(broadcast( + &client, + party_num_int, + "round4", + serde_json::to_string(&vss_scheme).unwrap(), + uuid.clone() + ) + .is_ok()); + let round4_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round4", + uuid.clone(), + ); + + let mut j = 0; + let mut vss_scheme_vec: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + vss_scheme_vec.push(vss_scheme.clone()); + } else { + let vss_scheme_j: VerifiableSS = + serde_json::from_str(&round4_ans_vec[j]).unwrap(); + vss_scheme_vec.push(vss_scheme_j); + j += 1; + } + } + + let (shared_keys, dlog_proof) = party_keys + .phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶ms, + &point_vec, + &party_shares, + &vss_scheme_vec, + party_num_int, + ) + .expect("invalid vss"); + + // round 5: send dlog proof + assert!(broadcast( + &client, + party_num_int, + "round5", + serde_json::to_string(&dlog_proof).unwrap(), + uuid.clone() + ) + .is_ok()); + let round5_ans_vec = + poll_for_broadcasts(&client, party_num_int, PARTIES, delay, "round5", uuid); + + let mut j = 0; + let mut dlog_proof_vec: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + dlog_proof_vec.push(dlog_proof.clone()); + } else { + let dlog_proof_j: DLogProof = + serde_json::from_str(&round5_ans_vec[j]).unwrap(); + dlog_proof_vec.push(dlog_proof_j); + j += 1; + } + } + Keys::verify_dlog_proofs(¶ms, &dlog_proof_vec, &point_vec).expect("bad dlog proof"); + + //save key to file: + let paillier_key_vec = (0..PARTIES) + .map(|i| bc1_vec[i as usize].e.clone()) + .collect::>(); + + let keygen_json = serde_json::to_string(&( + party_keys, + shared_keys, + party_num_int, + vss_scheme_vec, + paillier_key_vec, + y_sum, + )) + .unwrap(); + fs::write(env::args().nth(2).unwrap(), keygen_json).expect("Unable to save !"); +} + +pub fn signup(client: &Client) -> Result { + let key = "signup-keygen".to_string(); + + let res_body = postb(client, "signupkeygen", key).unwrap(); + serde_json::from_str(&res_body).unwrap() +} diff --git a/examples/gg18_sign_client.rs b/examples/gg18_sign_client.rs new file mode 100644 index 0000000..dab57f6 --- /dev/null +++ b/examples/gg18_sign_client.rs @@ -0,0 +1,534 @@ +#![allow(non_snake_case)] + +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::{ + proofs::sigma_correct_homomorphic_elgamal_enc::HomoELGamalProof, + proofs::sigma_dlog::DLogProof, secret_sharing::feldman_vss::VerifiableSS, + }, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2018::party_i::{ + Keys, LocalSignature, PartyPrivate, Phase5ADecom1, Phase5Com1, Phase5Com2, Phase5DDecom2, + SharedKeys, SignBroadcastPhase1, SignDecommitPhase1, SignKeys, +}; +use multi_party_ecdsa::utilities::mta::*; +use sha2::Sha256; + +use paillier::EncryptionKey; +use reqwest::Client; +use std::{env, fs, time}; + +mod common; +use common::{ + broadcast, check_sig, poll_for_broadcasts, poll_for_p2p, postb, sendp2p, Params, PartySignup, +}; + +#[allow(clippy::cognitive_complexity)] +fn main() { + if env::args().nth(4).is_some() { + panic!("too many arguments") + } + if env::args().nth(3).is_none() { + panic!("too few arguments") + } + let message_str = env::args().nth(3).unwrap_or_else(|| "".to_string()); + let message = match hex::decode(message_str.clone()) { + Ok(x) => x, + Err(_e) => message_str.as_bytes().to_vec(), + }; + let message = &message[..]; + let client = Client::new(); + // delay: + let delay = time::Duration::from_millis(25); + // read key file + let data = fs::read_to_string(env::args().nth(2).unwrap()) + .expect("Unable to load keys, did you run keygen first? "); + let (party_keys, shared_keys, party_id, vss_scheme_vec, paillier_key_vector, y_sum): ( + Keys, + SharedKeys, + u16, + Vec>, + Vec, + Point, + ) = serde_json::from_str(&data).unwrap(); + + //read parameters: + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + let THRESHOLD = params.threshold.parse::().unwrap(); + + //signup: + let (party_num_int, uuid) = match signup(&client).unwrap() { + PartySignup { number, uuid } => (number, uuid), + }; + println!("number: {:?}, uuid: {:?}", party_num_int, uuid); + + // round 0: collect signers IDs + assert!(broadcast( + &client, + party_num_int, + "round0", + serde_json::to_string(&party_id).unwrap(), + uuid.clone() + ) + .is_ok()); + let round0_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round0", + uuid.clone(), + ); + + let mut j = 0; + let mut signers_vec: Vec = Vec::new(); + for i in 1..=THRESHOLD + 1 { + if i == party_num_int { + signers_vec.push(party_id - 1); + } else { + let signer_j: u16 = serde_json::from_str(&round0_ans_vec[j]).unwrap(); + signers_vec.push(signer_j - 1); + j += 1; + } + } + + let private = PartyPrivate::set_private(party_keys.clone(), shared_keys); + + let sign_keys = SignKeys::create( + &private, + &vss_scheme_vec[usize::from(signers_vec[usize::from(party_num_int - 1)])], + signers_vec[usize::from(party_num_int - 1)], + &signers_vec, + ); + + let xi_com_vec = Keys::get_commitments_to_xi(&vss_scheme_vec); + ////////////////////////////////////////////////////////////////////////////// + let (com, decommit) = sign_keys.phase1_broadcast(); + let (m_a_k, _) = MessageA::a(&sign_keys.k_i, &party_keys.ek, &[]); + assert!(broadcast( + &client, + party_num_int, + "round1", + serde_json::to_string(&(com.clone(), m_a_k)).unwrap(), + uuid.clone() + ) + .is_ok()); + let round1_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round1", + uuid.clone(), + ); + + let mut j = 0; + let mut bc1_vec: Vec = Vec::new(); + let mut m_a_vec: Vec = Vec::new(); + + for i in 1..THRESHOLD + 2 { + if i == party_num_int { + bc1_vec.push(com.clone()); + // m_a_vec.push(m_a_k.clone()); + } else { + // if signers_vec.contains(&(i as usize)) { + let (bc1_j, m_a_party_j): (SignBroadcastPhase1, MessageA) = + serde_json::from_str(&round1_ans_vec[j]).unwrap(); + bc1_vec.push(bc1_j); + m_a_vec.push(m_a_party_j); + + j += 1; + // } + } + } + assert_eq!(signers_vec.len(), bc1_vec.len()); + + ////////////////////////////////////////////////////////////////////////////// + let mut m_b_gamma_send_vec: Vec = Vec::new(); + let mut beta_vec: Vec> = Vec::new(); + let mut m_b_w_send_vec: Vec = Vec::new(); + let mut ni_vec: Vec> = Vec::new(); + let mut j = 0; + for i in 1..THRESHOLD + 2 { + if i != party_num_int { + let (m_b_gamma, beta_gamma, _, _) = MessageB::b( + &sign_keys.gamma_i, + &paillier_key_vector[usize::from(signers_vec[usize::from(i - 1)])], + m_a_vec[j].clone(), + &[], + ) + .unwrap(); + let (m_b_w, beta_wi, _, _) = MessageB::b( + &sign_keys.w_i, + &paillier_key_vector[usize::from(signers_vec[usize::from(i - 1)])], + m_a_vec[j].clone(), + &[], + ) + .unwrap(); + m_b_gamma_send_vec.push(m_b_gamma); + m_b_w_send_vec.push(m_b_w); + beta_vec.push(beta_gamma); + ni_vec.push(beta_wi); + j += 1; + } + } + + let mut j = 0; + for i in 1..THRESHOLD + 2 { + if i != party_num_int { + assert!(sendp2p( + &client, + party_num_int, + i, + "round2", + serde_json::to_string(&(m_b_gamma_send_vec[j].clone(), m_b_w_send_vec[j].clone())) + .unwrap(), + uuid.clone() + ) + .is_ok()); + j += 1; + } + } + + let round2_ans_vec = poll_for_p2p( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round2", + uuid.clone(), + ); + + let mut m_b_gamma_rec_vec: Vec = Vec::new(); + let mut m_b_w_rec_vec: Vec = Vec::new(); + + for i in 0..THRESHOLD { + // if signers_vec.contains(&(i as usize)) { + let (m_b_gamma_i, m_b_w_i): (MessageB, MessageB) = + serde_json::from_str(&round2_ans_vec[i as usize]).unwrap(); + m_b_gamma_rec_vec.push(m_b_gamma_i); + m_b_w_rec_vec.push(m_b_w_i); + // } + } + + let mut alpha_vec: Vec> = Vec::new(); + let mut miu_vec: Vec> = Vec::new(); + + let mut j = 0; + for i in 1..THRESHOLD + 2 { + if i != party_num_int { + let m_b = m_b_gamma_rec_vec[j].clone(); + + let alpha_ij_gamma = m_b + .verify_proofs_get_alpha(&party_keys.dk, &sign_keys.k_i) + .expect("wrong dlog or m_b"); + let m_b = m_b_w_rec_vec[j].clone(); + let alpha_ij_wi = m_b + .verify_proofs_get_alpha(&party_keys.dk, &sign_keys.k_i) + .expect("wrong dlog or m_b"); + alpha_vec.push(alpha_ij_gamma.0); + miu_vec.push(alpha_ij_wi.0); + let g_w_i = Keys::update_commitments_to_xi( + &xi_com_vec[usize::from(signers_vec[usize::from(i - 1)])], + &vss_scheme_vec[usize::from(signers_vec[usize::from(i - 1)])], + signers_vec[usize::from(i - 1)], + &signers_vec, + ); + assert_eq!(m_b.b_proof.pk, g_w_i); + j += 1; + } + } + ////////////////////////////////////////////////////////////////////////////// + let delta_i = sign_keys.phase2_delta_i(&alpha_vec, &beta_vec); + let sigma = sign_keys.phase2_sigma_i(&miu_vec, &ni_vec); + + assert!(broadcast( + &client, + party_num_int, + "round3", + serde_json::to_string(&delta_i).unwrap(), + uuid.clone() + ) + .is_ok()); + let round3_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round3", + uuid.clone(), + ); + let mut delta_vec: Vec> = Vec::new(); + format_vec_from_reads( + &round3_ans_vec, + party_num_int as usize, + delta_i, + &mut delta_vec, + ); + let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); + + ////////////////////////////////////////////////////////////////////////////// + // decommit to gamma_i + assert!(broadcast( + &client, + party_num_int, + "round4", + serde_json::to_string(&decommit).unwrap(), + uuid.clone() + ) + .is_ok()); + let round4_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round4", + uuid.clone(), + ); + + let mut decommit_vec: Vec = Vec::new(); + format_vec_from_reads( + &round4_ans_vec, + party_num_int as usize, + decommit, + &mut decommit_vec, + ); + let decomm_i = decommit_vec.remove(usize::from(party_num_int - 1)); + bc1_vec.remove(usize::from(party_num_int - 1)); + let b_proof_vec = (0..m_b_gamma_rec_vec.len()) + .map(|i| &m_b_gamma_rec_vec[i].b_proof) + .collect::>>(); + let R = SignKeys::phase4(&delta_inv, &b_proof_vec, decommit_vec, &bc1_vec) + .expect("bad gamma_i decommit"); + + // adding local g_gamma_i + let R = R + decomm_i.g_gamma_i * delta_inv; + + // we assume the message is already hashed (by the signer). + let message_bn = BigInt::from_bytes(message); + let local_sig = + LocalSignature::phase5_local_sig(&sign_keys.k_i, &message_bn, &R, &sigma, &y_sum); + + let (phase5_com, phase_5a_decom, helgamal_proof, dlog_proof_rho) = + local_sig.phase5a_broadcast_5b_zkproof(); + + //phase (5A) broadcast commit + assert!(broadcast( + &client, + party_num_int, + "round5", + serde_json::to_string(&phase5_com).unwrap(), + uuid.clone() + ) + .is_ok()); + let round5_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round5", + uuid.clone(), + ); + + let mut commit5a_vec: Vec = Vec::new(); + format_vec_from_reads( + &round5_ans_vec, + party_num_int as usize, + phase5_com, + &mut commit5a_vec, + ); + + //phase (5B) broadcast decommit and (5B) ZK proof + assert!(broadcast( + &client, + party_num_int, + "round6", + serde_json::to_string(&( + phase_5a_decom.clone(), + helgamal_proof.clone(), + dlog_proof_rho.clone() + )) + .unwrap(), + uuid.clone() + ) + .is_ok()); + let round6_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round6", + uuid.clone(), + ); + + let mut decommit5a_and_elgamal_and_dlog_vec: Vec<( + Phase5ADecom1, + HomoELGamalProof, + DLogProof, + )> = Vec::new(); + format_vec_from_reads( + &round6_ans_vec, + party_num_int as usize, + (phase_5a_decom.clone(), helgamal_proof, dlog_proof_rho), + &mut decommit5a_and_elgamal_and_dlog_vec, + ); + let decommit5a_and_elgamal_and_dlog_vec_includes_i = + decommit5a_and_elgamal_and_dlog_vec.clone(); + decommit5a_and_elgamal_and_dlog_vec.remove(usize::from(party_num_int - 1)); + commit5a_vec.remove(usize::from(party_num_int - 1)); + let phase_5a_decomm_vec = (0..THRESHOLD) + .map(|i| decommit5a_and_elgamal_and_dlog_vec[i as usize].0.clone()) + .collect::>(); + let phase_5a_elgamal_vec = (0..THRESHOLD) + .map(|i| decommit5a_and_elgamal_and_dlog_vec[i as usize].1.clone()) + .collect::>>(); + let phase_5a_dlog_vec = (0..THRESHOLD) + .map(|i| decommit5a_and_elgamal_and_dlog_vec[i as usize].2.clone()) + .collect::>>(); + let (phase5_com2, phase_5d_decom2) = local_sig + .phase5c( + &phase_5a_decomm_vec, + &commit5a_vec, + &phase_5a_elgamal_vec, + &phase_5a_dlog_vec, + &phase_5a_decom.V_i, + &R, + ) + .expect("error phase5"); + + ////////////////////////////////////////////////////////////////////////////// + assert!(broadcast( + &client, + party_num_int, + "round7", + serde_json::to_string(&phase5_com2).unwrap(), + uuid.clone() + ) + .is_ok()); + let round7_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round7", + uuid.clone(), + ); + + let mut commit5c_vec: Vec = Vec::new(); + format_vec_from_reads( + &round7_ans_vec, + party_num_int as usize, + phase5_com2, + &mut commit5c_vec, + ); + + //phase (5B) broadcast decommit and (5B) ZK proof + assert!(broadcast( + &client, + party_num_int, + "round8", + serde_json::to_string(&phase_5d_decom2).unwrap(), + uuid.clone() + ) + .is_ok()); + let round8_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + THRESHOLD + 1, + delay, + "round8", + uuid.clone(), + ); + + let mut decommit5d_vec: Vec = Vec::new(); + format_vec_from_reads( + &round8_ans_vec, + party_num_int as usize, + phase_5d_decom2, + &mut decommit5d_vec, + ); + + let phase_5a_decomm_vec_includes_i = (0..=THRESHOLD) + .map(|i| { + decommit5a_and_elgamal_and_dlog_vec_includes_i[i as usize] + .0 + .clone() + }) + .collect::>(); + let s_i = local_sig + .phase5d( + &decommit5d_vec, + &commit5c_vec, + &phase_5a_decomm_vec_includes_i, + ) + .expect("bad com 5d"); + + ////////////////////////////////////////////////////////////////////////////// + assert!(broadcast( + &client, + party_num_int, + "round9", + serde_json::to_string(&s_i).unwrap(), + uuid.clone() + ) + .is_ok()); + let round9_ans_vec = + poll_for_broadcasts(&client, party_num_int, THRESHOLD + 1, delay, "round9", uuid); + + let mut s_i_vec: Vec> = Vec::new(); + format_vec_from_reads(&round9_ans_vec, party_num_int as usize, s_i, &mut s_i_vec); + + s_i_vec.remove(usize::from(party_num_int - 1)); + let sig = local_sig + .output_signature(&s_i_vec) + .expect("verification failed"); + //println!("party {:?} Output Signature: \n", party_num_int); + //println!("R: {:?}", sig.r); + //println!("s: {:?} \n", sig.s); + //println!("recid: {:?} \n", sig.recid.clone()); + + let sign_json = serde_json::to_string(&( + "r", + BigInt::from_bytes(sig.r.to_bytes().as_ref()).to_str_radix(16), + "s", + BigInt::from_bytes(sig.s.to_bytes().as_ref()).to_str_radix(16), + )) + .unwrap(); + + println!("sig_json: {:?}, {:?}, {:?}", BigInt::from_bytes(sig.r.to_bytes().as_ref()).to_str_radix(16), BigInt::from_bytes(sig.s.to_bytes().as_ref()).to_str_radix(16), sig.recid.clone()); + + + // check sig against secp256k1 + check_sig(&sig.r, &sig.s, &message_bn, &y_sum); + + fs::write("signature".to_string(), sign_json).expect("Unable to save !"); +} + +fn format_vec_from_reads<'a, T: serde::Deserialize<'a> + Clone>( + ans_vec: &'a [String], + party_num: usize, + value_i: T, + new_vec: &'a mut Vec, +) { + let mut j = 0; + for i in 1..ans_vec.len() + 2 { + if i == party_num { + new_vec.push(value_i.clone()); + } else { + let value_j: T = serde_json::from_str(&ans_vec[j]).unwrap(); + new_vec.push(value_j); + j += 1; + } + } +} + +pub fn signup(client: &Client) -> Result { + let key = "signup-sign".to_string(); + + let res_body = postb(client, "signupsign", key).unwrap(); + serde_json::from_str(&res_body).unwrap() +} diff --git a/examples/gg18_sm_manager.rs b/examples/gg18_sm_manager.rs new file mode 100644 index 0000000..9bbe55d --- /dev/null +++ b/examples/gg18_sm_manager.rs @@ -0,0 +1,143 @@ +use std::collections::HashMap; +use std::fs; +use std::sync::RwLock; + +use rocket::serde::json::Json; +use rocket::{post, routes, State}; +use uuid::Uuid; + +mod common; +use common::{Entry, Index, Key, Params, PartySignup}; + +#[post("/get", format = "json", data = "")] +fn get( + db_mtx: &State>>, + request: Json, +) -> Json> { + let index: Index = request.0; + let hm = db_mtx.read().unwrap(); + match hm.get(&index.key) { + Some(v) => { + let entry = Entry { + key: index.key, + value: v.clone(), + }; + Json(Ok(entry)) + } + None => Json(Err(())), + } +} + +#[post("/set", format = "json", data = "")] +fn set(db_mtx: &State>>, request: Json) -> Json> { + let entry: Entry = request.0; + let mut hm = db_mtx.write().unwrap(); + hm.insert(entry.key.clone(), entry.value); + Json(Ok(())) +} + +#[post("/signupkeygen", format = "json")] +fn signup_keygen(db_mtx: &State>>) -> Json> { + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + let parties = params.parties.parse::().unwrap(); + + let key = "signup-keygen".to_string(); + + let party_signup = { + let hm = db_mtx.read().unwrap(); + let value = hm.get(&key).unwrap(); + let client_signup: PartySignup = serde_json::from_str(value).unwrap(); + if client_signup.number < parties { + PartySignup { + number: client_signup.number + 1, + uuid: client_signup.uuid, + } + } else { + PartySignup { + number: 1, + uuid: Uuid::new_v4().to_string(), + } + } + }; + + let mut hm = db_mtx.write().unwrap(); + hm.insert(key, serde_json::to_string(&party_signup).unwrap()); + Json(Ok(party_signup)) +} + +#[post("/signupsign", format = "json")] +fn signup_sign(db_mtx: &State>>) -> Json> { + //read parameters: + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + let threshold = params.threshold.parse::().unwrap(); + let key = "signup-sign".to_string(); + + let party_signup = { + let hm = db_mtx.read().unwrap(); + let value = hm.get(&key).unwrap(); + let client_signup: PartySignup = serde_json::from_str(value).unwrap(); + if client_signup.number < threshold + 1 { + PartySignup { + number: client_signup.number + 1, + uuid: client_signup.uuid, + } + } else { + PartySignup { + number: 1, + uuid: Uuid::new_v4().to_string(), + } + } + }; + + let mut hm = db_mtx.write().unwrap(); + hm.insert(key, serde_json::to_string(&party_signup).unwrap()); + Json(Ok(party_signup)) +} + +#[tokio::main] +async fn main() { + // let mut my_config = Config::development(); + // my_config.set_port(18001); + let db: HashMap = HashMap::new(); + let db_mtx = RwLock::new(db); + //rocket::custom(my_config).mount("/", routes![get, set]).manage(db_mtx).launch(); + + ///////////////////////////////////////////////////////////////// + //////////////////////////init signups:////////////////////////// + ///////////////////////////////////////////////////////////////// + + let keygen_key = "signup-keygen".to_string(); + let sign_key = "signup-sign".to_string(); + + let uuid_keygen = Uuid::new_v4().to_string(); + let uuid_sign = Uuid::new_v4().to_string(); + + let party1 = 0; + let party_signup_keygen = PartySignup { + number: party1, + uuid: uuid_keygen, + }; + let party_signup_sign = PartySignup { + number: party1, + uuid: uuid_sign, + }; + { + let mut hm = db_mtx.write().unwrap(); + hm.insert( + keygen_key, + serde_json::to_string(&party_signup_keygen).unwrap(), + ); + hm.insert(sign_key, serde_json::to_string(&party_signup_sign).unwrap()); + } + ///////////////////////////////////////////////////////////////// + rocket::build() + .mount("/", routes![get, set, signup_keygen, signup_sign]) + .manage(db_mtx) + .launch() + .await + .unwrap(); +} diff --git a/examples/gg20_keygen.rs b/examples/gg20_keygen.rs new file mode 100644 index 0000000..2de51da --- /dev/null +++ b/examples/gg20_keygen.rs @@ -0,0 +1,58 @@ +use anyhow::{anyhow, Context, Result}; +use futures::StreamExt; +use std::path::PathBuf; +use structopt::StructOpt; + +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::Keygen; +use round_based::async_runtime::AsyncProtocol; + +mod gg20_sm_client; +use gg20_sm_client::join_computation; + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(short, long, default_value = "http://localhost:8000/")] + address: surf::Url, + #[structopt(short, long, default_value = "default-keygen")] + room: String, + #[structopt(short, long)] + output: PathBuf, + + #[structopt(short, long)] + index: u16, + #[structopt(short, long)] + threshold: u16, + #[structopt(short, long)] + number_of_parties: u16, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args: Cli = Cli::from_args(); + let mut output_file = tokio::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(args.output) + .await + .context("cannot create output file")?; + + let (_i, incoming, outgoing) = join_computation(args.address, &args.room) + .await + .context("join computation")?; + + let incoming = incoming.fuse(); + tokio::pin!(incoming); + tokio::pin!(outgoing); + + let keygen = Keygen::new(args.index, args.threshold, args.number_of_parties)?; + let output = AsyncProtocol::new(keygen, incoming, outgoing) + .run() + .await + .map_err(|e| anyhow!("protocol execution terminated with error: {}", e))?; + let output = serde_json::to_vec_pretty(&output).context("serialize output")?; + tokio::io::copy(&mut output.as_slice(), &mut output_file) + .await + .context("save output to file")?; + + Ok(()) +} diff --git a/examples/gg20_signing.rs b/examples/gg20_signing.rs new file mode 100644 index 0000000..cb417ba --- /dev/null +++ b/examples/gg20_signing.rs @@ -0,0 +1,90 @@ +use std::path::PathBuf; + +use anyhow::{anyhow, Context, Result}; +use futures::{SinkExt, StreamExt, TryStreamExt}; +use structopt::StructOpt; + +use curv::arithmetic::Converter; +use curv::BigInt; + +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::sign::{ + OfflineStage, SignManual, +}; +use round_based::async_runtime::AsyncProtocol; +use round_based::Msg; + +mod gg20_sm_client; +use gg20_sm_client::join_computation; + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(short, long, default_value = "http://localhost:8000/")] + address: surf::Url, + #[structopt(short, long, default_value = "default-signing")] + room: String, + #[structopt(short, long)] + local_share: PathBuf, + + #[structopt(short, long, use_delimiter(true))] + parties: Vec, + #[structopt(short, long)] + data_to_sign: String, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args: Cli = Cli::from_args(); + let local_share = tokio::fs::read(args.local_share) + .await + .context("cannot read local share")?; + let local_share = serde_json::from_slice(&local_share).context("parse local share")?; + let number_of_parties = args.parties.len(); + + let (i, incoming, outgoing) = + join_computation(args.address.clone(), &format!("{}-offline", args.room)) + .await + .context("join offline computation")?; + + let incoming = incoming.fuse(); + tokio::pin!(incoming); + tokio::pin!(outgoing); + + let signing = OfflineStage::new(i, args.parties, local_share)?; + let completed_offline_stage = AsyncProtocol::new(signing, incoming, outgoing) + .run() + .await + .map_err(|e| anyhow!("protocol execution terminated with error: {}", e))?; + + let (_i, incoming, outgoing) = join_computation(args.address, &format!("{}-online", args.room)) + .await + .context("join online computation")?; + + tokio::pin!(incoming); + tokio::pin!(outgoing); + + let (signing, partial_signature) = SignManual::new( + BigInt::from_bytes(args.data_to_sign.as_bytes()), + completed_offline_stage, + )?; + + outgoing + .send(Msg { + sender: i, + receiver: None, + body: partial_signature, + }) + .await?; + + let partial_signatures: Vec<_> = incoming + .take(number_of_parties - 1) + .map_ok(|msg| msg.body) + .try_collect() + .await?; + let signature = signing + .complete(&partial_signatures) + .context("online stage failed")?; + let signature = serde_json::to_string(&signature).context("serialize signature")?; + println!("{}", signature); + + Ok(()) +} diff --git a/examples/gg20_sm_client.rs b/examples/gg20_sm_client.rs new file mode 100644 index 0000000..f28c44d --- /dev/null +++ b/examples/gg20_sm_client.rs @@ -0,0 +1,159 @@ +use std::convert::TryInto; + +use anyhow::{Context, Result}; +use futures::{Sink, Stream, StreamExt, TryStreamExt}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use structopt::StructOpt; + +use round_based::Msg; + +pub async fn join_computation( + address: surf::Url, + room_id: &str, +) -> Result<( + u16, + impl Stream>>, + impl Sink, Error = anyhow::Error>, +)> +where + M: Serialize + DeserializeOwned, +{ + let client = SmClient::new(address, room_id).context("construct SmClient")?; + + // Construct channel of incoming messages + let incoming = client + .subscribe() + .await + .context("subscribe")? + .and_then(|msg| async move { + serde_json::from_str::>(&msg).context("deserialize message") + }); + + // Obtain party index + let index = client.issue_index().await.context("issue an index")?; + + // Ignore incoming messages addressed to someone else + let incoming = incoming.try_filter(move |msg| { + futures::future::ready( + msg.sender != index && (msg.receiver.is_none() || msg.receiver == Some(index)), + ) + }); + + // Construct channel of outgoing messages + let outgoing = futures::sink::unfold(client, |client, message: Msg| async move { + let serialized = serde_json::to_string(&message).context("serialize message")?; + client + .broadcast(&serialized) + .await + .context("broadcast message")?; + Ok::<_, anyhow::Error>(client) + }); + + Ok((index, incoming, outgoing)) +} + +pub struct SmClient { + http_client: surf::Client, +} + +impl SmClient { + pub fn new(address: surf::Url, room_id: &str) -> Result { + let config = surf::Config::new() + .set_base_url(address.join(&format!("rooms/{}/", room_id))?) + .set_timeout(None); + Ok(Self { + http_client: config.try_into()?, + }) + } + + pub async fn issue_index(&self) -> Result { + let response = self + .http_client + .post("issue_unique_idx") + .recv_json::() + .await + .map_err(|e| e.into_inner())?; + Ok(response.unique_idx) + } + + pub async fn broadcast(&self, message: &str) -> Result<()> { + self.http_client + .post("broadcast") + .body(message) + .await + .map_err(|e| e.into_inner())?; + Ok(()) + } + + pub async fn subscribe(&self) -> Result>> { + let response = self + .http_client + .get("subscribe") + .await + .map_err(|e| e.into_inner())?; + let events = async_sse::decode(response); + Ok(events.filter_map(|msg| async { + match msg { + Ok(async_sse::Event::Message(msg)) => Some( + String::from_utf8(msg.into_bytes()) + .context("SSE message is not valid UTF-8 string"), + ), + Ok(_) => { + // ignore other types of events + None + } + Err(e) => Some(Err(e.into_inner())), + } + })) + } +} + +#[derive(Deserialize, Debug)] +struct IssuedUniqueIdx { + unique_idx: u16, +} + +#[derive(StructOpt, Debug)] +struct Cli { + #[structopt(short, long)] + address: surf::Url, + #[structopt(short, long)] + room: String, + #[structopt(subcommand)] + cmd: Cmd, +} + +#[derive(StructOpt, Debug)] +enum Cmd { + Subscribe, + Broadcast { + #[structopt(short, long)] + message: String, + }, + IssueIdx, +} + +#[tokio::main] +#[allow(dead_code)] +async fn main() -> Result<()> { + let args: Cli = Cli::from_args(); + let client = SmClient::new(args.address, &args.room).context("create SmClient")?; + match args.cmd { + Cmd::Broadcast { message } => client + .broadcast(&message) + .await + .context("broadcast message")?, + Cmd::IssueIdx => { + let index = client.issue_index().await.context("issue index")?; + println!("Index: {}", index); + } + Cmd::Subscribe => { + let messages = client.subscribe().await.context("subsribe")?; + tokio::pin!(messages); + while let Some(message) = messages.next().await { + println!("{:?}", message); + } + } + } + Ok(()) +} diff --git a/examples/gg20_sm_manager.rs b/examples/gg20_sm_manager.rs new file mode 100644 index 0000000..ba34e6d --- /dev/null +++ b/examples/gg20_sm_manager.rs @@ -0,0 +1,193 @@ +use std::collections::hash_map::{Entry, HashMap}; +use std::sync::{ + atomic::{AtomicU16, Ordering}, + Arc, +}; + +use futures::Stream; +use rocket::data::ToByteUnit; +use rocket::http::Status; +use rocket::request::{FromRequest, Outcome, Request}; +use rocket::response::stream::{stream, Event, EventStream}; +use rocket::serde::json::Json; +use rocket::State; +use serde::{Deserialize, Serialize}; +use tokio::sync::{Notify, RwLock}; + +#[rocket::get("/rooms//subscribe")] +async fn subscribe( + db: &State, + mut shutdown: rocket::Shutdown, + last_seen_msg: LastEventId, + room_id: &str, +) -> EventStream> { + let room = db.get_room_or_create_empty(room_id).await; + let mut subscription = room.subscribe(last_seen_msg.0); + EventStream::from(stream! { + loop { + let (id, msg) = tokio::select! { + message = subscription.next() => message, + _ = &mut shutdown => return, + }; + yield Event::data(msg) + .event("new-message") + .id(id.to_string()) + } + }) +} + +#[rocket::post("/rooms//issue_unique_idx")] +async fn issue_idx(db: &State, room_id: &str) -> Json { + let room = db.get_room_or_create_empty(room_id).await; + let idx = room.issue_unique_idx(); + Json::from(IssuedUniqueIdx { unique_idx: idx }) +} + +#[rocket::post("/rooms//broadcast", data = "")] +async fn broadcast(db: &State, room_id: &str, message: String) -> Status { + let room = db.get_room_or_create_empty(room_id).await; + room.publish(message).await; + Status::Ok +} + +struct Db { + rooms: RwLock>>, +} + +struct Room { + messages: RwLock>, + message_appeared: Notify, + subscribers: AtomicU16, + next_idx: AtomicU16, +} + +impl Db { + pub fn empty() -> Self { + Self { + rooms: RwLock::new(HashMap::new()), + } + } + + pub async fn get_room_or_create_empty(&self, room_id: &str) -> Arc { + let rooms = self.rooms.read().await; + if let Some(room) = rooms.get(room_id) { + // If no one is watching this room - we need to clean it up first + if !room.is_abandoned() { + return room.clone(); + } + } + drop(rooms); + + let mut rooms = self.rooms.write().await; + match rooms.entry(room_id.to_owned()) { + Entry::Occupied(entry) if !entry.get().is_abandoned() => entry.get().clone(), + Entry::Occupied(entry) => { + let room = Arc::new(Room::empty()); + *entry.into_mut() = room.clone(); + room + } + Entry::Vacant(entry) => entry.insert(Arc::new(Room::empty())).clone(), + } + } +} + +impl Room { + pub fn empty() -> Self { + Self { + messages: RwLock::new(vec![]), + message_appeared: Notify::new(), + subscribers: AtomicU16::new(0), + next_idx: AtomicU16::new(1), + } + } + + pub async fn publish(self: &Arc, message: String) { + let mut messages = self.messages.write().await; + messages.push(message); + self.message_appeared.notify_waiters(); + } + + pub fn subscribe(self: Arc, last_seen_msg: Option) -> Subscription { + self.subscribers.fetch_add(1, Ordering::SeqCst); + Subscription { + room: self, + next_event: last_seen_msg.map(|i| i + 1).unwrap_or(0), + } + } + + pub fn is_abandoned(&self) -> bool { + self.subscribers.load(Ordering::SeqCst) == 0 + } + + pub fn issue_unique_idx(&self) -> u16 { + self.next_idx.fetch_add(1, Ordering::Relaxed) + } +} + +struct Subscription { + room: Arc, + next_event: u16, +} + +impl Subscription { + pub async fn next(&mut self) -> (u16, String) { + loop { + let history = self.room.messages.read().await; + if let Some(msg) = history.get(usize::from(self.next_event)) { + let event_id = self.next_event; + self.next_event = event_id + 1; + return (event_id, msg.clone()); + } + let notification = self.room.message_appeared.notified(); + drop(history); + notification.await; + } + } +} + +impl Drop for Subscription { + fn drop(&mut self) { + self.room.subscribers.fetch_sub(1, Ordering::SeqCst); + } +} + +/// Represents a header Last-Event-ID +struct LastEventId(Option); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for LastEventId { + type Error = &'static str; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let header = request + .headers() + .get_one("Last-Event-ID") + .map(|id| id.parse::()); + match header { + Some(Ok(last_seen_msg)) => Outcome::Success(LastEventId(Some(last_seen_msg))), + Some(Err(_parse_err)) => { + Outcome::Error((Status::BadRequest, "last seen msg id is not valid")) + } + None => Outcome::Success(LastEventId(None)), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct IssuedUniqueIdx { + unique_idx: u16, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let figment = rocket::Config::figment().merge(( + "limits", + rocket::data::Limits::new().limit("string", 100.megabytes()), + )); + rocket::custom(figment) + .mount("/", rocket::routes![subscribe, issue_idx, broadcast]) + .manage(Db::empty()) + .launch() + .await?; + Ok(()) +} diff --git a/kzen-dev-setup.sh b/kzen-dev-setup.sh new file mode 100644 index 0000000..f926700 --- /dev/null +++ b/kzen-dev-setup.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +echo "KZen dev Setup" +echo "Installing Git hooks..." +git clone https://github.com/KZen-networks/scripts.git +chmod -R +x scripts/git/hooks/rust/ +cp scripts/git/hooks/rust/* .git/hooks/ +rm -rf scripts +echo "Done." \ No newline at end of file diff --git a/params.json b/params.json new file mode 100644 index 0000000..8dcdefd --- /dev/null +++ b/params.json @@ -0,0 +1 @@ +{"parties":"3", "threshold":"1"} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8aec526 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,31 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +#![allow(clippy::many_single_char_names)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] + +pub mod protocols; +pub mod utilities; +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum Error { + InvalidKey, + InvalidSS, + InvalidCom, + InvalidSig, + Phase5BadSum, + Phase6Error, +} diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs new file mode 100644 index 0000000..9c17a1a --- /dev/null +++ b/src/protocols/mod.rs @@ -0,0 +1,18 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +pub mod multi_party_ecdsa; +pub mod two_party_ecdsa; diff --git a/src/protocols/multi_party_ecdsa/gg_2018/mod.rs b/src/protocols/multi_party_ecdsa/gg_2018/mod.rs new file mode 100644 index 0000000..ad1652b --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2018/mod.rs @@ -0,0 +1,20 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +pub mod party_i; + +#[cfg(test)] +mod test; diff --git a/src/protocols/multi_party_ecdsa/gg_2018/party_i.rs b/src/protocols/multi_party_ecdsa/gg_2018/party_i.rs new file mode 100644 index 0000000..7899f31 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2018/party_i.rs @@ -0,0 +1,737 @@ +#![allow(non_snake_case)] + +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +use std::convert::TryFrom; + +use centipede::juggling::proof_system::{Helgamalsegmented, Witness}; +use centipede::juggling::segmentation::Msegmentation; +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_correct_homomorphic_elgamal_enc::*; +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; +use curv::elliptic::curves::{Curve, Point, Scalar, Secp256k1}; +use curv::BigInt; +use paillier::{ + Decrypt, DecryptionKey, EncryptionKey, KeyGeneration, Paillier, RawCiphertext, RawPlaintext, +}; +use sha2::Sha256; +use zk_paillier::zkproofs::NiCorrectKeyProof; + +use serde::{Deserialize, Serialize}; + +use crate::Error::{self, InvalidCom, InvalidKey, InvalidSS, InvalidSig}; + +const SECURITY: usize = 256; + +#[derive(Debug)] +pub struct Parameters { + pub threshold: u16, //t + pub share_count: u16, //n +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Keys { + pub u_i: Scalar, + pub y_i: Point, + pub dk: DecryptionKey, + pub ek: EncryptionKey, + pub party_index: u16, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PartyPrivate { + u_i: Scalar, + x_i: Scalar, + dk: DecryptionKey, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenBroadcastMessage1 { + pub e: EncryptionKey, + pub com: BigInt, + pub correct_key_proof: NiCorrectKeyProof, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenDecommitMessage1 { + pub blind_factor: BigInt, + pub y_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SharedKeys { + pub y: Point, + pub x_i: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignKeys { + pub w_i: Scalar, + pub g_w_i: Point, + pub k_i: Scalar, + pub gamma_i: Scalar, + pub g_gamma_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignBroadcastPhase1 { + pub com: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignDecommitPhase1 { + pub blind_factor: BigInt, + pub g_gamma_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalSignature { + pub l_i: Scalar, + pub rho_i: Scalar, + pub R: Point, + pub s_i: Scalar, + pub m: BigInt, + pub y: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Phase5Com1 { + pub com: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Phase5Com2 { + pub com: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Phase5ADecom1 { + pub V_i: Point, + pub A_i: Point, + pub B_i: Point, + pub blind_factor: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Phase5DDecom2 { + pub u_i: Point, + pub t_i: Point, + pub blind_factor: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignatureRecid { + pub r: Scalar, + pub s: Scalar, + pub recid: u8, +} + +impl Keys { + pub fn create(index: u16) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + } + } + + // we recommend using safe primes if the code is used in production + pub fn create_safe_prime(index: u16) -> Keys { + let u = Scalar::::random(); + let y = Point::generator() * &u; + + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + } + } + pub fn create_from(u: Scalar, index: u16) -> Keys { + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + } + } + + pub fn phase1_broadcast_phase3_proof_of_correct_key( + &self, + ) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { + let blind_factor = BigInt::sample(SECURITY); + let correct_key_proof = NiCorrectKeyProof::proof(&self.dk, None); + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(self.y_i.to_bytes(true).as_ref()), + &blind_factor, + ); + let bcm1 = KeyGenBroadcastMessage1 { + e: self.ek.clone(), + com, + correct_key_proof, + }; + let decom1 = KeyGenDecommitMessage1 { + blind_factor, + y_i: self.y_i.clone(), + }; + (bcm1, decom1) + } + + #[allow(clippy::type_complexity)] + pub fn phase1_verify_com_phase3_verify_correct_key_phase2_distribute( + &self, + params: &Parameters, + decom_vec: &[KeyGenDecommitMessage1], + bc1_vec: &[KeyGenBroadcastMessage1], + ) -> Result<(VerifiableSS, Vec>, u16), Error> { + // test length: + assert_eq!(decom_vec.len(), usize::from(params.share_count)); + assert_eq!(bc1_vec.len(), usize::from(params.share_count)); + // test paillier correct key and test decommitments + let correct_key_correct_decom_all = (0..bc1_vec.len()).all(|i| { + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(decom_vec[i].y_i.to_bytes(true).as_ref()), + &decom_vec[i].blind_factor, + ) == bc1_vec[i].com + && bc1_vec[i] + .correct_key_proof + .verify(&bc1_vec[i].e, zk_paillier::zkproofs::SALT_STRING) + .is_ok() + }); + + let (vss_scheme, secret_shares) = + VerifiableSS::share(params.threshold, params.share_count, &self.u_i); + if correct_key_correct_decom_all { + Ok((vss_scheme, secret_shares.to_vec(), self.party_index)) + } else { + Err(InvalidKey) + } + } + + pub fn phase2_verify_vss_construct_keypair_phase3_pok_dlog( + &self, + params: &Parameters, + y_vec: &[Point], + secret_shares_vec: &[Scalar], + vss_scheme_vec: &[VerifiableSS], + index: u16, + ) -> Result<(SharedKeys, DLogProof), Error> { + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(secret_shares_vec.len(), usize::from(params.share_count)); + assert_eq!(vss_scheme_vec.len(), usize::from(params.share_count)); + + let correct_ss_verify = (0..y_vec.len()).all(|i| { + vss_scheme_vec[i] + .validate_share(&secret_shares_vec[i], index) + .is_ok() + && vss_scheme_vec[i].commitments[0] == y_vec[i] + }); + + if correct_ss_verify { + let y: Point = y_vec.iter().sum(); + let x_i: Scalar = secret_shares_vec.iter().sum(); + let dlog_proof = DLogProof::prove(&x_i); + Ok((SharedKeys { y, x_i }, dlog_proof)) + } else { + Err(InvalidSS) + } + } + + pub fn get_commitments_to_xi( + vss_scheme_vec: &[VerifiableSS], + ) -> Vec> { + let len = vss_scheme_vec.len(); + (1..=u16::try_from(len).unwrap()) + .map(|i| { + (0..len) + .map(|j| vss_scheme_vec[j].get_point_commitment(i)) + .sum() + }) + .collect::>>() + } + + pub fn update_commitments_to_xi( + comm: &Point, + vss_scheme: &VerifiableSS, + index: u16, + s: &[u16], + ) -> Point { + let li = + VerifiableSS::::map_share_to_new_params(&vss_scheme.parameters, index, s); + comm * &li + } + + pub fn verify_dlog_proofs( + params: &Parameters, + dlog_proofs_vec: &[DLogProof], + y_vec: &[Point], + ) -> Result<(), Error> { + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(dlog_proofs_vec.len(), usize::from(params.share_count)); + + let xi_dlog_verify = + (0..y_vec.len()).all(|i| DLogProof::verify(&dlog_proofs_vec[i]).is_ok()); + + if xi_dlog_verify { + Ok(()) + } else { + Err(InvalidKey) + } + } +} + +impl PartyPrivate { + pub fn set_private(key: Keys, shared_key: SharedKeys) -> Self { + Self { + u_i: key.u_i, + x_i: shared_key.x_i, + dk: key.dk, + } + } + + pub fn y_i(&self) -> Point { + Point::generator() * &self.u_i + } + + pub fn decrypt(&self, ciphertext: BigInt) -> RawPlaintext { + Paillier::decrypt(&self.dk, &RawCiphertext::from(ciphertext)) + } + + pub fn refresh_private_key(&self, factor: &Scalar, index: u16) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + } + } + + // we recommend using safe primes if the code is used in production + pub fn refresh_private_key_safe_prime(&self, factor: &Scalar, index: u16) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + } + } + + // used for verifiable recovery + pub fn to_encrypted_segment( + &self, + segment_size: usize, + num_of_segments: usize, + pub_ke_y: &Point, + g: &Point, + ) -> (Witness, Helgamalsegmented) { + Msegmentation::to_encrypted_segments(&self.u_i, &segment_size, num_of_segments, pub_ke_y, g) + } + + pub fn update_private_key( + &self, + factor_u_i: &Scalar, + factor_x_i: &Scalar, + ) -> Self { + PartyPrivate { + u_i: &self.u_i + factor_u_i, + x_i: &self.x_i + factor_x_i, + dk: self.dk.clone(), + } + } +} + +impl SignKeys { + pub fn create( + private: &PartyPrivate, + vss_scheme: &VerifiableSS, + index: u16, + s: &[u16], + ) -> Self { + let li = + VerifiableSS::::map_share_to_new_params(&vss_scheme.parameters, index, s); + let w_i = li * &private.x_i; + let g = Point::generator(); + let g_w_i = g * &w_i; + let gamma_i = Scalar::::random(); + let g_gamma_i = g * &gamma_i; + + Self { + w_i, + g_w_i, + k_i: Scalar::::random(), + gamma_i, + g_gamma_i, + } + } + + pub fn phase1_broadcast(&self) -> (SignBroadcastPhase1, SignDecommitPhase1) { + let blind_factor = BigInt::sample(SECURITY); + let g = Point::generator(); + let g_gamma_i = g * &self.gamma_i; + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(g_gamma_i.to_bytes(true).as_ref()), + &blind_factor, + ); + + ( + SignBroadcastPhase1 { com }, + SignDecommitPhase1 { + blind_factor, + g_gamma_i: self.g_gamma_i.clone(), + }, + ) + } + + pub fn phase2_delta_i( + &self, + alpha_vec: &[Scalar], + beta_vec: &[Scalar], + ) -> Scalar { + assert_eq!(alpha_vec.len(), beta_vec.len()); + let ki_gamma_i = &self.k_i * &self.gamma_i; + ki_gamma_i + alpha_vec.iter().chain(beta_vec).sum::>() + } + + pub fn phase2_sigma_i( + &self, + miu_vec: &[Scalar], + ni_vec: &[Scalar], + ) -> Scalar { + assert_eq!(miu_vec.len(), ni_vec.len()); + let ki_w_i = &self.k_i * &self.w_i; + ki_w_i + miu_vec.iter().chain(ni_vec).sum::>() + } + + pub fn phase3_reconstruct_delta(delta_vec: &[Scalar]) -> Scalar { + delta_vec + .iter() + .sum::>() + .invert() + .expect("sum of deltas is zero") + } + + pub fn phase4( + delta_inv: &Scalar, + b_proof_vec: &[&DLogProof], + phase1_decommit_vec: Vec, + bc1_vec: &[SignBroadcastPhase1], + ) -> Result, Error> { + // note: b_proof_vec is populated using the results + //from the MtAwc, which is handling the proof of knowledge verification of gamma_i such that + // Gamme_i = gamma_i * G in the verify_proofs_get_alpha() + let test_b_vec_and_com = (0..b_proof_vec.len()).all(|i| { + b_proof_vec[i].pk == phase1_decommit_vec[i].g_gamma_i + && HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(phase1_decommit_vec[i].g_gamma_i.to_bytes(true).as_ref()), + &phase1_decommit_vec[i].blind_factor, + ) == bc1_vec[i].com + }); + + if test_b_vec_and_com { + Ok({ + let gamma_sum: Point = phase1_decommit_vec + .iter() + .map(|decom| &decom.g_gamma_i) + .sum(); + // R + gamma_sum * delta_inv + }) + } else { + Err(InvalidKey) + } + } +} + +impl LocalSignature { + pub fn phase5_local_sig( + k_i: &Scalar, + message: &BigInt, + R: &Point, + sigma_i: &Scalar, + pubkey: &Point, + ) -> Self { + let m_fe = Scalar::::from(message); + let r = Scalar::::from( + &R.x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let s_i = m_fe * k_i + r * sigma_i; + let l_i = Scalar::::random(); + let rho_i = Scalar::::random(); + Self { + l_i, + rho_i, + R: R.clone(), + s_i, + m: message.clone(), + y: pubkey.clone(), + } + } + + pub fn phase5a_broadcast_5b_zkproof( + &self, + ) -> ( + Phase5Com1, + Phase5ADecom1, + HomoELGamalProof, + DLogProof, + ) { + let blind_factor = BigInt::sample(SECURITY); + let g = Point::generator(); + let A_i = g * &self.rho_i; + let l_i_rho_i = &self.l_i * &self.rho_i; + let B_i = g * l_i_rho_i; + let V_i = &self.R * &self.s_i + g * &self.l_i; + let input_hash = Sha256::new() + .chain_points([&V_i, &A_i, &B_i]) + .result_bigint(); + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &input_hash, + &blind_factor, + ); + let witness = HomoElGamalWitness { + r: self.l_i.clone(), + x: self.s_i.clone(), + }; + let delta = HomoElGamalStatement { + G: A_i.clone(), + H: self.R.clone(), + Y: g.to_point(), + D: V_i.clone(), + E: B_i.clone(), + }; + let dlog_proof_rho = DLogProof::prove(&self.rho_i); + let proof = HomoELGamalProof::prove(&witness, &delta); + + ( + Phase5Com1 { com }, + Phase5ADecom1 { + V_i, + A_i, + B_i, + blind_factor, + }, + proof, + dlog_proof_rho, + ) + } + + pub fn phase5c( + &self, + decom_vec: &[Phase5ADecom1], + com_vec: &[Phase5Com1], + elgamal_proofs: &[HomoELGamalProof], + dlog_proofs_rho: &[DLogProof], + v_i: &Point, + R: &Point, + ) -> Result<(Phase5Com2, Phase5DDecom2), Error> { + assert_eq!(decom_vec.len(), com_vec.len()); + + let g = Point::generator(); + let test_com_elgamal = (0..com_vec.len()).all(|i| { + let delta = HomoElGamalStatement { + G: decom_vec[i].A_i.clone(), + H: R.clone(), + Y: g.to_point(), + D: decom_vec[i].V_i.clone(), + E: decom_vec[i].B_i.clone(), + }; + + let input_hash = Sha256::new() + .chain_points([&decom_vec[i].V_i, &decom_vec[i].A_i, &decom_vec[i].B_i]) + .result_bigint(); + + HashCommitment::::create_commitment_with_user_defined_randomness( + &input_hash, + &decom_vec[i].blind_factor, + ) == com_vec[i].com + && elgamal_proofs[i].verify(&delta).is_ok() + && DLogProof::verify(&dlog_proofs_rho[i]).is_ok() + }); + + let v_iter = (0..com_vec.len()).map(|i| &decom_vec[i].V_i); + let a_iter = (0..com_vec.len()).map(|i| &decom_vec[i].A_i); + + let v = v_i + v_iter.sum::>(); + // V = -mG -ry - sum (vi) + let a: Point = a_iter.sum(); + + let r = Scalar::::from( + &self + .R + .x_coord() + .ok_or(Error::InvalidSig)? + .mod_floor(Scalar::::group_order()), + ); + let yr = &self.y * r; + let g = Point::generator(); + let m_fe = Scalar::::from(&self.m); + let gm = g * m_fe; + let v = v - &gm - &yr; + let u_i = v * &self.rho_i; + let t_i = a * &self.l_i; + let input_hash = Sha256::new().chain_points([&u_i, &t_i]).result_bigint(); + let blind_factor = BigInt::sample(SECURITY); + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &input_hash, + &blind_factor, + ); + + if test_com_elgamal { + Ok({ + ( + Phase5Com2 { com }, + Phase5DDecom2 { + u_i, + t_i, + blind_factor, + }, + ) + }) + } else { + Err(InvalidCom) + } + } + + pub fn phase5d( + &self, + decom_vec2: &[Phase5DDecom2], + com_vec2: &[Phase5Com2], + decom_vec1: &[Phase5ADecom1], + ) -> Result, Error> { + assert_eq!(decom_vec2.len(), decom_vec1.len()); + assert_eq!(decom_vec2.len(), com_vec2.len()); + + let test_com = (0..com_vec2.len()).all(|i| { + let input_hash = Sha256::new() + .chain_points([&decom_vec2[i].u_i, &decom_vec2[i].t_i]) + .result_bigint(); + HashCommitment::::create_commitment_with_user_defined_randomness( + &input_hash, + &decom_vec2[i].blind_factor, + ) == com_vec2[i].com + }); + + let t_iter = decom_vec2.iter().map(|decom| &decom.t_i); + let u_iter = decom_vec2.iter().map(|decom| &decom.u_i); + let b_iter = decom_vec1.iter().map(|decom| &decom.B_i); + + let g = Point::generator(); + let biased_sum_tb = g + t_iter.chain(b_iter).sum::>(); + let biased_sum_tb_minus_u = biased_sum_tb - u_iter.sum::>(); + if test_com { + if *g.as_point() == biased_sum_tb_minus_u { + Ok(self.s_i.clone()) + } else { + Err(InvalidKey) + } + } else { + Err(InvalidCom) + } + } + pub fn output_signature(&self, s_vec: &[Scalar]) -> Result { + let mut s = &self.s_i + s_vec.iter().sum::>(); + let s_bn = s.to_bigint(); + + let r = Scalar::::from( + &self + .R + .x_coord() + .ok_or(Error::InvalidSig)? + .mod_floor(Scalar::::group_order()), + ); + let ry: BigInt = self + .R + .y_coord() + .ok_or(Error::InvalidSig)? + .mod_floor(Scalar::::group_order()); + + /* + Calculate recovery id - it is not possible to compute the public key out of the signature + itself. Recovery id is used to enable extracting the public key uniquely. + 1. id = R.y & 1 + 2. if (s > curve.q / 2) id = id ^ 1 + */ + let is_ry_odd = ry.test_bit(0); + let mut recid = if is_ry_odd { 1 } else { 0 }; + let s_tag_bn = Scalar::::group_order() - &s_bn; + if s_bn > s_tag_bn { + s = Scalar::::from(&s_tag_bn); + recid ^= 1; + } + let sig = SignatureRecid { r, s, recid }; + let ver = verify(&sig, &self.y, &self.m).is_ok(); + if ver { + Ok(sig) + } else { + Err(InvalidSig) + } + } +} + +pub fn verify(sig: &SignatureRecid, y: &Point, message: &BigInt) -> Result<(), Error> { + let b = sig.s.invert().ok_or(Error::InvalidSig)?; + let a = Scalar::::from(message); + let u1 = a * &b; + let u2 = &sig.r * &b; + + let g = Point::generator(); + let gu1 = g * u1; + let yu2 = y * &u2; + // can be faster using shamir trick + + if sig.r + == Scalar::::from( + &(gu1 + yu2) + .x_coord() + .ok_or(Error::InvalidSig)? + .mod_floor(Scalar::::group_order()), + ) + { + Ok(()) + } else { + Err(InvalidSig) + } +} diff --git a/src/protocols/multi_party_ecdsa/gg_2018/test.rs b/src/protocols/multi_party_ecdsa/gg_2018/test.rs new file mode 100644 index 0000000..a3451a4 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2018/test.rs @@ -0,0 +1,459 @@ +#![allow(non_snake_case)] + +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +use crate::protocols::multi_party_ecdsa::gg_2018::party_i::{ + verify, KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, LocalSignature, Parameters, + PartyPrivate, Phase5ADecom1, Phase5Com1, SharedKeys, SignKeys, +}; +use crate::utilities::mta::{MessageA, MessageB}; + +use curv::arithmetic::traits::Converter; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use paillier::*; +use sha2::Sha256; + +#[test] +fn test_keygen_t1_n2() { + keygen_t_n_parties(1, 2); +} + +#[test] +fn test_keygen_t2_n3() { + keygen_t_n_parties(2, 3); +} + +#[test] +fn test_keygen_t2_n4() { + keygen_t_n_parties(2, 4); +} + +#[test] +fn test_sign_n5_t2_ttag4() { + sign(2, 5, 4, vec![0, 2, 3, 4]) +} +#[test] +fn test_sign_n8_t4_ttag6() { + sign(4, 8, 6, vec![0, 1, 2, 4, 6, 7]) +} + +fn keygen_t_n_parties( + t: u16, + n: u16, +) -> ( + Vec, + Vec, + Vec>, + Point, + VerifiableSS, +) { + let parames = Parameters { + threshold: t, + share_count: n, + }; + let party_keys_vec = (0..n).map(Keys::create).collect::>(); + + let (bc1_vec, decom_vec): (Vec<_>, Vec<_>) = party_keys_vec + .iter() + .map(|k| k.phase1_broadcast_phase3_proof_of_correct_key()) + .unzip(); + + let y_vec = (0..usize::from(n)) + .map(|i| decom_vec[i].y_i.clone()) + .collect::>>(); + let mut y_vec_iter = y_vec.iter(); + let head = y_vec_iter.next().unwrap(); + let tail = y_vec_iter; + let y_sum = tail.fold(head.clone(), |acc, x| acc + x); + + let mut vss_scheme_vec = Vec::new(); + let mut secret_shares_vec = Vec::new(); + let mut index_vec = Vec::new(); + + let vss_result: Vec<_> = party_keys_vec + .iter() + .map(|k| { + k.phase1_verify_com_phase3_verify_correct_key_phase2_distribute( + ¶mes, &decom_vec, &bc1_vec, + ) + .expect("invalid key") + }) + .collect(); + + for (vss_scheme, secret_shares, index) in vss_result { + vss_scheme_vec.push(vss_scheme); + secret_shares_vec.push(secret_shares); // cannot unzip + index_vec.push(index as u16); + } + + let vss_scheme_for_test = vss_scheme_vec.clone(); + + let party_shares = (0..usize::from(n)) + .map(|i| { + (0..usize::from(n)) + .map(|j| secret_shares_vec[j][i].clone()) + .collect::>>() + }) + .collect::>>>(); + + let mut shared_keys_vec = Vec::new(); + let mut dlog_proof_vec = Vec::new(); + for (i, key) in party_keys_vec.iter().enumerate() { + let (shared_keys, dlog_proof) = key + .phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶mes, + &y_vec, + &party_shares[i], + &vss_scheme_vec, + (&index_vec[i] + 1).into(), + ) + .expect("invalid vss"); + shared_keys_vec.push(shared_keys); + dlog_proof_vec.push(dlog_proof); + } + + let pk_vec = dlog_proof_vec + .iter() + .map(|dlog_proof| dlog_proof.pk.clone()) + .collect::>>(); + + //both parties run: + Keys::verify_dlog_proofs(¶mes, &dlog_proof_vec, &y_vec).expect("bad dlog proof"); + + //test + let xi_vec = shared_keys_vec + .iter() + .take(usize::from(t + 1)) + .map(|shared_keys| shared_keys.x_i.clone()) + .collect::>>(); + let x = vss_scheme_for_test[0] + .clone() + .reconstruct(&index_vec[0..=usize::from(t)], &xi_vec); + let sum_u_i = party_keys_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + &x.u_i); + assert_eq!(x, sum_u_i); + + ( + party_keys_vec, + shared_keys_vec, + pk_vec, + y_sum, + vss_scheme_for_test[0].clone(), + ) +} + +fn sign(t: u16, n: u16, ttag: u16, s: Vec) { + // full key gen emulation + let (party_keys_vec, shared_keys_vec, _pk_vec, y, vss_scheme) = keygen_t_n_parties(t, n); + + let private_vec = (0..shared_keys_vec.len()) + .map(|i| PartyPrivate::set_private(party_keys_vec[i].clone(), shared_keys_vec[i].clone())) + .collect::>(); + // make sure that we have t t); + let ttag = ttag as usize; + assert_eq!(s.len(), ttag); + + // each party creates a signing key. This happens in parallel IRL. In this test we + // create a vector of signing keys, one for each party. + // throughout i will index parties + let sign_keys_vec = (0..ttag) + .map(|i| SignKeys::create(&private_vec[usize::from(s[i])], &vss_scheme, s[i], &s)) + .collect::>(); + + // each party computes [Ci,Di] = com(g^gamma_i) and broadcast the commitments + let (bc1_vec, decommit_vec1): (Vec<_>, Vec<_>) = + sign_keys_vec.iter().map(|k| k.phase1_broadcast()).unzip(); + + // each party i sends encryption of k_i under her Paillier key + // m_a_vec = [ma_0;ma_1;,...] + // range proofs are ignored here, as there's no h1, h2, N_tilde setup in this version of GG18 + let m_a_vec: Vec<_> = sign_keys_vec + .iter() + .enumerate() + .map(|(i, k)| MessageA::a(&k.k_i, &party_keys_vec[usize::from(s[i])].ek, &[]).0) + .collect(); + + // each party i sends responses to m_a_vec she received (one response with input gamma_i and one with w_i) + // m_b_gamma_vec_all is a matrix where column i is a vector of message_b's that party i answers to all ma_{j!=i} using paillier key of party j to answer to ma_j + + // aggregation of the n messages of all parties + let mut m_b_gamma_vec_all = Vec::new(); + let mut beta_vec_all = Vec::new(); + let mut m_b_w_vec_all = Vec::new(); + let mut ni_vec_all = Vec::new(); + + for (i, key) in sign_keys_vec.iter().enumerate() { + let mut m_b_gamma_vec = Vec::new(); + let mut beta_vec = Vec::new(); + let mut m_b_w_vec = Vec::new(); + let mut ni_vec = Vec::new(); + + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + + let (m_b_gamma, beta_gamma, _, _) = MessageB::b( + &key.gamma_i, + &party_keys_vec[usize::from(s[ind])].ek, + m_a_vec[ind].clone(), + &[], + ) + .unwrap(); + let (m_b_w, beta_wi, _, _) = MessageB::b( + &key.w_i, + &party_keys_vec[usize::from(s[ind])].ek, + m_a_vec[ind].clone(), + &[], + ) + .unwrap(); + + m_b_gamma_vec.push(m_b_gamma); + beta_vec.push(beta_gamma); + m_b_w_vec.push(m_b_w); + ni_vec.push(beta_wi); + } + m_b_gamma_vec_all.push(m_b_gamma_vec.clone()); + beta_vec_all.push(beta_vec.clone()); + m_b_w_vec_all.push(m_b_w_vec.clone()); + ni_vec_all.push(ni_vec.clone()); + } + + // Here we complete the MwA protocols by taking the mb matrices and starting with the first column generating the appropriate message + // for example for index i=0 j=0 we need party at index s[1] to answer to mb that party s[0] sent, completing a protocol between s[0] and s[1]. + // for index i=1 j=0 we need party at index s[0] to answer to mb that party s[1]. etc. + // IRL each party i should get only the mb messages that other parties sent in response to the party i ma's. + // TODO: simulate as IRL + let mut alpha_vec_all = Vec::new(); + let mut miu_vec_all = Vec::new(); + + for i in 0..ttag { + let mut alpha_vec = Vec::new(); + let mut miu_vec = Vec::new(); + + let m_b_gamma_vec_i = &m_b_gamma_vec_all[i]; + let m_b_w_vec_i = &m_b_w_vec_all[i]; + + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let m_b = m_b_gamma_vec_i[j].clone(); + + let alpha_ij_gamma = m_b + .verify_proofs_get_alpha( + &party_keys_vec[usize::from(s[ind])].dk, + &sign_keys_vec[ind].k_i, + ) + .expect("wrong dlog or m_b"); + let m_b = m_b_w_vec_i[j].clone(); + let alpha_ij_wi = m_b + .verify_proofs_get_alpha( + &party_keys_vec[usize::from(s[ind])].dk, + &sign_keys_vec[ind].k_i, + ) + .expect("wrong dlog or m_b"); + + // since we actually run two MtAwc each party needs to make sure that the values B are the same as the public values + // here for b=w_i the parties already know W_i = g^w_i for each party so this check is done here. for b = gamma_i the check will be later when g^gamma_i will become public + // currently we take the W_i from the other parties signing keys + // TODO: use pk_vec (first change from x_i to w_i) for this check. + assert_eq!(m_b.b_proof.pk, sign_keys_vec[i].g_w_i); + + alpha_vec.push(alpha_ij_gamma); + miu_vec.push(alpha_ij_wi); + } + alpha_vec_all.push(alpha_vec.clone()); + miu_vec_all.push(miu_vec.clone()); + } + + let mut delta_vec = Vec::new(); + let mut sigma_vec = Vec::new(); + + for i in 0..ttag { + let alpha_vec: Vec> = (0..alpha_vec_all[i].len()) + .map(|j| alpha_vec_all[i][j].0.clone()) + .collect(); + let miu_vec: Vec> = (0..miu_vec_all[i].len()) + .map(|j| miu_vec_all[i][j].0.clone()) + .collect(); + + let delta = sign_keys_vec[i].phase2_delta_i(&alpha_vec[..], &beta_vec_all[i]); + let sigma = sign_keys_vec[i].phase2_sigma_i(&miu_vec[..], &ni_vec_all[i]); + delta_vec.push(delta); + sigma_vec.push(sigma); + } + + // all parties broadcast delta_i and compute delta_i ^(-1) + let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); + + // de-commit to g^gamma_i from phase1, test comm correctness, and that it is the same value used in MtA. + // Return R + + let _g_gamma_i_vec = (0..ttag) + .map(|i| sign_keys_vec[i].g_gamma_i.clone()) + .collect::>>(); + + let R_vec = (0..ttag) + .map(|_| { + // each party i tests all B = g^b = g ^ gamma_i she received. + let b_proof_vec = (0..ttag) + .map(|j| { + let b_gamma_vec = &m_b_gamma_vec_all[j]; + &b_gamma_vec[0].b_proof + }) + .collect::>>(); + SignKeys::phase4(&delta_inv, &b_proof_vec, decommit_vec1.clone(), &bc1_vec) + .expect("bad gamma_i decommit") + }) + .collect::>>(); + + let message: [u8; 4] = [79, 77, 69, 82]; + let message_bn = Sha256::new() + .chain_bigint(&BigInt::from_bytes(&message[..])) + .result_bigint(); + let mut local_sig_vec = Vec::new(); + + // each party computes s_i but don't send it yet. we start with phase5 + for i in 0..ttag { + let local_sig = LocalSignature::phase5_local_sig( + &sign_keys_vec[i].k_i, + &message_bn, + &R_vec[i], + &sigma_vec[i], + &y, + ); + local_sig_vec.push(local_sig); + } + + let mut phase5_com_vec: Vec = Vec::new(); + let mut phase_5a_decom_vec: Vec = Vec::new(); + let mut helgamal_proof_vec = Vec::new(); + let mut dlog_proof_rho_vec = Vec::new(); + // we notice that the proof for V= R^sg^l, B = A^l is a general form of homomorphic elgamal. + for sig in &local_sig_vec { + let (phase5_com, phase_5a_decom, helgamal_proof, dlog_proof_rho) = + sig.phase5a_broadcast_5b_zkproof(); + phase5_com_vec.push(phase5_com); + phase_5a_decom_vec.push(phase_5a_decom); + helgamal_proof_vec.push(helgamal_proof); + dlog_proof_rho_vec.push(dlog_proof_rho); + } + + let mut phase5_com2_vec = Vec::new(); + let mut phase_5d_decom2_vec = Vec::new(); + for i in 0..ttag { + let mut phase_5a_decom_vec_clone = phase_5a_decom_vec.clone(); + let mut phase_5a_com_vec_clone = phase5_com_vec.clone(); + let mut phase_5b_elgamal_vec_clone = helgamal_proof_vec.clone(); + + let _decom_i = phase_5a_decom_vec_clone.remove(i); + let _com_i = phase_5a_com_vec_clone.remove(i); + let _elgamal_i = phase_5b_elgamal_vec_clone.remove(i); + // for j in 0..s_minus_i.len() { + let (phase5_com2, phase_5d_decom2) = local_sig_vec[i] + .phase5c( + &phase_5a_decom_vec_clone, + &phase_5a_com_vec_clone, + &phase_5b_elgamal_vec_clone, + &dlog_proof_rho_vec, + &phase_5a_decom_vec[i].V_i, + &R_vec[0], + ) + .expect("error phase5"); + phase5_com2_vec.push(phase5_com2); + phase_5d_decom2_vec.push(phase_5d_decom2); + // } + } + + // assuming phase5 checks passes each party sends s_i and compute sum_i{s_i} + let mut s_vec: Vec> = Vec::new(); + for sig in &local_sig_vec { + let s_i = sig + .phase5d(&phase_5d_decom2_vec, &phase5_com2_vec, &phase_5a_decom_vec) + .expect("bad com 5d"); + s_vec.push(s_i); + } + + // here we compute the signature only of party i=0 to demonstrate correctness. + s_vec.remove(0); + let sig = local_sig_vec[0] + .output_signature(&s_vec) + .expect("verification failed"); + + assert_eq!(local_sig_vec[0].y, y); + verify(&sig, &local_sig_vec[0].y, &local_sig_vec[0].m).unwrap(); + check_sig(&sig.r, &sig.s, &local_sig_vec[0].m, &y); +} + +fn check_sig(r: &Scalar, s: &Scalar, msg: &BigInt, pk: &Point) { + use secp256k1::{Message, PublicKey, Signature, SECP256K1}; + + let raw_msg = BigInt::to_bytes(msg); + let mut msg: Vec = Vec::new(); // padding + msg.extend(vec![0u8; 32 - raw_msg.len()]); + msg.extend(raw_msg.iter()); + + let msg = Message::from_slice(msg.as_slice()).unwrap(); + let slice = pk.to_bytes(false); + let mut raw_pk = Vec::new(); + if slice.len() != 65 { + // after curv's pk_to_key_slice return 65 bytes, this can be removed + raw_pk.insert(0, 4u8); + raw_pk.extend(vec![0u8; 64 - slice.len()]); + raw_pk.extend(slice.as_ref()); + } else { + raw_pk.extend(slice.as_ref()); + } + + assert_eq!(raw_pk.len(), 65); + + let pk = PublicKey::from_slice(&raw_pk).unwrap(); + + let mut compact: Vec = Vec::new(); + let bytes_r = &r.to_bytes()[..]; + compact.extend(vec![0u8; 32 - bytes_r.len()]); + compact.extend(bytes_r.iter()); + + let bytes_s = &s.to_bytes()[..]; + compact.extend(vec![0u8; 32 - bytes_s.len()]); + compact.extend(bytes_s.iter()); + + let secp_sig = Signature::from_compact(compact.as_slice()).unwrap(); + + let is_correct = SECP256K1.verify(&msg, &secp_sig, &pk).is_ok(); + assert!(is_correct); +} + +#[test] +fn test_serialize_deserialize() { + use serde_json; + + let k = Keys::create(0); + let (commit, decommit) = k.phase1_broadcast_phase3_proof_of_correct_key(); + + let encoded = serde_json::to_string(&commit).unwrap(); + let decoded: KeyGenBroadcastMessage1 = serde_json::from_str(&encoded).unwrap(); + assert_eq!(commit.com, decoded.com); + + let encoded = serde_json::to_string(&decommit).unwrap(); + let decoded: KeyGenDecommitMessage1 = serde_json::from_str(&encoded).unwrap(); + assert_eq!(decommit.y_i, decoded.y_i); +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/blame.rs b/src/protocols/multi_party_ecdsa/gg_2020/blame.rs new file mode 100644 index 0000000..929a43d --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/blame.rs @@ -0,0 +1,455 @@ +#![allow(non_snake_case)] +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ +use crate::protocols::multi_party_ecdsa::gg_2020::ErrorType; +use crate::utilities::mta::{MessageA, MessageB}; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::ECDDHProof; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::ECDDHStatement; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::ECDDHWitness; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::traits::EncryptWithChosenRandomness; +use paillier::traits::Open; +use paillier::DecryptionKey; +use paillier::Paillier; +use paillier::{EncryptionKey, Randomness, RawCiphertext, RawPlaintext}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalStatePhase5 { + pub k: Scalar, + pub k_randomness: BigInt, + pub gamma: Scalar, + pub beta_randomness: Vec, + pub beta_tag: Vec, + pub encryption_key: EncryptionKey, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GlobalStatePhase5 { + pub k_vec: Vec>, + pub k_randomness_vec: Vec, + pub gamma_vec: Vec>, + pub beta_randomness_vec: Vec>, + pub beta_tag_vec: Vec>, + pub encryption_key_vec: Vec, + // stuff to check against + pub delta_vec: Vec>, + pub g_gamma_vec: Vec>, + pub m_a_vec: Vec, + pub m_b_mat: Vec>, +} + +// TODO: check all parties submitted inputs +// TODO: if not - abort gracefully with list of parties that did not produce inputs +impl GlobalStatePhase5 { + pub fn local_state_to_global_state( + encryption_key_vec: &[EncryptionKey], + delta_vec: &[Scalar], //to test against delta_vec + g_gamma_vec: &[Point], // to test against the opened commitment for g_gamma + m_a_vec: &[MessageA], // to test against broadcast message A + m_b_mat: Vec>, // to test against broadcast message B + local_state_vec: &[LocalStatePhase5], + ) -> Self { + let len = local_state_vec.len(); + let k_vec = (0..len) + .map(|i| local_state_vec[i].k.clone()) + .collect::>>(); + let k_randomness_vec = (0..len) + .map(|i| local_state_vec[i].k_randomness.clone()) + .collect::>(); + let gamma_vec = (0..len) + .map(|i| local_state_vec[i].gamma.clone()) + .collect::>>(); + let beta_randomness_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + local_state_vec[ind1].beta_randomness[ind2].clone() + }) + .collect::>() + }) + .collect::>>(); + let beta_tag_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + local_state_vec[ind1].beta_tag[ind2].clone() + }) + .collect::>() + }) + .collect::>>(); + + // let encryption_key_vec = (0..len).map(|i| local_state_vec[i].encryption_key.clone() ).collect::>(); + GlobalStatePhase5 { + k_vec, + k_randomness_vec, + gamma_vec, + beta_randomness_vec, + beta_tag_vec, + encryption_key_vec: encryption_key_vec.to_vec(), + delta_vec: delta_vec.to_vec(), + g_gamma_vec: g_gamma_vec.to_vec(), + m_a_vec: m_a_vec.to_vec(), + m_b_mat, + } + } + + pub fn phase5_blame(&self) -> Result<(), ErrorType> { + let len = self.delta_vec.len(); + let mut bad_signers_vec = Vec::new(); + + // check commitment to g_gamma + for i in 0..len { + if self.g_gamma_vec[i] != Point::generator() * &self.gamma_vec[i] { + bad_signers_vec.push(i) + } + } + + let alpha_beta_matrix = (0..len) + .map(|i| { + let message_a = MessageA::a_with_predefined_randomness( + &self.k_vec[i], + &self.encryption_key_vec[i], + &self.k_randomness_vec[i], + &[], + ); + + // check message a + if message_a.c != self.m_a_vec[i].c { + bad_signers_vec.push(i) + } + + if bad_signers_vec.is_empty() { + (0..len - 1) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let (message_b, beta) = MessageB::b_with_predefined_randomness( + &self.gamma_vec[ind], + &self.encryption_key_vec[i], + message_a.clone(), + &self.beta_randomness_vec[i][j], + &self.beta_tag_vec[i][j], + &[], + ) + .unwrap(); + // check message_b + if message_b.c != self.m_b_mat[i][j].c { + bad_signers_vec.push(ind) + } + + let k_i_gamma_j = &self.k_vec[i] * &self.gamma_vec[ind]; + let alpha = k_i_gamma_j - β + + (alpha, beta) + }) + .collect::, Scalar)>>() + } else { + vec![] + } + }) + .collect::, Scalar)>>>(); + + // The matrix we got: + // [P2, P1, P1, P1 ...] + // [P3, P3, P2, P2, ...] + // [P4, P4, P4, P3, ...] + // [..., ...] + // [Pn, Pn, Pn, Pn, ...] + // We have n columns, one for each party for all the times the party played alice. + // The Pi's indicate the counter party that played bob + + // we only proceed to check the blame if everyone opened values that are + // consistent with publicly known commitments and ciphertexts + if bad_signers_vec.is_empty() { + //reconstruct delta's + let delta_vec_reconstruct = (0..len) + .map(|i| { + let k_i_gamma_i = &self.k_vec[i] * &self.gamma_vec[i]; + + let alpha_sum = alpha_beta_matrix[i] + .iter() + .fold(Scalar::::zero(), |acc, x| acc + &x.0); + let beta_vec = (0..len - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + alpha_beta_matrix[ind1][ind2].1.clone() + }) + .collect::>>(); + + let beta_sum = beta_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + + k_i_gamma_i + alpha_sum + beta_sum + }) + .collect::>>(); + + // compare delta vec to reconstructed delta vec + + #[allow(clippy::needless_range_loop)] + for i in 0..len { + if self.delta_vec[i] != delta_vec_reconstruct[i] { + bad_signers_vec.push(i) + } + } + } + + bad_signers_vec.sort_unstable(); + bad_signers_vec.dedup(); + let err_type = ErrorType { + error_type: "phase6_blame".to_string(), + bad_actors: bad_signers_vec, + }; + Err(err_type) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalStatePhase6 { + pub k: Scalar, + pub k_randomness: BigInt, + pub miu: Vec, // we need the value before reduction + pub miu_randomness: Vec, + pub proof_of_eq_dlog: ECDDHProof, +} + +// It is assumed the second message of MtAwc (ciphertext from b to a) is broadcasted in the original protocol +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GlobalStatePhase6 { + pub k_vec: Vec>, + pub k_randomness_vec: Vec, + pub miu_vec: Vec>, + pub miu_randomness_vec: Vec>, + pub g_w_vec: Vec>, + pub encryption_key_vec: Vec, + pub proof_vec: Vec>, + pub S_vec: Vec>, + pub m_a_vec: Vec, + pub m_b_mat: Vec>, +} + +impl GlobalStatePhase6 { + pub fn extract_paillier_randomness(ciphertext: &BigInt, dk: &DecryptionKey) -> BigInt { + let raw_c = RawCiphertext::from(ciphertext.clone()); + let (_plaintext, randomness) = Paillier::open(dk, raw_c); + randomness.0 + } + + pub fn ecddh_proof( + sigma_i: &Scalar, + R: &Point, + S: &Point, + ) -> ECDDHProof { + let delta = ECDDHStatement { + g1: Point::generator().to_point(), + g2: R.clone(), + h1: Point::generator() * sigma_i, + h2: S.clone(), + }; + let w = ECDDHWitness { x: sigma_i.clone() }; + ECDDHProof::prove(&w, &delta) + } + + // TODO: check all parties submitted inputs + // TODO: if not - abort gracefully with list of parties that did not produce inputs + pub fn local_state_to_global_state( + encryption_key_vec: &[EncryptionKey], + S_vec: &[Point], + g_w_vec: &[Point], + m_a_vec: &[MessageA], // to test against broadcast message A + m_b_mat: Vec>, // to test against broadcast message B + local_state_vec: &[LocalStatePhase6], + ) -> Self { + let len = local_state_vec.len(); + let k_vec = (0..len) + .map(|i| local_state_vec[i].k.clone()) + .collect::>>(); + let k_randomness_vec = (0..len) + .map(|i| local_state_vec[i].k_randomness.clone()) + .collect::>(); + let proof_vec = (0..len) + .map(|i| local_state_vec[i].proof_of_eq_dlog.clone()) + .collect::>>(); + let miu_randomness_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| local_state_vec[i].miu_randomness[j].clone()) + .collect::>() + }) + .collect::>>(); + let miu_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| local_state_vec[i].miu[j].clone()) + .collect::>() + }) + .collect::>>(); + + GlobalStatePhase6 { + k_vec, + k_randomness_vec, + miu_vec, + miu_randomness_vec, + g_w_vec: g_w_vec.to_vec(), + encryption_key_vec: encryption_key_vec.to_vec(), + proof_vec, + S_vec: S_vec.to_vec(), + m_a_vec: m_a_vec.to_vec(), + m_b_mat, + } + } + + pub fn phase6_blame(&self, R: &Point) -> Result<(), ErrorType> { + let len = self.k_vec.len(); + let mut bad_signers_vec = Vec::new(); + + // check correctness of miu + for i in 0..len { + for j in 0..len - 1 { + if Paillier::encrypt_with_chosen_randomness( + &self.encryption_key_vec[i], + RawPlaintext::from(self.miu_vec[i][j].clone()), + &Randomness::from(self.miu_randomness_vec[i][j].clone()), + ) != RawCiphertext::from(self.m_b_mat[i][j].c.clone()) + { + bad_signers_vec.push(i) + } + } + } + + // check correctness of k + for i in 0..len { + if MessageA::a_with_predefined_randomness( + &self.k_vec[i], + &self.encryption_key_vec[i], + &self.k_randomness_vec[i], + &[], + ) + .c != self.m_a_vec[i].c + { + bad_signers_vec.push(i) + } + } + + // we only proceed to check the blame if everyone opened values that are + // consistent with publicly known ciphertexts sent during MtA + if bad_signers_vec.is_empty() { + // compute g_ni + let g_ni_mat = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let k_i = &self.k_vec[i]; + let g_w_j = &self.g_w_vec[ind]; + let g_w_j_ki = g_w_j * k_i; + let miu: Scalar = + Scalar::::from(&self.miu_vec[i][j]); + let g_miu = Point::generator() * &miu; + g_w_j_ki - &g_miu + }) + .collect::>>() + }) + .collect::>>>(); + + // compute g_sigma_i + + let mut g_sigma_i_vec = (0..len) + .map(|i| { + let g_wi_ki = &self.g_w_vec[i] * &self.k_vec[i]; + let sum = self.miu_vec[i].iter().fold(g_wi_ki, |acc, x| { + acc + (Point::generator() * &Scalar::::from(&*x)) + }); + sum + }) + .collect::>>(); + + #[allow(clippy::needless_range_loop)] + for i in 0..len { + for j in 0..len - 1 { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + g_sigma_i_vec[i] = &g_sigma_i_vec[i] + &g_ni_mat[ind1][ind2]; + } + } + + // check zero knowledge proof + #[allow(clippy::needless_range_loop)] + for i in 0..len { + let statement = ECDDHStatement { + g1: Point::generator().to_point(), + g2: R.clone(), + h1: g_sigma_i_vec[i].clone(), + h2: self.S_vec[i].clone(), + }; + + let result = self.proof_vec[i].verify(&statement); + if result.is_err() { + bad_signers_vec.push(i) + } + } + } + + bad_signers_vec.sort_unstable(); + bad_signers_vec.dedup(); + let err_type = ErrorType { + error_type: "phase6_blame".to_string(), + bad_actors: bad_signers_vec, + }; + Err(err_type) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GlobalStatePhase7 { + pub s_vec: Vec>, + pub r: Scalar, + pub R_dash_vec: Vec>, + pub m: BigInt, + pub R: Point, + pub S_vec: Vec>, +} + +impl GlobalStatePhase7 { + pub fn phase7_blame(&self) -> Result<(), ErrorType> { + let len = self.s_vec.len(); //TODO: check bounds + let mut bad_signers_vec = Vec::new(); + + for i in 0..len { + let R_si = &self.R * &self.s_vec[i]; + let R_dash_m = &self.R_dash_vec[i] * &Scalar::::from(&self.m); + let Si_r = &self.S_vec[i] * &self.r; + let right = R_dash_m + Si_r; + let left = R_si; + if left != right { + bad_signers_vec.push(i); + } + } + + let err_type = ErrorType { + error_type: "phase7_blame".to_string(), + bad_actors: bad_signers_vec, + }; + Err(err_type) + } +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/mod.rs b/src/protocols/multi_party_ecdsa/gg_2020/mod.rs new file mode 100644 index 0000000..7975483 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/mod.rs @@ -0,0 +1,28 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +pub mod blame; +pub mod party_i; +pub mod state_machine; +#[cfg(test)] +mod test; + +#[allow(dead_code)] +#[derive(Clone, Debug)] +pub struct ErrorType { + error_type: String, + bad_actors: Vec, +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/party_i.rs b/src/protocols/multi_party_ecdsa/gg_2020/party_i.rs new file mode 100644 index 0000000..6fd6d44 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/party_i.rs @@ -0,0 +1,936 @@ +#![allow(non_snake_case)] + +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +use std::fmt::Debug; + +use centipede::juggling::proof_system::{Helgamalsegmented, Witness}; +use centipede::juggling::segmentation::Msegmentation; +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::cryptographic_primitives::proofs::sigma_correct_homomorphic_elgamal_enc::*; +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Curve, Point, Scalar}; +use curv::BigInt; +use sha2::Sha256; + +use crate::Error::{self, InvalidSig, Phase5BadSum, Phase6Error}; +use paillier::{ + Decrypt, DecryptionKey, EncryptionKey, KeyGeneration, Paillier, RawCiphertext, RawPlaintext, +}; + +use serde::{Deserialize, Serialize}; +use zk_paillier::zkproofs::NiCorrectKeyProof; +use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + +use crate::protocols::multi_party_ecdsa::gg_2020::ErrorType; +use crate::utilities::zk_pdl_with_slack::{PDLwSlackProof, PDLwSlackStatement, PDLwSlackWitness}; +use curv::cryptographic_primitives::proofs::sigma_valid_pedersen::PedersenProof; + +use std::convert::TryInto; + +const SECURITY: usize = 256; +const PAILLIER_MIN_BIT_LENGTH: usize = 2047; +const PAILLIER_MAX_BIT_LENGTH: usize = 2048; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Parameters { + pub threshold: u16, //t + pub share_count: u16, //n +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Keys { + pub u_i: Scalar, + pub y_i: Point, + pub dk: DecryptionKey, + pub ek: EncryptionKey, + pub party_index: usize, + pub N_tilde: BigInt, + pub h1: BigInt, + pub h2: BigInt, + pub xhi: BigInt, + pub xhi_inv: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PartyPrivate { + u_i: Scalar, + x_i: Scalar, + dk: DecryptionKey, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenBroadcastMessage1 { + pub e: EncryptionKey, + pub dlog_statement: DLogStatement, + pub com: BigInt, + pub correct_key_proof: NiCorrectKeyProof, + pub composite_dlog_proof_base_h1: CompositeDLogProof, + pub composite_dlog_proof_base_h2: CompositeDLogProof, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenDecommitMessage1 { + pub blind_factor: BigInt, + pub y_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SharedKeys { + pub y: Point, + pub x_i: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignKeys { + pub w_i: Scalar, + pub g_w_i: Point, + pub k_i: Scalar, + pub gamma_i: Scalar, + pub g_gamma_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignBroadcastPhase1 { + pub com: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignDecommitPhase1 { + pub blind_factor: BigInt, + pub g_gamma_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalSignature { + pub r: Scalar, + pub R: Point, + pub s_i: Scalar, + pub m: BigInt, + pub y: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignatureRecid { + pub r: Scalar, + pub s: Scalar, + pub recid: u8, +} + +pub fn generate_h1_h2_N_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) { + // note, should be safe primes: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys();; + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (mut xhi, mut xhi_inv) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + xhi = BigInt::sub(&phi, &xhi); + xhi_inv = BigInt::sub(&phi, &xhi_inv); + + (ek_tilde.n, h1, h2, xhi, xhi_inv) +} + +impl Keys { + pub fn create(index: usize) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // we recommend using safe primes if the code is used in production + pub fn create_safe_prime(index: usize) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + pub fn create_from(u: Scalar, index: usize) -> Self { + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + pub fn phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2( + &self, + ) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { + let blind_factor = BigInt::sample(SECURITY); + let correct_key_proof = NiCorrectKeyProof::proof(&self.dk, None); + + let dlog_statement_base_h1 = DLogStatement { + N: self.N_tilde.clone(), + g: self.h1.clone(), + ni: self.h2.clone(), + }; + let dlog_statement_base_h2 = DLogStatement { + N: self.N_tilde.clone(), + g: self.h2.clone(), + ni: self.h1.clone(), + }; + + let composite_dlog_proof_base_h1 = + CompositeDLogProof::prove(&dlog_statement_base_h1, &self.xhi); + let composite_dlog_proof_base_h2 = + CompositeDLogProof::prove(&dlog_statement_base_h2, &self.xhi_inv); + + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(self.y_i.to_bytes(true).as_ref()), + &blind_factor, + ); + let bcm1 = KeyGenBroadcastMessage1 { + e: self.ek.clone(), + dlog_statement: dlog_statement_base_h1, + com, + correct_key_proof, + composite_dlog_proof_base_h1, + composite_dlog_proof_base_h2, + }; + let decom1 = KeyGenDecommitMessage1 { + blind_factor, + y_i: self.y_i.clone(), + }; + (bcm1, decom1) + } + + pub fn phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + &self, + params: &Parameters, + decom_vec: &[KeyGenDecommitMessage1], + bc1_vec: &[KeyGenBroadcastMessage1], + ) -> Result<(VerifiableSS, Vec>, usize), ErrorType> { + let mut bad_actors_vec = Vec::new(); + // test length: + assert_eq!(decom_vec.len(), usize::from(params.share_count)); + assert_eq!(bc1_vec.len(), usize::from(params.share_count)); + // test paillier correct key, h1,h2 correct generation and test decommitments + let correct_key_correct_decom_all = (0..bc1_vec.len()) + .map(|i| { + let dlog_statement_base_h2 = DLogStatement { + N: bc1_vec[i].dlog_statement.N.clone(), + g: bc1_vec[i].dlog_statement.ni.clone(), + ni: bc1_vec[i].dlog_statement.g.clone(), + }; + let test_res = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(&decom_vec[i].y_i.to_bytes(true)), + &decom_vec[i].blind_factor, + ) == bc1_vec[i].com + && bc1_vec[i] + .correct_key_proof + .verify(&bc1_vec[i].e, zk_paillier::zkproofs::SALT_STRING) + .is_ok() + && bc1_vec[i].e.n.bit_length() >= PAILLIER_MIN_BIT_LENGTH + && bc1_vec[i].e.n.bit_length() <= PAILLIER_MAX_BIT_LENGTH + && bc1_vec[i].dlog_statement.N.bit_length() >= PAILLIER_MIN_BIT_LENGTH + && bc1_vec[i].dlog_statement.N.bit_length() <= PAILLIER_MAX_BIT_LENGTH + && bc1_vec[i] + .composite_dlog_proof_base_h1 + .verify(&bc1_vec[i].dlog_statement) + .is_ok() + && bc1_vec[i] + .composite_dlog_proof_base_h2 + .verify(&dlog_statement_base_h2) + .is_ok(); + if !test_res { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "invalid key".to_string(), + bad_actors: bad_actors_vec, + }; + + let (vss_scheme, secret_shares) = + VerifiableSS::share(params.threshold, params.share_count, &self.u_i); + if correct_key_correct_decom_all { + Ok((vss_scheme, secret_shares.to_vec(), self.party_index)) + } else { + Err(err_type) + } + } + + pub fn phase2_verify_vss_construct_keypair_phase3_pok_dlog( + &self, + params: &Parameters, + y_vec: &[Point], + secret_shares_vec: &[Scalar], + vss_scheme_vec: &[VerifiableSS], + index: usize, + ) -> Result<(SharedKeys, DLogProof), ErrorType> { + let mut bad_actors_vec = Vec::new(); + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(secret_shares_vec.len(), usize::from(params.share_count)); + assert_eq!(vss_scheme_vec.len(), usize::from(params.share_count)); + + let correct_ss_verify = (0..y_vec.len()) + .map(|i| { + let res = vss_scheme_vec[i] + .validate_share(&secret_shares_vec[i], index.try_into().unwrap()) + .is_ok() + && vss_scheme_vec[i].commitments[0] == y_vec[i]; + if !res { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "invalid vss".to_string(), + bad_actors: bad_actors_vec, + }; + + if correct_ss_verify { + let (head, tail) = y_vec.split_at(1); + let y = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + + let x_i = secret_shares_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + let dlog_proof = DLogProof::prove(&x_i); + Ok((SharedKeys { y, x_i }, dlog_proof)) + } else { + Err(err_type) + } + } + + pub fn get_commitments_to_xi( + vss_scheme_vec: &[VerifiableSS], + ) -> Vec> { + let len = vss_scheme_vec.len(); + let (head, tail) = vss_scheme_vec.split_at(1); + let mut global_coefficients = head[0].commitments.clone(); + for vss in tail { + for (i, coefficient_commitment) in vss.commitments.iter().enumerate() { + global_coefficients[i] = &global_coefficients[i] + &*coefficient_commitment; + } + } + + let global_vss = VerifiableSS { + parameters: vss_scheme_vec[0].parameters.clone(), + commitments: global_coefficients, + }; + (1..=len) + .map(|i| global_vss.get_point_commitment(i.try_into().unwrap())) + .collect::>>() + } + + pub fn update_commitments_to_xi( + comm: &Point, + vss_scheme: &VerifiableSS, + index: usize, + s: &[usize], + ) -> Point { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + let li = VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + index.try_into().unwrap(), + s.as_slice(), + ); + comm * &li + } + + pub fn verify_dlog_proofs_check_against_vss( + params: &Parameters, + dlog_proofs_vec: &[DLogProof], + y_vec: &[Point], + vss_vec: &[VerifiableSS], + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(dlog_proofs_vec.len(), usize::from(params.share_count)); + let xi_commitments = Keys::get_commitments_to_xi(vss_vec); + let xi_dlog_verify = (0..y_vec.len()) + .map(|i| { + let ver_res = DLogProof::verify(&dlog_proofs_vec[i]).is_ok(); + let verify_against_vss = xi_commitments[i] == dlog_proofs_vec[i].pk; + if !ver_res || !verify_against_vss { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "bad dlog proof".to_string(), + bad_actors: bad_actors_vec, + }; + + if xi_dlog_verify { + Ok(()) + } else { + Err(err_type) + } + } +} + +impl PartyPrivate { + pub fn set_private(key: Keys, shared_key: SharedKeys) -> Self { + Self { + u_i: key.u_i, + x_i: shared_key.x_i, + dk: key.dk, + } + } + + pub fn y_i(&self) -> Point { + let g = Point::generator(); + g * &self.u_i + } + + pub fn decrypt(&self, ciphertext: BigInt) -> RawPlaintext { + Paillier::decrypt(&self.dk, &RawCiphertext::from(ciphertext)) + } + + pub fn refresh_private_key(&self, factor: &Scalar, index: usize) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // we recommend using safe primes if the code is used in production + pub fn refresh_private_key_safe_prime(&self, factor: &Scalar, index: usize) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // used for verifiable recovery + pub fn to_encrypted_segment( + &self, + segment_size: usize, + num_of_segments: usize, + pub_ke_y: &Point, + g: &Point, + ) -> (Witness, Helgamalsegmented) { + Msegmentation::to_encrypted_segments(&self.u_i, &segment_size, num_of_segments, pub_ke_y, g) + } + + pub fn update_private_key( + &self, + factor_u_i: &Scalar, + factor_x_i: &Scalar, + ) -> Self { + PartyPrivate { + u_i: &self.u_i + factor_u_i, + x_i: &self.x_i + factor_x_i, + dk: self.dk.clone(), + } + } +} + +impl SignKeys { + pub fn g_w_vec( + pk_vec: &[Point], + s: &[usize], + vss_scheme: &VerifiableSS, + ) -> Vec> { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + // TODO: check bounds + (0..s.len()) + .map(|i| { + let li = VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + s[i], + s.as_slice(), + ); + &pk_vec[s[i] as usize] * &li + }) + .collect::>>() + } + + pub fn create( + private_x_i: &Scalar, + vss_scheme: &VerifiableSS, + index: usize, + s: &[usize], + ) -> Self { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + let li = VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + index.try_into().unwrap(), + s.as_slice(), + ); + let w_i = li * private_x_i; + let g = Point::generator(); + let g_w_i = g * &w_i; + let gamma_i = Scalar::::random(); + let g_gamma_i = g * &gamma_i; + let k_i = Scalar::::random(); + Self { + w_i, + g_w_i, + k_i, + gamma_i, + g_gamma_i, + } + } + + pub fn phase1_broadcast(&self) -> (SignBroadcastPhase1, SignDecommitPhase1) { + let blind_factor = BigInt::sample(SECURITY); + let g = Point::generator(); + let g_gamma_i = g * &self.gamma_i; + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(g_gamma_i.to_bytes(true).as_ref()), + &blind_factor, + ); + + ( + SignBroadcastPhase1 { com }, + SignDecommitPhase1 { + blind_factor, + g_gamma_i: self.g_gamma_i.clone(), + }, + ) + } + + pub fn phase2_delta_i( + &self, + alpha_vec: &[Scalar], + beta_vec: &[Scalar], + ) -> Scalar { + let vec_len = alpha_vec.len(); + assert_eq!(alpha_vec.len(), beta_vec.len()); + // assert_eq!(alpha_vec.len(), self.s.len() - 1); + let ki_gamma_i = &self.k_i * &self.gamma_i; + + (0..vec_len) + .map(|i| &alpha_vec[i] + &beta_vec[i]) + .fold(ki_gamma_i, |acc, x| acc + x) + } + + pub fn phase2_sigma_i( + &self, + miu_vec: &[Scalar], + ni_vec: &[Scalar], + ) -> Scalar { + let vec_len = miu_vec.len(); + assert_eq!(miu_vec.len(), ni_vec.len()); + //assert_eq!(miu_vec.len(), self.s.len() - 1); + let ki_w_i = &self.k_i * &self.w_i; + (0..vec_len) + .map(|i| &miu_vec[i] + &ni_vec[i]) + .fold(ki_w_i, |acc, x| acc + x) + } + + pub fn phase3_compute_t_i( + sigma_i: &Scalar, + ) -> ( + Point, + Scalar, + PedersenProof, + ) { + let g_sigma_i = Point::generator() * sigma_i; + let l = Scalar::::random(); + let h_l = Point::::base_point2() * &l; + let T = g_sigma_i + h_l; + let T_zk_proof = PedersenProof::::prove(sigma_i, &l); + + (T, l, T_zk_proof) + } + pub fn phase3_reconstruct_delta(delta_vec: &[Scalar]) -> Scalar { + let sum = delta_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + sum.invert().unwrap() + } + + pub fn phase4( + delta_inv: &Scalar, + b_proof_vec: &[&DLogProof], + phase1_decommit_vec: Vec, + bc1_vec: &[SignBroadcastPhase1], + index: usize, + ) -> Result, ErrorType> { + let mut bad_actors_vec = Vec::new(); + let test_b_vec_and_com = (0..b_proof_vec.len()) + .map(|j| { + let ind = if j < index { j } else { j + 1 }; + let res = b_proof_vec[j].pk == phase1_decommit_vec[ind].g_gamma_i + && HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes( + phase1_decommit_vec[ind].g_gamma_i.to_bytes(true).as_ref(), + ), + &phase1_decommit_vec[ind].blind_factor, + ) == bc1_vec[ind].com; + if !res { + bad_actors_vec.push(ind); + false + } else { + true + } + }) + .all(|x| x); + + let mut g_gamma_i_iter = phase1_decommit_vec.iter(); + let head = g_gamma_i_iter.next().unwrap(); + let tail = g_gamma_i_iter; + + let err_type = ErrorType { + error_type: "bad gamma_i decommit".to_string(), + bad_actors: bad_actors_vec, + }; + + if test_b_vec_and_com { + Ok({ + let gamma_sum = tail.fold(head.g_gamma_i.clone(), |acc, x| acc + &x.g_gamma_i); + // R + gamma_sum * delta_inv + }) + } else { + Err(err_type) + } + } +} + +impl LocalSignature { + pub fn phase5_proof_pdl( + R_dash: &Point, + R: &Point, + k_ciphertext: &BigInt, + ek: &EncryptionKey, + k_i: &Scalar, + k_enc_randomness: &BigInt, + dlog_statement: &DLogStatement, + ) -> PDLwSlackProof { + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: k_ciphertext.clone(), + ek: ek.clone(), + Q: R_dash.clone(), + G: R.clone(), + h1: dlog_statement.g.clone(), + h2: dlog_statement.ni.clone(), + N_tilde: dlog_statement.N.clone(), + }; + + let pdl_w_slack_witness = PDLwSlackWitness { + x: k_i.clone(), + r: k_enc_randomness.clone(), + }; + + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement) + } + + pub fn phase5_verify_pdl( + pdl_w_slack_proof_vec: &[PDLwSlackProof], + R_dash: &Point, + R: &Point, + k_ciphertext: &BigInt, + ek: &EncryptionKey, + dlog_statement: &[DLogStatement], + s: &[usize], + i: usize, + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + + let num_of_other_participants = s.len() - 1; + if pdl_w_slack_proof_vec.len() != num_of_other_participants { + bad_actors_vec.push(i); + } else { + let proofs_verification = (0..pdl_w_slack_proof_vec.len()) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: k_ciphertext.clone(), + ek: ek.clone(), + Q: R_dash.clone(), + G: R.clone(), + h1: dlog_statement[s[ind]].g.clone(), + h2: dlog_statement[s[ind]].ni.clone(), + N_tilde: dlog_statement[s[ind]].N.clone(), + }; + let ver_res = pdl_w_slack_proof_vec[j].verify(&pdl_w_slack_statement); + if ver_res.is_err() { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + if proofs_verification { + return Ok(()); + } + } + + let err_type = ErrorType { + error_type: "Bad PDLwSlack proof".to_string(), + bad_actors: bad_actors_vec, + }; + Err(err_type) + } + + pub fn phase5_check_R_dash_sum(R_dash_vec: &[Point]) -> Result<(), Error> { + let sum = R_dash_vec + .iter() + .fold(Point::generator().to_point(), |acc, x| acc + x); + match sum - &Point::generator().to_point() == Point::generator().to_point() { + true => Ok(()), + false => Err(Phase5BadSum), + } + } + + pub fn phase6_compute_S_i_and_proof_of_consistency( + R: &Point, + T: &Point, + sigma: &Scalar, + l: &Scalar, + ) -> (Point, HomoELGamalProof) { + let S = R * sigma; + let delta = HomoElGamalStatement { + G: R.clone(), + H: Point::::base_point2().clone(), + Y: Point::generator().to_point(), + D: T.clone(), + E: S.clone(), + }; + let witness = HomoElGamalWitness { + x: l.clone(), + r: sigma.clone(), + }; + let proof = HomoELGamalProof::prove(&witness, &delta); + + (S, proof) + } + + pub fn phase6_verify_proof( + S_vec: &[Point], + proof_vec: &[HomoELGamalProof], + R_vec: &[Point], + T_vec: &[Point], + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + let mut verify_proofs = true; + for i in 0..proof_vec.len() { + let delta = HomoElGamalStatement { + G: R_vec[i].clone(), + H: Point::::base_point2().clone(), + Y: Point::generator().to_point(), + D: T_vec[i].clone(), + E: S_vec[i].clone(), + }; + if proof_vec[i].verify(&delta).is_err() { + verify_proofs = false; + bad_actors_vec.push(i); + }; + } + + match verify_proofs { + true => Ok(()), + false => { + let err_type = ErrorType { + error_type: "phase6".to_string(), + bad_actors: bad_actors_vec, + }; + Err(err_type) + } + } + } + + pub fn phase6_check_S_i_sum( + pubkey_y: &Point, + S_vec: &[Point], + ) -> Result<(), Error> { + let sum_plus_g = S_vec + .iter() + .fold(Point::generator().to_point(), |acc, x| acc + x); + let sum = sum_plus_g - &Point::generator().to_point(); + + match &sum == pubkey_y { + true => Ok(()), + false => Err(Phase6Error), + } + } + + pub fn phase7_local_sig( + k_i: &Scalar, + message: &BigInt, + R: &Point, + sigma_i: &Scalar, + pubkey: &Point, + ) -> Self { + let m_fe = Scalar::::from(message); + let r = Scalar::::from( + &R.x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let s_i = m_fe * k_i + &r * sigma_i; + Self { + r, + R: R.clone(), + s_i, + m: message.clone(), + y: pubkey.clone(), + } + } + + pub fn output_signature(&self, s_vec: &[Scalar]) -> Result { + let mut s = s_vec.iter().fold(self.s_i.clone(), |acc, x| acc + x); + let s_bn = s.to_bigint(); + + let r = Scalar::::from( + &self + .R + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let ry: BigInt = self + .R + .y_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + + /* + Calculate recovery id - it is not possible to compute the public key out of the signature + itself. Recovery id is used to enable extracting the public key uniquely. + 1. id = R.y & 1 + 2. if (s > curve.q / 2) id = id ^ 1 + */ + let is_ry_odd = ry.test_bit(0); + let mut recid = if is_ry_odd { 1 } else { 0 }; + let s_tag_bn = Scalar::::group_order() - &s_bn; + if s_bn > s_tag_bn { + s = Scalar::::from(&s_tag_bn); + recid ^= 1; + } + let sig = SignatureRecid { r, s, recid }; + let ver = verify(&sig, &self.y, &self.m).is_ok(); + if ver { + Ok(sig) + } else { + Err(InvalidSig) + } + } +} + +pub fn verify(sig: &SignatureRecid, y: &Point, message: &BigInt) -> Result<(), Error> { + let b = sig.s.invert().unwrap(); + let a = Scalar::::from(message); + let u1 = a * &b; + let u2 = &sig.r * &b; + + let g = Point::generator(); + let gu1 = g * u1; + let yu2 = y * &u2; + // can be faster using shamir trick + + if sig.r + == Scalar::::from( + &(gu1 + yu2) + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ) + { + Ok(()) + } else { + Err(InvalidSig) + } +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/keygen.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/keygen.rs new file mode 100644 index 0000000..37e157a --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/keygen.rs @@ -0,0 +1,528 @@ +//! High-level keygen protocol implementation + +use std::fmt; +use std::mem::replace; +use std::time::Duration; + +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Scalar}; +use round_based::containers::{ + push::{Push, PushExt}, + *, +}; +use round_based::{IsCritical, Msg, StateMachine}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use thiserror::Error; + +use crate::protocols::multi_party_ecdsa::gg_2020; + +mod rounds; + +use private::InternalError; +pub use rounds::{LocalKey, ProceedError}; +use rounds::{Round0, Round1, Round2, Round3, Round4}; + +/// Keygen protocol state machine +/// +/// Successfully completed keygen protocol produces [LocalKey] that can be used in further +/// [signing](super::sign) protocol. +pub struct Keygen { + round: R, + + msgs1: Option>>, + msgs2: Option>>, + msgs3: Option, Scalar)>>>, + msgs4: Option>>>, + + msgs_queue: Vec>, + + party_i: u16, + party_n: u16, +} + +impl Keygen { + /// Constructs a party of keygen protocol + /// + /// Takes party index `i` (in range `[1; n]`), threshold value `t`, and total number of + /// parties `n`. Party index identifies this party in the protocol, so it must be guaranteed + /// to be unique. + /// + /// Returns error if: + /// * `n` is less than 2, returns [Error::TooFewParties] + /// * `t` is not in range `[1; n-1]`, returns [Error::InvalidThreshold] + /// * `i` is not in range `[1; n]`, returns [Error::InvalidPartyIndex] + pub fn new(i: u16, t: u16, n: u16) -> Result { + if n < 2 { + return Err(Error::TooFewParties); + } + if t == 0 || t >= n { + return Err(Error::InvalidThreshold); + } + if i == 0 || i > n { + return Err(Error::InvalidPartyIndex); + } + let mut state = Self { + round: R::Round0(Round0 { party_i: i, t, n }), + + msgs1: Some(Round1::expects_messages(i, n)), + msgs2: Some(Round2::expects_messages(i, n)), + msgs3: Some(Round3::expects_messages(i, n)), + msgs4: Some(Round4::expects_messages(i, n)), + + msgs_queue: vec![], + + party_i: i, + party_n: n, + }; + + state.proceed_round(false)?; + Ok(state) + } + + fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a + where + F: FnMut(T) -> M + 'a, + { + (&mut self.msgs_queue).gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) + } + + /// Proceeds round state if it received enough messages and if it's cheap to compute or + /// `may_block == true` + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + let next_state: R; + let try_again: bool = match replace(&mut self.round, R::Gone) { + R::Round0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(self.gmap_queue(M::Round1)) + .map(R::Round1) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round0(_) => { + next_state = s; + false + } + R::Round1(round) if !store1_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs1.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round2)) + .map(R::Round2) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round1(_) => { + next_state = s; + false + } + R::Round2(round) if !store2_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs2.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round3)) + .map(R::Round3) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round2(_) => { + next_state = s; + false + } + R::Round3(round) if !store3_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs3.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round4)) + .map(R::Round4) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round3(_) => { + next_state = s; + false + } + R::Round4(round) if !store4_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs4.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs) + .map(R::Final) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round4(_) => { + next_state = s; + false + } + s @ R::Final(_) | s @ R::Gone => { + next_state = s; + false + } + }; + + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } +} + +impl StateMachine for Keygen { + type MessageBody = ProtocolMessage; + type Err = Error; + type Output = LocalKey; + + fn handle_incoming(&mut self, msg: Msg) -> Result<()> { + let current_round = self.current_round(); + + match msg.body { + ProtocolMessage(M::Round1(m)) => { + let store = self + .msgs1 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round2(m)) => { + let store = self + .msgs2 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round3(m)) => { + let store = self + .msgs3 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 3, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round4(m)) => { + let store = self + .msgs4 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 4, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + } + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + match &self.round { + R::Round0(_) => true, + R::Round1(_) => !store1_wants_more, + R::Round2(_) => !store2_wants_more, + R::Round3(_) => !store3_wants_more, + R::Round4(_) => !store4_wants_more, + R::Final(_) | R::Gone => false, + } + } + + fn proceed(&mut self) -> Result<()> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(self.round, R::Final(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + R::Final(_) => (), + R::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, R::Gone) { + R::Final(result) => Some(Ok(result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + R::Round0(_) => 0, + R::Round1(_) => 1, + R::Round2(_) => 2, + R::Round3(_) => 3, + R::Round4(_) => 4, + R::Final(_) | R::Gone => 5, + } + } + + fn total_rounds(&self) -> Option { + Some(4) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } +} + +impl super::traits::RoundBlame for Keygen { + /// Returns number of unwilling parties and a vector of their party indexes. + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = self.msgs1.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store2_blame = self.msgs2.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store3_blame = self.msgs3.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store4_blame = self.msgs4.as_ref().map(|s| s.blame()).unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + R::Round0(_) => default, + R::Round1(_) => store1_blame, + R::Round2(_) => store2_blame, + R::Round3(_) => store3_blame, + R::Round4(_) => store4_blame, + R::Final(_) | R::Gone => default, + } + } +} + +impl fmt::Debug for Keygen { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let current_round = match &self.round { + R::Round0(_) => "0", + R::Round1(_) => "1", + R::Round2(_) => "2", + R::Round3(_) => "3", + R::Round4(_) => "4", + R::Final(_) => "[Final]", + R::Gone => "[Gone]", + }; + let msgs1 = match self.msgs1.as_ref() { + Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), + None => "[None]".into(), + }; + let msgs2 = match self.msgs2.as_ref() { + Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), + None => "[None]".into(), + }; + let msgs3 = match self.msgs3.as_ref() { + Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), + None => "[None]".into(), + }; + let msgs4 = match self.msgs4.as_ref() { + Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), + None => "[None]".into(), + }; + write!( + f, + "{{Keygen at round={} msgs1={} msgs2={} msgs3={} msgs4={} queue=[len={}]}}", + current_round, + msgs1, + msgs2, + msgs3, + msgs4, + self.msgs_queue.len() + ) + } +} + +// Rounds + +enum R { + Round0(Round0), + Round1(Round1), + Round2(Round2), + Round3(Round3), + Round4(Round4), + Final(LocalKey), + Gone, +} + +// Messages + +/// Protocol message which parties send on wire +/// +/// Hides actual messages structure so it could be changed without breaking semver policy. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProtocolMessage(M); + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum M { + Round1(gg_2020::party_i::KeyGenBroadcastMessage1), + Round2(gg_2020::party_i::KeyGenDecommitMessage1), + Round3((VerifiableSS, Scalar)), + Round4(DLogProof), +} + +// Error + +type Result = std::result::Result; + +/// Error type of keygen protocol +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum Error { + /// Round proceeding resulted in error + #[error("proceed round: {0}")] + ProceedRound(#[source] ProceedError), + + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for keygen")] + TooFewParties, + /// Threshold value `t` is not in range `[1; n-1]` + #[error("threshold is not in range [1; n-1]")] + InvalidThreshold, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + /// Received message which we didn't expect to receive now (e.g. message from previous round) + #[error( + "didn't expect to receive message from round {msg_round} (being at round {current_round})" + )] + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// [Keygen::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// Some internal assertions were failed, which is a bug + #[doc(hidden)] + #[error("internal error: {0:?}")] + InternalError(InternalError), +} + +impl IsCritical for Error { + fn is_critical(&self) -> bool { + true + } +} + +impl From for Error { + fn from(err: InternalError) -> Self { + Self::InternalError(err) + } +} + +mod private { + #[derive(Debug)] + #[non_exhaustive] + pub enum InternalError { + /// [Messages store](super::MessageStore) reported that it received all messages it wanted to receive, + /// but refused to return message container + RetrieveRoundMessages(super::StoreErr), + #[doc(hidden)] + StoreGone, + } +} + +#[cfg(test)] +pub mod test { + use round_based::dev::Simulation; + + use super::*; + + pub fn simulate_keygen(t: u16, n: u16) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(true); + + for i in 1..=n { + simulation.add_party(Keygen::new(i, t, n).unwrap()); + } + + let keys = simulation.run().unwrap(); + + println!("Benchmark results:"); + println!("{:#?}", simulation.benchmark_results().unwrap()); + + keys + } + + #[test] + fn simulate_keygen_t1_n2() { + simulate_keygen(1, 2); + } + + #[test] + fn simulate_keygen_t1_n3() { + simulate_keygen(1, 3); + } + + #[test] + fn simulate_keygen_t2_n3() { + simulate_keygen(2, 3); + } +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/keygen/rounds.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/keygen/rounds.rs new file mode 100644 index 0000000..ed8df42 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/keygen/rounds.rs @@ -0,0 +1,347 @@ +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Curve, Point, Scalar}; +use sha2::Sha256; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use paillier::EncryptionKey; +use round_based::containers::push::Push; +use round_based::containers::{self, BroadcastMsgs, P2PMsgs, Store}; +use round_based::Msg; +use zk_paillier::zkproofs::DLogStatement; + +use crate::protocols::multi_party_ecdsa::gg_2020::party_i::{ + KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, +}; +use crate::protocols::multi_party_ecdsa::gg_2020::{self, ErrorType}; + +pub struct Round0 { + pub party_i: u16, + pub t: u16, + pub n: u16, +} + +impl Round0 { + pub fn proceed(self, mut output: O) -> Result + where + O: Push>, + { + let party_keys = Keys::create(self.party_i as usize); + let (bc1, decom1) = + party_keys.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2(); + + output.push(Msg { + sender: self.party_i, + receiver: None, + body: bc1.clone(), + }); + Ok(Round1 { + keys: party_keys, + bc1, + decom1, + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round1 { + keys: Keys, + bc1: KeyGenBroadcastMessage1, + decom1: KeyGenDecommitMessage1, + party_i: u16, + t: u16, + n: u16, +} + +impl Round1 { + pub fn proceed( + self, + input: BroadcastMsgs, + mut output: O, + ) -> Result + where + O: Push>, + { + output.push(Msg { + sender: self.party_i, + receiver: None, + body: self.decom1.clone(), + }); + Ok(Round2 { + keys: self.keys, + received_comm: input.into_vec_including_me(self.bc1), + decom: self.decom1, + + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } +} + +pub struct Round2 { + keys: gg_2020::party_i::Keys, + received_comm: Vec, + decom: KeyGenDecommitMessage1, + + party_i: u16, + t: u16, + n: u16, +} + +impl Round2 { + pub fn proceed( + self, + input: BroadcastMsgs, + mut output: O, + ) -> Result + where + O: Push, Scalar)>>, + { + let params = gg_2020::party_i::Parameters { + threshold: self.t, + share_count: self.n, + }; + let received_decom = input.into_vec_including_me(self.decom); + + let vss_result = self + .keys + .phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + ¶ms, + &received_decom, + &self.received_comm, + ) + .map_err(ProceedError::Round2VerifyCommitments)?; + + for (i, share) in vss_result.1.iter().enumerate() { + if i + 1 == usize::from(self.party_i) { + continue; + } + + output.push(Msg { + sender: self.party_i, + receiver: Some(i as u16 + 1), + body: (vss_result.0.clone(), share.clone()), + }) + } + + Ok(Round3 { + keys: self.keys, + + y_vec: received_decom.into_iter().map(|d| d.y_i).collect(), + bc_vec: self.received_comm, + + own_vss: vss_result.0.clone(), + own_share: vss_result.1[usize::from(self.party_i - 1)].clone(), + + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + true + } + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } +} + +pub struct Round3 { + keys: gg_2020::party_i::Keys, + + y_vec: Vec>, + bc_vec: Vec, + + own_vss: VerifiableSS, + own_share: Scalar, + + party_i: u16, + t: u16, + n: u16, +} + +impl Round3 { + pub fn proceed( + self, + input: P2PMsgs<(VerifiableSS, Scalar)>, + mut output: O, + ) -> Result + where + O: Push>>, + { + let params = gg_2020::party_i::Parameters { + threshold: self.t, + share_count: self.n, + }; + let (vss_schemes, party_shares): (Vec<_>, Vec<_>) = input + .into_vec_including_me((self.own_vss, self.own_share)) + .into_iter() + .unzip(); + + let (shared_keys, dlog_proof) = self + .keys + .phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶ms, + &self.y_vec, + &party_shares, + &vss_schemes, + self.party_i.into(), + ) + .map_err(ProceedError::Round3VerifyVssConstruct)?; + + output.push(Msg { + sender: self.party_i, + receiver: None, + body: dlog_proof.clone(), + }); + + Ok(Round4 { + keys: self.keys.clone(), + y_vec: self.y_vec.clone(), + bc_vec: self.bc_vec, + shared_keys, + own_dlog_proof: dlog_proof, + vss_vec: vss_schemes, + + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + true + } + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store, Scalar)>> { + containers::P2PMsgsStore::new(i, n) + } +} + +pub struct Round4 { + keys: gg_2020::party_i::Keys, + y_vec: Vec>, + bc_vec: Vec, + shared_keys: gg_2020::party_i::SharedKeys, + own_dlog_proof: DLogProof, + vss_vec: Vec>, + + party_i: u16, + t: u16, + n: u16, +} + +impl Round4 { + pub fn proceed( + self, + input: BroadcastMsgs>, + ) -> Result> { + let params = gg_2020::party_i::Parameters { + threshold: self.t, + share_count: self.n, + }; + let dlog_proofs = input.into_vec_including_me(self.own_dlog_proof.clone()); + + Keys::verify_dlog_proofs_check_against_vss( + ¶ms, + &dlog_proofs, + &self.y_vec, + &self.vss_vec, + ) + .map_err(ProceedError::Round4VerifyDLogProof)?; + let pk_vec = (0..params.share_count as usize) + .map(|i| dlog_proofs[i].pk.clone()) + .collect::>>(); + + let paillier_key_vec = (0..params.share_count) + .map(|i| self.bc_vec[i as usize].e.clone()) + .collect::>(); + let h1_h2_n_tilde_vec = self + .bc_vec + .iter() + .map(|bc1| bc1.dlog_statement.clone()) + .collect::>(); + + let (head, tail) = self.y_vec.split_at(1); + let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + + let local_key = LocalKey { + paillier_dk: self.keys.dk, + pk_vec, + + keys_linear: self.shared_keys.clone(), + paillier_key_vec, + y_sum_s: y_sum, + h1_h2_n_tilde_vec, + + vss_scheme: self.vss_vec[usize::from(self.party_i - 1)].clone(), + + i: self.party_i, + t: self.t, + n: self.n, + }; + + Ok(local_key) + } + pub fn is_expensive(&self) -> bool { + true + } + pub fn expects_messages(i: u16, n: u16) -> Store>> { + containers::BroadcastMsgsStore::new(i, n) + } +} + +/// Local secret obtained by party after [keygen](super::Keygen) protocol is completed +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LocalKey { + pub paillier_dk: paillier::DecryptionKey, + pub pk_vec: Vec>, + pub keys_linear: gg_2020::party_i::SharedKeys, + pub paillier_key_vec: Vec, + pub y_sum_s: Point, + pub h1_h2_n_tilde_vec: Vec, + pub vss_scheme: VerifiableSS, + pub i: u16, + pub t: u16, + pub n: u16, +} + +impl LocalKey { + /// Public key of secret shared between parties + pub fn public_key(&self) -> Point { + self.y_sum_s.clone() + } +} + +// Errors + +type Result = std::result::Result; + +/// Proceeding protocol error +/// +/// Subset of [keygen errors](enum@super::Error) that can occur at protocol proceeding (i.e. after +/// every message was received and pre-validated). +#[derive(Debug, Error)] +pub enum ProceedError { + #[error("round 2: verify commitments: {0:?}")] + Round2VerifyCommitments(ErrorType), + #[error("round 3: verify vss construction: {0:?}")] + Round3VerifyVssConstruct(ErrorType), + #[error("round 4: verify dlog proof: {0:?}")] + Round4VerifyDLogProof(ErrorType), +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/mod.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/mod.rs new file mode 100644 index 0000000..5958528 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/mod.rs @@ -0,0 +1,3 @@ +pub mod keygen; +pub mod sign; +pub mod traits; diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign.rs new file mode 100644 index 0000000..04dbc1e --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign.rs @@ -0,0 +1,764 @@ +//! # High-level threshold signing protocol implementation +//! +//! Key feature of GG20 protocol is one-round online signing, meaning that every party needs to +//! broadcast just a single message to sign a data. However, it still requires completing an offline +//! computation for fixed set of parties +//! +//! ## How to get things work +//! +//! First of all, parties need to carry out distributed key generation protocol (see [keygen module]). +//! After DKG is successfully completed, it outputs [LocalKey] — a party local secret share. +//! Then you fix a set of parties who will participate in threshold signing, and they run +//! [OfflineStage] protocol. `OfflineStage` implements [StateMachine] and can be executed in the same +//! way as [Keygen]. `OfflineStage` outputs a [CompletedOfflineStage]. [SignManual] takes a +//! `CompletedOfflineStage` and allows you to perform one-round signing. It doesn't implement +//! `StateMachine`, but rather provides methods to construct messages and final signature manually +//! (refer to [SignManual] documentation to see how to use it). +//! +//! [keygen module]: super::keygen +//! [Keygen]: super::keygen::Keygen +//! [LocalKey]: super::keygen::LocalKey +//! [StateMachine]: round_based::StateMachine + +use std::convert::TryFrom; +use std::mem::replace; +use std::time::Duration; + +use round_based::containers::{push::Push, BroadcastMsgs, MessageStore, P2PMsgs, Store, StoreErr}; +use round_based::{IsCritical, Msg, StateMachine}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::utilities::mta::MessageA; + +use crate::protocols::multi_party_ecdsa::gg_2020 as gg20; +use curv::elliptic::curves::secp256_k1::Secp256k1; +use gg20::party_i::{SignBroadcastPhase1, SignDecommitPhase1, SignatureRecid}; +use gg20::state_machine::keygen::LocalKey; + +mod fmt; +mod rounds; + +use crate::utilities::zk_pdl_with_slack::PDLwSlackProof; +use curv::BigInt; +use rounds::*; +pub use rounds::{CompletedOfflineStage, Error as ProceedError, PartialSignature}; + +/// Offline Stage of GG20 signing +/// +/// Successfully carried out Offline Stage will produce [CompletedOfflineStage] that can +/// be used for one-round signing multiple times. +pub struct OfflineStage { + round: OfflineR, + + msgs1: Option>>, + msgs2: Option>>, + msgs3: Option>>, + msgs4: Option>>, + msgs5: Option)>>>, + msgs6: Option>>, + + msgs_queue: MsgQueue, + + party_i: u16, + party_n: u16, +} + +impl OfflineStage { + /// Construct a party of offline stage of threshold signing protocol + /// + /// Once offline stage is finished, parties can do one-round threshold signing (i.e. they only + /// need to exchange a single set of messages). + /// + /// Takes party index `i` (in range `[1; n]`), list `s_l` of parties' indexes from keygen protocol + /// (`s_l[i]` must be an index of party `i` that was used by this party in keygen protocol), and + /// party local secret share `local_key`. + /// + /// Returns error if given arguments are contradicting. + pub fn new(i: u16, s_l: Vec, local_key: LocalKey) -> Result { + if s_l.len() < 2 { + return Err(Error::TooFewParties); + } + if i == 0 || usize::from(i) > s_l.len() { + return Err(Error::InvalidPartyIndex); + } + + let keygen_n = local_key.n; + if s_l.iter().any(|&i| i == 0 || i > keygen_n) { + return Err(Error::InvalidSl); + } + { + // Check if s_l has duplicates + let mut s_l_sorted = s_l.clone(); + s_l_sorted.sort_unstable(); + let mut s_l_sorted_deduped = s_l_sorted.clone(); + s_l_sorted_deduped.dedup(); + + if s_l_sorted != s_l_sorted_deduped { + return Err(Error::InvalidSl); + } + } + + let n = u16::try_from(s_l.len()).map_err(|_| Error::TooManyParties { n: s_l.len() })?; + + Ok(Self { + round: OfflineR::R0(Round0 { i, s_l, local_key }), + + msgs1: Some(Round1::expects_messages(i, n)), + msgs2: Some(Round2::expects_messages(i, n)), + msgs3: Some(Round3::expects_messages(i, n)), + msgs4: Some(Round4::expects_messages(i, n)), + msgs5: Some(Round5::expects_messages(i, n)), + msgs6: Some(Round6::expects_messages(i, n)), + + msgs_queue: MsgQueue(vec![]), + + party_i: i, + party_n: n, + }) + } + + // fn proceed_state(&mut self, may_block: bool) -> Result<()> { + // self.proceed_round(may_block)?; + // self.proceed_decommit_round(may_block) + // } + + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store5_wants_more = self.msgs5.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store6_wants_more = self.msgs6.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + let next_state: OfflineR; + let try_again: bool = match replace(&mut self.round, OfflineR::Gone) { + OfflineR::R0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(&mut self.msgs_queue) + .map(OfflineR::R1) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R0(_) => { + next_state = s; + false + } + OfflineR::R1(round) if !store1_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs1.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R2) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R1(_) => { + next_state = s; + false + } + OfflineR::R2(round) if !store2_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs2.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R3) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R2(_) => { + next_state = s; + false + } + OfflineR::R3(round) if !store3_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs3.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R4) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R3(_) => { + next_state = s; + false + } + OfflineR::R4(round) if !store4_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs4.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R5) + .map_err(Error::ProceedRound)?; + false + } + s @ OfflineR::R4(_) => { + next_state = s; + false + } + OfflineR::R5(round) if !store5_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs5.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R6) + .map_err(Error::ProceedRound)?; + false + } + s @ OfflineR::R5(_) => { + next_state = s; + false + } + OfflineR::R6(round) if !store6_wants_more && (!round.is_expensive() || may_block) => { + let store = self.msgs6.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs) + .map(OfflineR::Finished) + .map_err(Error::ProceedRound)?; + false + } + s @ OfflineR::R6(_) => { + next_state = s; + false + } + s @ OfflineR::Finished(_) | s @ OfflineR::Gone => { + next_state = s; + false + } + }; + + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } +} + +impl StateMachine for OfflineStage { + type MessageBody = OfflineProtocolMessage; + type Err = Error; + type Output = CompletedOfflineStage; + + fn handle_incoming(&mut self, msg: Msg) -> Result<(), Self::Err> { + let current_round = self.current_round(); + + match msg.body { + OfflineProtocolMessage(OfflineM::M1(m)) => { + let store = self + .msgs1 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M2(m)) => { + let store = self + .msgs2 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M3(m)) => { + let store = self + .msgs3 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M4(m)) => { + let store = self + .msgs4 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M5(m)) => { + let store = self + .msgs5 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M6(m)) => { + let store = self + .msgs6 + .as_mut() + .ok_or(Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + })?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + } + self.proceed_round(false) + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue.0 + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store5_wants_more = self.msgs5.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store6_wants_more = self.msgs6.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + match &self.round { + OfflineR::R0(_) => true, + OfflineR::R1(_) => !store1_wants_more, + OfflineR::R2(_) => !store2_wants_more, + OfflineR::R3(_) => !store3_wants_more, + OfflineR::R4(_) => !store4_wants_more, + OfflineR::R5(_) => !store5_wants_more, + OfflineR::R6(_) => !store6_wants_more, + OfflineR::Finished(_) | OfflineR::Gone => false, + } + } + + fn proceed(&mut self) -> Result<(), Self::Err> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(&self.round, OfflineR::Finished(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + OfflineR::Finished(_) => (), + OfflineR::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, OfflineR::Gone) { + OfflineR::Finished(result) => Some(Ok(result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + OfflineR::R0(_) => 0, + OfflineR::R1(_) => 1, + OfflineR::R2(_) => 2, + OfflineR::R3(_) => 3, + OfflineR::R4(_) => 4, + OfflineR::R5(_) => 5, + OfflineR::R6(_) => 6, + OfflineR::Finished(_) | OfflineR::Gone => 7, + } + } + + fn total_rounds(&self) -> Option { + Some(6) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } +} + +impl super::traits::RoundBlame for OfflineStage { + /// RoundBlame returns number of unwilling parties and a vector of their party indexes. + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = self.msgs1.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store2_blame = self.msgs2.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store3_blame = self.msgs3.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store4_blame = self.msgs4.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store5_blame = self.msgs5.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store6_blame = self.msgs6.as_ref().map(|s| s.blame()).unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + OfflineR::R0(_) => default, + OfflineR::R1(_) => store1_blame, + OfflineR::R2(_) => store2_blame, + OfflineR::R3(_) => store3_blame, + OfflineR::R4(_) => store4_blame, + OfflineR::R5(_) => store5_blame, + OfflineR::R6(_) => store6_blame, + OfflineR::Finished(_) => store6_blame, + OfflineR::Gone => default, + } + } +} + +#[allow(clippy::large_enum_variant)] +enum OfflineR { + R0(Round0), + R1(Round1), + R2(Round2), + R3(Round3), + R4(Round4), + R5(Round5), + R6(Round6), + Finished(CompletedOfflineStage), + Gone, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OfflineProtocolMessage(OfflineM); + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(clippy::large_enum_variant)] +enum OfflineM { + M1((MessageA, SignBroadcastPhase1)), + M2((GammaI, WI)), + M3((DeltaI, TI, TIProof)), + M4(SignDecommitPhase1), + M5((RDash, Vec)), + M6((SI, HEGProof)), +} + +struct MsgQueue(Vec>); + +macro_rules! make_pushable { + ($($constructor:ident $t:ty),*$(,)?) => { + $( + impl Push> for MsgQueue { + fn push(&mut self, m: Msg<$t>) { + Vec::push(&mut self.0, Msg{ + sender: m.sender, + receiver: m.receiver, + body: OfflineProtocolMessage(OfflineM::$constructor(m.body)) + }) + } + } + )* + }; +} + +make_pushable! { + M1 (MessageA, SignBroadcastPhase1), + M2 (GammaI, WI), + M3 (DeltaI, TI, TIProof), + M4 SignDecommitPhase1, + M5 (RDash, Vec), + M6 (SI, HEGProof), +} + +type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for signing")] + TooFewParties, + /// Too many parties. `n` must fit into `u16`, so only `n < u16::MAX` values are supported. + #[error("too many parties: n={n}, n must be less than 2^16")] + TooManyParties { n: usize }, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + /// List `s_l` is invalid. Either it contains duplicates (`exist i j. i != j && s_l[i] = s_l[j]`), + /// or contains index that is not in the range `[1; keygen_n]`, `keygen_n` — number of parties + /// participated in DKG (`exist i. s_l[i] = 0 || s_l[i] > keygen_n`). + #[error("invalid s_l")] + InvalidSl, + + /// Round proceeding resulted in protocol error + #[error("proceeding round: {0}")] + ProceedRound(rounds::Error), + + /// Received message which we didn't expect to receive now (e.g. message from previous round) + #[error( + "didn't expect to receive message from round {msg_round} (being at round {current_round})" + )] + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + + /// [OfflineStage::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// A bug in protocol implementation + #[error("offline stage protocol bug: {0}")] + Bug(InternalError), +} + +#[derive(Debug, Error)] +pub enum InternalError { + #[error("store gone")] + StoreGone, + #[error("store reported that it's collected all the messages it needed, but refused to give received messages")] + RetrieveMessagesFromStore(StoreErr), + #[error("decommit round expected to be in NotStarted state")] + DecommitRoundWasntInInitialState, +} + +impl From for Error { + fn from(err: InternalError) -> Self { + Error::Bug(err) + } +} + +impl IsCritical for Error { + fn is_critical(&self) -> bool { + match self { + Error::TooFewParties => true, + Error::TooManyParties { .. } => true, + Error::InvalidPartyIndex => true, + Error::InvalidSl => true, + Error::ProceedRound(_) => true, + Error::ReceivedOutOfOrderMessage { .. } => false, + Error::HandleMessage(_) => false, + Error::DoublePickOutput => true, + Error::Bug(_) => true, + } + } +} + +/// Manual GG20 signing +/// +/// After you completed [OfflineStage] and got [CompletedOfflineStage], parties can perform signing +/// simply by broadcasting a single message. +/// +/// ## Example +/// ```no_run +/// # use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ +/// # state_machine::sign::{CompletedOfflineStage, SignManual, PartialSignature}, +/// # party_i::{LocalSignature, verify}, +/// # }; +/// # use curv::arithmetic::{BigInt, Converter}; +/// # type Result = std::result::Result>; +/// # fn broadcast(msg: PartialSignature) -> Result<()> { panic!() } +/// # fn wait_messages() -> Result> { panic!() } +/// # fn main() -> Result<()> { +/// # let completed_offline_stage: CompletedOfflineStage = panic!(); +/// let data = BigInt::from_bytes(b"a message"); +/// +/// // Sign a message locally +/// let (sign, msg) = SignManual::new(data.clone(), completed_offline_stage)?; +/// // Broadcast local partial signature +/// broadcast(msg)?; +/// // Collect partial signatures from other parties +/// let sigs: Vec = wait_messages()?; +/// // Complete signing +/// let signature = sign.complete(&sigs)?; +/// // Verify that signature matches joint public key +/// assert!(verify(&signature, completed_offline_stage.public_key(), &data).is_ok()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone)] +pub struct SignManual { + state: Round7, +} + +impl SignManual { + pub fn new( + message: BigInt, + completed_offline_stage: CompletedOfflineStage, + ) -> Result<(Self, PartialSignature), SignError> { + Round7::new(&message, completed_offline_stage) + .map(|(state, m)| (Self { state }, m)) + .map_err(SignError::LocalSigning) + } + + /// `sigs` must not include partial signature produced by local party (only partial signatures produced + /// by other parties) + pub fn complete(self, sigs: &[PartialSignature]) -> Result { + self.state + .proceed_manual(sigs) + .map_err(SignError::CompleteSigning) + } +} + +#[derive(Debug, Error)] +pub enum SignError { + #[error("signing message locally: {0}")] + LocalSigning(rounds::Error), + #[error("couldn't complete signing: {0}")] + CompleteSigning(rounds::Error), +} + +#[cfg(test)] +mod test { + use curv::arithmetic::Converter; + use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; + use round_based::dev::Simulation; + use sha2::Sha256; + + use super::*; + use gg20::party_i::verify; + use gg20::state_machine::keygen::test::simulate_keygen; + + fn simulate_offline_stage( + local_keys: Vec>, + s_l: &[u16], + ) -> Vec { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(true); + + for (i, &keygen_i) in (1..).zip(s_l) { + simulation.add_party( + OfflineStage::new( + i, + s_l.to_vec(), + local_keys[usize::from(keygen_i - 1)].clone(), + ) + .unwrap(), + ); + } + + let stages = simulation.run().unwrap(); + + println!("Benchmark results:"); + println!("{:#?}", simulation.benchmark_results().unwrap()); + + stages + } + + fn simulate_signing(offline: Vec, message: &[u8]) { + let message = Sha256::new() + .chain_bigint(&BigInt::from_bytes(message)) + .result_bigint(); + let pk = offline[0].public_key().clone(); + + let parties = offline + .iter() + .map(|o| SignManual::new(message.clone(), o.clone())) + .collect::, _>>() + .unwrap(); + let (parties, local_sigs): (Vec<_>, Vec<_>) = parties.into_iter().unzip(); + // parties.remove(0).complete(&local_sigs[1..]).unwrap(); + let local_sigs_except = |i: usize| { + let mut v = vec![]; + v.extend_from_slice(&local_sigs[..i]); + if i + 1 < local_sigs.len() { + v.extend_from_slice(&local_sigs[i + 1..]); + } + v + }; + + assert!(parties + .into_iter() + .enumerate() + .map(|(i, p)| p.complete(&local_sigs_except(i)).unwrap()) + .all(|signature| verify(&signature, &pk, &message).is_ok())); + } + + #[test] + fn simulate_offline_stage_t1_n2_s2() { + let local_keys = simulate_keygen(1, 2); + simulate_offline_stage(local_keys, &[1, 2]); + } + + #[test] + fn simulate_offline_stage_t1_n3_s2() { + let local_keys = simulate_keygen(1, 3); + simulate_offline_stage(local_keys, &[1, 3]); + } + + #[test] + fn simulate_offline_stage_t2_n3_s3() { + let local_keys = simulate_keygen(2, 3); + simulate_offline_stage(local_keys, &[1, 2, 3]); + } + + #[test] + fn simulate_signing_t1_n2_s2() { + let local_keys = simulate_keygen(1, 2); + let offline_stage = simulate_offline_stage(local_keys, &[1, 2]); + simulate_signing(offline_stage, b"ZenGo") + } + + #[test] + fn simulate_signing_t1_n3_s2() { + let local_keys = simulate_keygen(1, 3); + let offline_stage = simulate_offline_stage(local_keys.clone(), &[1, 2]); + simulate_signing(offline_stage, b"ZenGo"); + let offline_stage = simulate_offline_stage(local_keys.clone(), &[1, 3]); + simulate_signing(offline_stage, b"ZenGo"); + let offline_stage = simulate_offline_stage(local_keys, &[2, 3]); + simulate_signing(offline_stage, b"ZenGo"); + } + + #[test] + fn simulate_signing_t2_n3_s3() { + let local_keys = simulate_keygen(2, 3); + let offline_stage = simulate_offline_stage(local_keys, &[1, 2, 3]); + simulate_signing(offline_stage, b"ZenGo") + } +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign/fmt.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign/fmt.rs new file mode 100644 index 0000000..3ecf463 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign/fmt.rs @@ -0,0 +1,123 @@ +use std::fmt; + +use round_based::containers::{BroadcastMsgsStore, MessageStore, P2PMsgsStore}; + +impl fmt::Debug for super::OfflineStage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + OfflineStageProgress::from(self).fmt(f) + } +} +#[allow(dead_code)] +#[derive(Debug)] +pub struct OfflineStageProgress { + round: OfflineR, + round1_msgs: ReceivedMessages, + round2_msgs: ReceivedMessages, + round3_msgs: ReceivedMessages, + round4_msgs: ReceivedMessages, + round5_msgs: ReceivedMessages, + msgs_queue: OutgoingMessages, +} + +impl From<&super::OfflineStage> for OfflineStageProgress { + fn from(state: &super::OfflineStage) -> Self { + Self { + round: match &state.round { + super::OfflineR::R0(_) => OfflineR::R0, + super::OfflineR::R1(_) => OfflineR::R1, + super::OfflineR::R2(_) => OfflineR::R2, + super::OfflineR::R3(_) => OfflineR::R3, + super::OfflineR::R4(_) => OfflineR::R4, + super::OfflineR::R5(_) => OfflineR::R5, + super::OfflineR::R6(_) => OfflineR::R6, + super::OfflineR::Finished(_) => OfflineR::Finished, + super::OfflineR::Gone => OfflineR::Gone, + }, + + round1_msgs: ReceivedMessages::from_broadcast(state.msgs1.as_ref()), + round2_msgs: ReceivedMessages::from_p2p(state.msgs2.as_ref()), + round3_msgs: ReceivedMessages::from_broadcast(state.msgs3.as_ref()), + round4_msgs: ReceivedMessages::from_broadcast(state.msgs4.as_ref()), + round5_msgs: ReceivedMessages::from_broadcast(state.msgs5.as_ref()), + + msgs_queue: OutgoingMessages { + len: state.msgs_queue.0.len(), + }, + } + } +} + +#[derive(Debug)] +pub enum OfflineR { + R0, + R1, + R2, + R3, + R4, + R5, + R6, + Finished, + Gone, +} + +pub enum ContainerType { + P2P, + Broadcast, +} + +pub struct ReceivedMessages(Option); + +pub struct MessagesContainer { + ty: ContainerType, + total: usize, + waiting_for: Vec, +} + +impl ReceivedMessages { + fn from_broadcast(store: Option<&BroadcastMsgsStore>) -> Self { + match store { + Some(store) => ReceivedMessages(Some(MessagesContainer { + ty: ContainerType::Broadcast, + total: store.messages_total(), + waiting_for: store.blame().1, + })), + None => ReceivedMessages(None), + } + } + fn from_p2p(store: Option<&P2PMsgsStore>) -> Self { + match store { + Some(store) => ReceivedMessages(Some(MessagesContainer { + ty: ContainerType::P2P, + total: store.messages_total(), + waiting_for: store.blame().1, + })), + None => ReceivedMessages(None), + } + } +} + +impl fmt::Debug for ReceivedMessages { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + Some(container) => { + let ty = match container.ty { + ContainerType::Broadcast => "bc", + ContainerType::P2P => "p2p", + }; + write!( + f, + "[{} {}/{}]", + ty, + container.total - container.waiting_for.len(), + container.total + ) + } + None => write!(f, "[gone]"), + } + } +} +#[allow(dead_code)] +#[derive(Debug)] +pub struct OutgoingMessages { + len: usize, +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign/rounds.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign/rounds.rs new file mode 100644 index 0000000..fb80dfe --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/sign/rounds.rs @@ -0,0 +1,697 @@ +#![allow(non_snake_case)] + +use std::convert::TryFrom; +use std::iter; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use sha2::Sha256; + +use round_based::containers::push::Push; +use round_based::containers::{self, BroadcastMsgs, P2PMsgs, Store}; +use round_based::Msg; + +use crate::utilities::mta::{MessageA, MessageB}; + +use crate::protocols::multi_party_ecdsa::gg_2020 as gg20; +use crate::utilities::zk_pdl_with_slack::PDLwSlackProof; +use curv::cryptographic_primitives::proofs::sigma_correct_homomorphic_elgamal_enc::HomoELGamalProof; +use curv::cryptographic_primitives::proofs::sigma_valid_pedersen::PedersenProof; +use gg20::party_i::{ + LocalSignature, SignBroadcastPhase1, SignDecommitPhase1, SignKeys, SignatureRecid, +}; +use gg20::state_machine::keygen::LocalKey; +use gg20::ErrorType; + +type Result = std::result::Result; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(clippy::upper_case_acronyms)] +pub struct GWI(pub Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GammaI(pub MessageB); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct WI(pub MessageB); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DeltaI(Scalar); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TI(pub Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TIProof(pub PedersenProof); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RDash(Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SI(pub Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct HEGProof(pub HomoELGamalProof); + +pub struct Round0 { + /// Index of this party + /// + /// Must be in range `[0; n)` where `n` is number of parties involved in signing. + pub i: u16, + + /// List of parties' indexes from keygen protocol + /// + /// I.e. `s_l[i]` must be an index of party `i` that was used by this party in keygen protocol. + // s_l.len()` equals to `n` (number of parties involved in signing) + pub s_l: Vec, + + /// Party local secret share + pub local_key: LocalKey, +} + +impl Round0 { + pub fn proceed(self, mut output: O) -> Result + where + O: Push>, + { + let sign_keys = SignKeys::create( + &self.local_key.keys_linear.x_i, + &self.local_key.vss_scheme.clone(), + usize::from(self.s_l[usize::from(self.i - 1)]) - 1, + &self + .s_l + .iter() + .map(|&i| usize::from(i) - 1) + .collect::>(), + ); + let (bc1, decom1) = sign_keys.phase1_broadcast(); + + let party_ek = self.local_key.paillier_key_vec[usize::from(self.local_key.i - 1)].clone(); + let m_a = MessageA::a(&sign_keys.k_i, &party_ek, &self.local_key.h1_h2_n_tilde_vec); + + output.push(Msg { + sender: self.i, + receiver: None, + body: (m_a.0.clone(), bc1.clone()), + }); + + let round1 = Round1 { + i: self.i, + s_l: self.s_l.clone(), + local_key: self.local_key, + m_a, + sign_keys, + phase1_com: bc1, + phase1_decom: decom1, + }; + + Ok(round1) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round1 { + i: u16, + s_l: Vec, + local_key: LocalKey, + m_a: (MessageA, BigInt), + sign_keys: SignKeys, + phase1_com: SignBroadcastPhase1, + phase1_decom: SignDecommitPhase1, +} + +impl Round1 { + pub fn proceed( + self, + input: BroadcastMsgs<(MessageA, SignBroadcastPhase1)>, + mut output: O, + ) -> Result + where + O: Push>, + { + let (m_a_vec, bc_vec): (Vec<_>, Vec<_>) = input + .into_vec_including_me((self.m_a.0.clone(), self.phase1_com.clone())) + .into_iter() + .unzip(); + + let mut m_b_gamma_vec = Vec::new(); + let mut beta_vec = Vec::new(); + let mut m_b_w_vec = Vec::new(); + let mut ni_vec = Vec::new(); + + let ttag = self.s_l.len(); + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let i = usize::from(self.i - 1); + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let (m_b_gamma, beta_gamma, _beta_randomness, _beta_tag) = MessageB::b( + &self.sign_keys.gamma_i, + &self.local_key.paillier_key_vec[l_s[ind]], + m_a_vec[ind].clone(), + &self.local_key.h1_h2_n_tilde_vec, + ) + .expect("Incorrect Alice's range proof in MtA"); + let (m_b_w, beta_wi, _, _) = MessageB::b( + &self.sign_keys.w_i, + &self.local_key.paillier_key_vec[l_s[ind]], + m_a_vec[ind].clone(), + &self.local_key.h1_h2_n_tilde_vec, + ) + .expect("Incorrect Alice's range proof in MtA"); + + m_b_gamma_vec.push(m_b_gamma); + beta_vec.push(beta_gamma); + m_b_w_vec.push(m_b_w); + ni_vec.push(beta_wi); + } + + let party_indices = (1..=self.s_l.len()) + .map(|j| u16::try_from(j).unwrap()) + .filter(|&j| j != self.i); + for ((j, gamma_i), w_i) in party_indices.zip(m_b_gamma_vec).zip(m_b_w_vec) { + output.push(Msg { + sender: self.i, + receiver: Some(j), + body: (GammaI(gamma_i.clone()), WI(w_i.clone())), + }); + } + + Ok(Round2 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + m_a: self.m_a, + beta_vec, + ni_vec, + bc_vec, + m_a_vec, + phase1_decom: self.phase1_decom, + }) + } + + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round2 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + m_a: (MessageA, BigInt), + beta_vec: Vec>, + ni_vec: Vec>, + bc_vec: Vec, + m_a_vec: Vec, + phase1_decom: SignDecommitPhase1, +} + +impl Round2 { + pub fn proceed(self, input_p2p: P2PMsgs<(GammaI, WI)>, mut output: O) -> Result + where + O: Push>, // TODO: unify TI and TIProof + { + let (m_b_gamma_s, m_b_w_s): (Vec<_>, Vec<_>) = input_p2p + .into_vec() + .into_iter() + .map(|(gamma_i, w_i)| (gamma_i.0, w_i.0)) + .unzip(); + + let mut alpha_vec = Vec::new(); + let mut miu_vec = Vec::new(); + + let ttag = self.s_l.len(); + let index = usize::from(self.i) - 1; + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let g_w_vec = SignKeys::g_w_vec( + &self.local_key.pk_vec[..], + &l_s[..], + &self.local_key.vss_scheme, + ); + for j in 0..ttag - 1 { + let ind = if j < index { j } else { j + 1 }; + let m_b = m_b_gamma_s[j].clone(); + + let alpha_ij_gamma = m_b + .verify_proofs_get_alpha(&self.local_key.paillier_dk, &self.sign_keys.k_i) + .expect("wrong dlog or m_b"); + let m_b = m_b_w_s[j].clone(); + let alpha_ij_wi = m_b + .verify_proofs_get_alpha(&self.local_key.paillier_dk, &self.sign_keys.k_i) + .expect("wrong dlog or m_b"); + assert_eq!(m_b.b_proof.pk, g_w_vec[ind]); //TODO: return error + + alpha_vec.push(alpha_ij_gamma.0); + miu_vec.push(alpha_ij_wi.0); + } + + let delta_i = self.sign_keys.phase2_delta_i(&alpha_vec, &self.beta_vec); + + let sigma_i = self.sign_keys.phase2_sigma_i(&miu_vec, &self.ni_vec); + let (t_i, l_i, t_i_proof) = SignKeys::phase3_compute_t_i(&sigma_i); + output.push(Msg { + sender: self.i, + receiver: None, + body: ( + DeltaI(delta_i.clone()), + TI(t_i.clone()), + TIProof(t_i_proof.clone()), + ), + }); + + Ok(Round3 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + m_a: self.m_a, + mb_gamma_s: m_b_gamma_s, + bc_vec: self.bc_vec, + m_a_vec: self.m_a_vec, + delta_i, + t_i, + l_i, + sigma_i, + t_i_proof, + phase1_decom: self.phase1_decom, + }) + } + + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::P2PMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round3 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + m_a: (MessageA, BigInt), + mb_gamma_s: Vec, + bc_vec: Vec, + m_a_vec: Vec, + delta_i: Scalar, + t_i: Point, + l_i: Scalar, + sigma_i: Scalar, + t_i_proof: PedersenProof, + + phase1_decom: SignDecommitPhase1, +} + +impl Round3 { + pub fn proceed( + self, + input: BroadcastMsgs<(DeltaI, TI, TIProof)>, + mut output: O, + ) -> Result + where + O: Push>, + { + let (delta_vec, t_vec, t_proof_vec) = input + .into_vec_including_me(( + DeltaI(self.delta_i), + TI(self.t_i.clone()), + TIProof(self.t_i_proof), + )) + .into_iter() + .map(|(delta_i, t_i, t_i_proof)| (delta_i.0, t_i.0, t_i_proof.0)) + .unzip3(); + + for i in 0..t_vec.len() { + assert_eq!(t_vec[i], t_proof_vec[i].com); + } + + let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); + let ttag = self.s_l.len(); + for proof in t_proof_vec.iter().take(ttag) { + PedersenProof::verify(proof).expect("error T proof"); + } + + output.push(Msg { + sender: self.i, + receiver: None, + body: self.phase1_decom.clone(), + }); + + Ok(Round4 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + m_a: self.m_a, + mb_gamma_s: self.mb_gamma_s, + bc_vec: self.bc_vec, + m_a_vec: self.m_a_vec, + t_i: self.t_i, + l_i: self.l_i, + sigma_i: self.sigma_i, + phase1_decom: self.phase1_decom, + delta_inv, + t_vec, + }) + } + + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round4 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + m_a: (MessageA, BigInt), + mb_gamma_s: Vec, + bc_vec: Vec, + m_a_vec: Vec, + t_i: Point, + l_i: Scalar, + sigma_i: Scalar, + delta_inv: Scalar, + t_vec: Vec>, + phase1_decom: SignDecommitPhase1, +} + +impl Round4 { + pub fn proceed( + self, + decommit_round1: BroadcastMsgs, + mut output: O, + ) -> Result + where + O: Push)>>, + { + let decom_vec: Vec<_> = decommit_round1.into_vec_including_me(self.phase1_decom.clone()); + + let ttag = self.s_l.len(); + let b_proof_vec: Vec<_> = (0..ttag - 1).map(|i| &self.mb_gamma_s[i].b_proof).collect(); + let R = SignKeys::phase4( + &self.delta_inv, + &b_proof_vec[..], + decom_vec, + &self.bc_vec, + usize::from(self.i - 1), + ) + .expect(""); //TODO: propagate the error + let R_dash = &R * &self.sign_keys.k_i; + + // each party sends first message to all other parties + let mut phase5_proofs_vec = Vec::new(); + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let index = usize::from(self.i - 1); + for j in 0..ttag - 1 { + let ind = if j < index { j } else { j + 1 }; + let proof = LocalSignature::phase5_proof_pdl( + &R_dash, + &R, + &self.m_a.0.c, + &self.local_key.paillier_key_vec[l_s[index]], + &self.sign_keys.k_i, + &self.m_a.1, + &self.local_key.h1_h2_n_tilde_vec[l_s[ind]], + ); + + phase5_proofs_vec.push(proof); + } + + output.push(Msg { + sender: self.i, + receiver: None, + body: (RDash(R_dash.clone()), phase5_proofs_vec.clone()), + }); + + Ok(Round5 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + t_vec: self.t_vec, + m_a_vec: self.m_a_vec, + t_i: self.t_i, + l_i: self.l_i, + sigma_i: self.sigma_i, + R, + R_dash, + phase5_proofs_vec, + }) + } + + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round5 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + t_vec: Vec>, + m_a_vec: Vec, + t_i: Point, + l_i: Scalar, + sigma_i: Scalar, + R: Point, + R_dash: Point, + phase5_proofs_vec: Vec, +} + +impl Round5 { + pub fn proceed( + self, + input: BroadcastMsgs<(RDash, Vec)>, + mut output: O, + ) -> Result + where + O: Push>, + { + let (r_dash_vec, pdl_proof_mat_inc_me): (Vec<_>, Vec<_>) = input + .into_vec_including_me((RDash(self.R_dash), self.phase5_proofs_vec)) + .into_iter() + .map(|(r_dash, pdl_proof)| (r_dash.0, pdl_proof)) + .unzip(); + + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let ttag = self.s_l.len(); + for i in 0..ttag { + LocalSignature::phase5_verify_pdl( + &pdl_proof_mat_inc_me[i], + &r_dash_vec[i], + &self.R, + &self.m_a_vec[i].c, + &self.local_key.paillier_key_vec[l_s[i]], + &self.local_key.h1_h2_n_tilde_vec, + &l_s, + i, + ) + .expect("phase5 verify pdl error"); + } + LocalSignature::phase5_check_R_dash_sum(&r_dash_vec).expect("R_dash error"); + + let (S_i, homo_elgamal_proof) = LocalSignature::phase6_compute_S_i_and_proof_of_consistency( + &self.R, + &self.t_i, + &self.sigma_i, + &self.l_i, + ); + + output.push(Msg { + sender: self.i, + receiver: None, + body: (SI(S_i.clone()), HEGProof(homo_elgamal_proof.clone())), + }); + + Ok(Round6 { + S_i, + homo_elgamal_proof, + s_l: self.s_l, + protocol_output: CompletedOfflineStage { + i: self.i, + local_key: self.local_key, + sign_keys: self.sign_keys, + t_vec: self.t_vec, + R: self.R, + sigma_i: self.sigma_i, + }, + }) + } + + pub fn expects_messages(i: u16, n: u16) -> Store)>> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round6 { + S_i: Point, + homo_elgamal_proof: HomoELGamalProof, + s_l: Vec, + /// Round 6 guards protocol output until final checks are taken the place + protocol_output: CompletedOfflineStage, +} + +impl Round6 { + pub fn proceed( + self, + input: BroadcastMsgs<(SI, HEGProof)>, + ) -> Result { + let (S_i_vec, hegp_vec): (Vec<_>, Vec<_>) = input + .into_vec_including_me((SI(self.S_i), HEGProof(self.homo_elgamal_proof))) + .into_iter() + .map(|(s_i, hegp_i)| (s_i.0, hegp_i.0)) + .unzip(); + let R_vec: Vec<_> = iter::repeat(self.protocol_output.R.clone()) + .take(self.s_l.len()) + .collect(); + + LocalSignature::phase6_verify_proof( + &S_i_vec, + &hegp_vec, + &R_vec, + &self.protocol_output.t_vec, + ) + .map_err(Error::Round6VerifyProof)?; + LocalSignature::phase6_check_S_i_sum(&self.protocol_output.local_key.y_sum_s, &S_i_vec) + .map_err(Error::Round6CheckSig)?; + + Ok(self.protocol_output) + } + + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +#[allow(dead_code)] +pub struct CompletedOfflineStage { + i: u16, + local_key: LocalKey, + sign_keys: SignKeys, + t_vec: Vec>, + R: Point, + sigma_i: Scalar, +} + +impl CompletedOfflineStage { + pub fn public_key(&self) -> &Point { + &self.local_key.y_sum_s + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PartialSignature(Scalar); + +#[derive(Clone)] +pub struct Round7 { + local_signature: LocalSignature, +} + +impl Round7 { + pub fn new( + message: &BigInt, + completed_offline_stage: CompletedOfflineStage, + ) -> Result<(Self, PartialSignature)> { + let local_signature = LocalSignature::phase7_local_sig( + &completed_offline_stage.sign_keys.k_i, + message, + &completed_offline_stage.R, + &completed_offline_stage.sigma_i, + &completed_offline_stage.local_key.y_sum_s, + ); + let partial = PartialSignature(local_signature.s_i.clone()); + Ok((Self { local_signature }, partial)) + } + + pub fn proceed_manual(self, sigs: &[PartialSignature]) -> Result { + let sigs = sigs.iter().map(|s_i| s_i.0.clone()).collect::>(); + self.local_signature + .output_signature(&sigs) + .map_err(Error::Round7) + } +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("round 1: {0:?}")] + Round1(ErrorType), + #[error("round 2 stage 3: {0:?}")] + Round2Stage3(crate::Error), + #[error("round 2 stage 4: {0:?}")] + Round2Stage4(ErrorType), + #[error("round 3: {0:?}")] + Round3(ErrorType), + #[error("round 5: {0:?}")] + Round5(ErrorType), + #[error("round 6: verify proof: {0:?}")] + Round6VerifyProof(ErrorType), + #[error("round 6: check sig: {0:?}")] + Round6CheckSig(crate::Error), + #[error("round 7: {0:?}")] + Round7(crate::Error), +} + +trait IteratorExt: Iterator { + fn unzip3(self) -> (Vec, Vec, Vec) + where + Self: Iterator + Sized, + { + let (mut a, mut b, mut c) = (vec![], vec![], vec![]); + for (a_i, b_i, c_i) in self { + a.push(a_i); + b.push(b_i); + c.push(c_i); + } + (a, b, c) + } +} + +impl IteratorExt for I where I: Iterator {} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/state_machine/traits.rs b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/traits.rs new file mode 100644 index 0000000..991ee52 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/state_machine/traits.rs @@ -0,0 +1,6 @@ +pub trait RoundBlame { + /// Retrieves a list of uncorporative parties + /// + /// Returns a numbers of messages yet to recieve and list of parties to send messages for the current round + fn round_blame(&self) -> (u16, Vec); +} diff --git a/src/protocols/multi_party_ecdsa/gg_2020/test.rs b/src/protocols/multi_party_ecdsa/gg_2020/test.rs new file mode 100644 index 0000000..00aebc2 --- /dev/null +++ b/src/protocols/multi_party_ecdsa/gg_2020/test.rs @@ -0,0 +1,783 @@ +#![allow(non_snake_case)] + +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ +use crate::protocols::multi_party_ecdsa::gg_2020::blame::{ + GlobalStatePhase5, GlobalStatePhase6, GlobalStatePhase7, LocalStatePhase5, LocalStatePhase6, +}; +use crate::protocols::multi_party_ecdsa::gg_2020::party_i::SignatureRecid; +use crate::protocols::multi_party_ecdsa::gg_2020::party_i::{ + KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, LocalSignature, Parameters, SharedKeys, + SignKeys, +}; +use crate::utilities::mta::{MessageA, MessageB}; +use curv::arithmetic::traits::Converter; + +use crate::protocols::multi_party_ecdsa::gg_2020::ErrorType; +use crate::utilities::zk_pdl_with_slack::PDLwSlackProof; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::cryptographic_primitives::proofs::sigma_valid_pedersen::PedersenProof; +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use paillier::*; +use sha2::Sha256; +use zk_paillier::zkproofs::DLogStatement; + +#[test] +fn test_keygen_t1_n2() { + assert!(keygen_t_n_parties(1, 2).is_ok()); +} + +#[test] +fn test_keygen_t2_n3() { + assert!(keygen_t_n_parties(2, 3).is_ok()); +} + +#[test] +fn test_keygen_t2_n4() { + assert!(keygen_t_n_parties(2, 4).is_ok()); +} + +#[test] +fn test_sign_n2_t1_ttag1() { + let _ = sign(1, 2, 2, vec![0, 1], 0, &[0]); +} + +#[test] +fn test_sign_n5_t2_ttag4() { + let _ = sign(2, 5, 4, vec![0, 2, 3, 4], 0, &[0]); +} +#[test] +fn test_sign_n8_t4_ttag6() { + let _ = sign(4, 8, 6, vec![0, 1, 2, 4, 6, 7], 0, &[0]); +} + +// party 1 is corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step5_party1() { + let res = sign(1, 2, 2, vec![0, 1], 5, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} + +// party 2 is corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step5_party2() { + let res = sign(1, 2, 2, vec![0, 1], 5, &[1]); + assert!(&res.err().unwrap().bad_actors[..] == &[1]) +} + +// both parties are corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step5_party12() { + let res = sign(1, 2, 2, vec![0, 1], 5, &[0, 1]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 1]) +} +// party 1 is corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step5_party1() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 5, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} + +// party 1,4 are corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step5_party14() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 5, &[0, 3]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 3]) +} + +// party 1 is corrupting step 6 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step6_party1() { + let res = sign(1, 2, 2, vec![0, 1], 6, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} +// party 2 is corrupting step 6 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step6_party2() { + let res = sign(1, 2, 2, vec![0, 1], 6, &[1]); + assert!(&res.err().unwrap().bad_actors[..] == &[1]) +} + +// both parties are corrupting step 6 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step6_party12() { + let res = sign(1, 2, 2, vec![0, 1], 6, &[0, 1]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 1]) +} +// party 1 is corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step6_party1() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 6, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} + +// party 1,4 are corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step6_party14() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 6, &[0, 3]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 3]) +} + +// party 1 is corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step7_party2() { + let res = sign(1, 2, 2, vec![0, 1], 7, &[1]); + assert!(&res.err().unwrap().bad_actors[..] == &[1]) +} + +// party 2,4 are corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step7_party24() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 7, &[1, 3]); + assert!(&res.err().unwrap().bad_actors[..] == &[1, 3]) +} + +fn keygen_t_n_parties( + t: u16, + n: u16, +) -> Result< + ( + Vec, + Vec, + Vec>, + Point, + VerifiableSS, + Vec, + Vec, + ), + ErrorType, +> { + let params = Parameters { + threshold: t, + share_count: n, + }; + let (t, n) = (t as usize, n as usize); + let party_keys_vec = (0..n).map(Keys::create).collect::>(); + + let (bc1_vec, decom_vec): (Vec<_>, Vec<_>) = party_keys_vec + .iter() + .map(|k| k.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2()) + .unzip(); + + let e_vec = bc1_vec + .iter() + .map(|bc1| bc1.e.clone()) + .collect::>(); + let h1_h2_N_tilde_vec = bc1_vec + .iter() + .map(|bc1| bc1.dlog_statement.clone()) + .collect::>(); + let y_vec = (0..n) + .map(|i| decom_vec[i].y_i.clone()) + .collect::>>(); + let mut y_vec_iter = y_vec.iter(); + let head = y_vec_iter.next().unwrap(); + let tail = y_vec_iter; + let y_sum = tail.fold(head.clone(), |acc, x| acc + x); + + let mut vss_scheme_vec = Vec::new(); + let mut secret_shares_vec = Vec::new(); + let mut index_vec = Vec::new(); + + // TODO: find way to propagate the error and bad actors list properly + let vss_result: Vec<_> = party_keys_vec + .iter() + .map(|k| { + k.phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + ¶ms, &decom_vec, &bc1_vec, + ) + .expect("") + }) + .collect(); + + for (vss_scheme, secret_shares, index) in vss_result { + vss_scheme_vec.push(vss_scheme); + secret_shares_vec.push(secret_shares); // cannot unzip + index_vec.push(index as u16); + } + + let vss_scheme_for_test = vss_scheme_vec.clone(); + + let party_shares = (0..n) + .map(|i| { + (0..n) + .map(|j| { + let vec_j = &secret_shares_vec[j]; + vec_j[i].clone() + }) + .collect::>>() + }) + .collect::>>>(); + + let mut shared_keys_vec = Vec::new(); + let mut dlog_proof_vec = Vec::new(); + for (i, key) in party_keys_vec.iter().enumerate() { + let res = key.phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶ms, + &y_vec, + &party_shares[i], + &vss_scheme_vec, + (&index_vec[i] + 1).into(), + ); + if res.is_err() { + return Err(res.err().unwrap()); + } + let (shared_keys, dlog_proof) = res.unwrap(); + shared_keys_vec.push(shared_keys); + dlog_proof_vec.push(dlog_proof); + } + + let pk_vec = (0..n) + .map(|i| dlog_proof_vec[i].pk.clone()) + .collect::>>(); + + let dlog_verification = Keys::verify_dlog_proofs_check_against_vss( + ¶ms, + &dlog_proof_vec, + &y_vec, + &vss_scheme_vec, + ); + + if dlog_verification.is_err() { + return Err(dlog_verification.err().unwrap()); + } + //test + let xi_vec = (0..=t) + .map(|i| shared_keys_vec[i].x_i.clone()) + .collect::>>(); + let x = vss_scheme_for_test[0] + .clone() + .reconstruct(&index_vec[0..=t], &xi_vec); + let sum_u_i = party_keys_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + &x.u_i); + assert_eq!(x, sum_u_i); + + Ok(( + party_keys_vec, // Paillier keys, keypair, N, h1, h2 + shared_keys_vec, // Private shares for this MPC keypair + pk_vec, // dlog proof for x_i + y_sum, // public key for this MPC keypair. + vss_scheme_for_test[0].clone(), // This contains the commitments for each initial share and the shares itself + e_vec, // paillier encryption keys. Why separate ? + h1_h2_N_tilde_vec, + )) +} + +fn sign( + t: u16, + n: u16, + ttag: u16, //number of participants + s: Vec, //participant list indexed from zero + corrupt_step: usize, + corrupted_parties: &[usize], +) -> Result { + // full key gen emulation + let (party_keys_vec, shared_keys_vec, pk_vec, y, vss_scheme, ek_vec, dlog_statement_vec) = + keygen_t_n_parties(t, n).unwrap(); + + // transform the t,n share to t,t+1 share. Get the public keys for the same. + let g_w_vec = SignKeys::g_w_vec(&pk_vec, &s[..], &vss_scheme); + + let private_vec = (0..shared_keys_vec.len()) + .map(|i| shared_keys_vec[i].x_i.clone()) + .collect::>(); + // make sure that we have t t); + let ttag = ttag as usize; + assert_eq!(s.len(), ttag); + + // each party creates a signing key. This happens in parallel IRL. In this test we + // create a vector of signing keys, one for each party. + // throughout i will index parties + let sign_keys_vec = (0..ttag) + .map(|i| SignKeys::create(&private_vec[s[i]], &vss_scheme, s[i], &s)) + .collect::>(); + + // each party computes [Ci,Di] = com(g^gamma_i) and broadcast the commitments + let (bc1_vec, decommit_vec1): (Vec<_>, Vec<_>) = + sign_keys_vec.iter().map(|k| k.phase1_broadcast()).unzip(); + + // each signer's dlog statement. in reality, parties prove statements + // using only other parties' h1,h2,N_tilde. here we also use own parameters for simplicity + let signers_dlog_statements = (0..ttag) + .map(|i| dlog_statement_vec[s[i]].clone()) + .collect::>(); + + // each party i BROADCASTS encryption of k_i under her Paillier key + // m_a_vec = [ma_0;ma_1;,...] + // we assume here that party sends the same encryption to all other parties. + // It should be changed to different encryption (randomness) to each counter party + let m_a_vec: Vec<_> = sign_keys_vec + .iter() + .enumerate() + .map(|(i, k)| MessageA::a(&k.k_i, &party_keys_vec[s[i]].ek, &signers_dlog_statements)) + .collect(); + + // #each party i sends responses to m_a_vec she received (one response with input gamma_i and one with w_i) + // #m_b_gamma_vec_all is a matrix where column i is a vector of message_b's that were sent to party i + + // aggregation of the n messages of all parties + let mut m_b_gamma_vec_all = Vec::new(); + let mut beta_vec_all = Vec::new(); + let mut m_b_w_vec_all = Vec::new(); + let mut ni_vec_all = Vec::new(); + let mut beta_randomness_vec_all = Vec::new(); //should be accessible in case of blame + let mut beta_tag_vec_all = Vec::new(); //should be accessible in case of blame + + // m_b_gamma and m_b_w are BROADCAST + for i in 0..ttag { + let mut m_b_gamma_vec = Vec::new(); + let mut beta_vec = Vec::new(); + let mut beta_randomness_vec = Vec::new(); + let mut beta_tag_vec = Vec::new(); + let mut m_b_w_vec = Vec::new(); + let mut ni_vec = Vec::new(); + + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let (m_b_gamma, beta_gamma, beta_randomness, beta_tag) = MessageB::b( + &sign_keys_vec[ind].gamma_i, + &ek_vec[s[i]], + m_a_vec[i].0.clone(), + &signers_dlog_statements, + ) + .expect("Alice's range proofs in MtA failed"); + let (m_b_w, beta_wi, _, _) = MessageB::b( + &sign_keys_vec[ind].w_i, + &ek_vec[s[i]], + m_a_vec[i].0.clone(), + &signers_dlog_statements, + ) + .expect("Alice's range proofs in MtA failed"); + + m_b_gamma_vec.push(m_b_gamma); + beta_vec.push(beta_gamma); + beta_randomness_vec.push(beta_randomness); + beta_tag_vec.push(beta_tag); + m_b_w_vec.push(m_b_w); + ni_vec.push(beta_wi); + } + m_b_gamma_vec_all.push(m_b_gamma_vec.clone()); + beta_vec_all.push(beta_vec.clone()); + beta_tag_vec_all.push(beta_tag_vec.clone()); + beta_randomness_vec_all.push(beta_randomness_vec.clone()); + m_b_w_vec_all.push(m_b_w_vec.clone()); + ni_vec_all.push(ni_vec.clone()); + } + + // Here we complete the MwA protocols by taking the mb matrices and starting with the first column, + // generating the appropriate message. The first column is the answers of party 1 to mb sent from other parties. + // The second column is the answers that party 2 is sending and so on. + + // TODO: simulate as IRL + let mut alpha_vec_all = Vec::new(); + let mut miu_vec_all = Vec::new(); + let mut miu_bigint_vec_all = Vec::new(); //required for the phase6 IA sub protocol + + for i in 0..ttag { + let mut alpha_vec = Vec::new(); + let mut miu_vec = Vec::new(); + let mut miu_bigint_vec = Vec::new(); //required for the phase6 IA sub protocol + + let m_b_gamma_vec_i = &m_b_gamma_vec_all[i]; + let m_b_w_vec_i = &m_b_w_vec_all[i]; + + // in case + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let m_b = m_b_gamma_vec_i[j].clone(); + + // TODO: identify these errors + let alpha_ij_gamma = m_b + .verify_proofs_get_alpha(&party_keys_vec[s[i]].dk, &sign_keys_vec[i].k_i) + .expect("wrong dlog or m_b"); + let m_b = m_b_w_vec_i[j].clone(); + let alpha_ij_wi = m_b + .verify_proofs_get_alpha(&party_keys_vec[s[i]].dk, &sign_keys_vec[i].k_i) + .expect("wrong dlog or m_b"); + + // since we actually run two MtAwc each party needs to make sure that the values B are the same as the public values + // here for b=w_i the parties already know W_i = g^w_i for each party so this check is done here. for b = gamma_i the check will be later when g^gamma_i will become public + // currently we take the W_i from the other parties signing keys + // TODO: use pk_vec (first change from x_i to w_i) for this check. + assert_eq!(m_b.b_proof.pk, sign_keys_vec[ind].g_w_i); + + alpha_vec.push(alpha_ij_gamma.0); + miu_vec.push(alpha_ij_wi.0); + miu_bigint_vec.push(alpha_ij_wi.1); + } + alpha_vec_all.push(alpha_vec.clone()); + miu_vec_all.push(miu_vec.clone()); + miu_bigint_vec_all.push(miu_bigint_vec.clone()); + } + + let mut delta_vec = Vec::new(); + let mut sigma_vec = Vec::new(); + + for i in 0..ttag { + // prepare beta_vec of party_i: + let beta_vec = (0..ttag - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + + beta_vec_all[ind1][ind2].clone() + }) + .collect::>>(); + + // prepare ni_vec of party_i: + let ni_vec = (0..ttag - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + ni_vec_all[ind1][ind2].clone() + }) + .collect::>>(); + + let mut delta = sign_keys_vec[i].phase2_delta_i(&alpha_vec_all[i], &beta_vec); + + let mut sigma = sign_keys_vec[i].phase2_sigma_i(&miu_vec_all[i], &ni_vec); + // test wrong delta corruption + if corrupt_step == 5 && corrupted_parties.iter().any(|&x| x == i) { + delta = &delta + δ + } + // test wrong sigma corruption + if corrupt_step == 6 && corrupted_parties.iter().any(|&x| x == i) { + sigma = &sigma + σ + } + delta_vec.push(delta); + sigma_vec.push(sigma); + } + + // all parties broadcast delta_i and compute delta_i ^(-1) + let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); + // all parties broadcast T_i: + let mut T_vec = Vec::new(); + let mut l_vec = Vec::new(); + let mut T_proof_vec = Vec::new(); + for i in 0..ttag { + let (T_i, l_i, T_proof_i) = SignKeys::phase3_compute_t_i(&sigma_vec[i]); + T_vec.push(T_i); + l_vec.push(l_i); + T_proof_vec.push(T_proof_i); + } + // verify T_proof_vec + for i in 0..ttag { + assert_eq!(T_vec[i], T_proof_vec[i].com.clone()); + PedersenProof::verify(&T_proof_vec[i]).expect("error T proof"); + } + // de-commit to g^gamma_i from phase1, test comm correctness, and that it is the same value used in MtA. + // Return R + + let R_vec = (0..ttag) + .map(|i| { + // each party i tests all B = g^b = g ^ gamma_i she received. + let m_b_gamma_vec = &m_b_gamma_vec_all[i]; + let b_proof_vec = (0..ttag - 1) + .map(|j| &m_b_gamma_vec[j].b_proof) + .collect::>>(); + SignKeys::phase4(&delta_inv, &b_proof_vec, decommit_vec1.clone(), &bc1_vec, i) + .expect("") //TODO: propagate the error + }) + .collect::>>(); + + //new phase 5 + // all parties broadcast R_dash = k_i * R. + let R_dash_vec = (0..ttag) + .map(|i| &R_vec[i] * &sign_keys_vec[i].k_i) + .collect::>>(); + + // each party sends first message to all other parties + let mut phase5_proofs_vec: Vec> = vec![Vec::new(); ttag]; + for i in 0..ttag { + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let proof = LocalSignature::phase5_proof_pdl( + &R_dash_vec[i], + &R_vec[i], + &m_a_vec[i].0.c, + &ek_vec[s[i]], + &sign_keys_vec[i].k_i, + &m_a_vec[i].1, + &dlog_statement_vec[s[ind]], + ); + + phase5_proofs_vec[i].push(proof); + } + } + + for i in 0..ttag { + let phase5_verify_zk = LocalSignature::phase5_verify_pdl( + &phase5_proofs_vec[i], + &R_dash_vec[i], + &R_vec[i], + &m_a_vec[i].0.c, + &ek_vec[s[i]], + &dlog_statement_vec[..], + &s, + i, + ); + if phase5_verify_zk.is_err() { + return Err(phase5_verify_zk.err().unwrap()); + } + } + + //each party must run the test + let phase5_check = LocalSignature::phase5_check_R_dash_sum(&R_dash_vec); + if phase5_check.is_err() { + // initiate phase 5 blame protocol to learn which parties acted maliciously. + // each party generates local state and share with other parties. + // assuming sync communication - if a message was failed to arrive from a party - + // this party should automatically be blamed + let mut local_state_vec = Vec::new(); + for i in 0..ttag { + // compose beta tag vector: + let mut beta_tag_vec_to_test = Vec::new(); + let mut beta_randomness_vec_to_test = Vec::new(); + for j in 0..ttag - 1 { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + beta_tag_vec_to_test.push(beta_tag_vec_all[ind1][ind2].clone()); + beta_randomness_vec_to_test.push(beta_randomness_vec_all[ind1][ind2].clone()); + } + + let local_state = LocalStatePhase5 { + k: sign_keys_vec[i].k_i.clone(), + k_randomness: m_a_vec[i].1.clone(), + gamma: sign_keys_vec[i].gamma_i.clone(), + beta_randomness: beta_randomness_vec_to_test, + beta_tag: beta_tag_vec_to_test, + encryption_key: ek_vec[s[i]].clone(), + }; + local_state_vec.push(local_state); + } + //g_gamma_vec: + let g_gamma_vec = (0..decommit_vec1.len()) + .map(|i| decommit_vec1[i].g_gamma_i.clone()) + .collect::>>(); + //m_a_vec + let m_a_vec = (0..m_a_vec.len()) + .map(|i| m_a_vec[i].0.clone()) + .collect::>(); + + // reduce ek vec to only ek of participants : + let ek_vec = (0..ttag) + .map(|k| ek_vec[s[k]].clone()) + .collect::>(); + let global_state = GlobalStatePhase5::local_state_to_global_state( + &ek_vec[..], + &delta_vec, + &g_gamma_vec[..], + &m_a_vec[..], + m_b_gamma_vec_all, + &local_state_vec[..], + ); + global_state.phase5_blame()?; + } + + let mut S_vec = Vec::new(); + let mut homo_elgamal_proof_vec = Vec::new(); + for i in 0..ttag { + let (S_i, homo_elgamal_proof) = LocalSignature::phase6_compute_S_i_and_proof_of_consistency( + &R_vec[i], + &T_vec[i], + &sigma_vec[i], + &l_vec[i], + ); + S_vec.push(S_i); + homo_elgamal_proof_vec.push(homo_elgamal_proof); + } + + LocalSignature::phase6_verify_proof(&S_vec, &homo_elgamal_proof_vec, &R_vec, &T_vec)?; + + let phase6_check = LocalSignature::phase6_check_S_i_sum(&y, &S_vec); + if phase6_check.is_err() { + // initiate phase 6 blame protocol to learn which parties acted maliciously. + // each party generates local state and share with other parties. + // assuming sync communication - if a message was failed to arrive from a party - + // this party should automatically be blamed + + let mut local_state_vec = Vec::new(); + for i in 0..ttag { + let mut miu_randomness_vec = Vec::new(); + for j in 0..ttag - 1 { + let rand = GlobalStatePhase6::extract_paillier_randomness( + &m_b_w_vec_all[i][j].c, + &party_keys_vec[s[i]].dk, + ); + miu_randomness_vec.push(rand); + } + let proof = GlobalStatePhase6::ecddh_proof(&sigma_vec[i], &R_vec[i], &S_vec[i]); + let local_state = LocalStatePhase6 { + k: sign_keys_vec[i].k_i.clone(), + k_randomness: m_a_vec[i].1.clone(), + miu: miu_bigint_vec_all[i].clone(), + miu_randomness: miu_randomness_vec, + proof_of_eq_dlog: proof, + }; + local_state_vec.push(local_state); + } + + //m_a_vec + let m_a_vec = (0..m_a_vec.len()) + .map(|i| m_a_vec[i].0.clone()) + .collect::>(); + + // reduce ek vec to only ek of participants : + let ek_vec = (0..ttag) + .map(|k| ek_vec[s[k]].clone()) + .collect::>(); + + let global_state = GlobalStatePhase6::local_state_to_global_state( + &ek_vec[..], + &S_vec[..], + &g_w_vec[..], + &m_a_vec[..], + m_b_w_vec_all, + &local_state_vec[..], + ); + global_state.phase6_blame(&R_vec[0])?; + } + + let message: [u8; 4] = [79, 77, 69, 82]; + let message_bn = Sha256::new() + .chain_bigint(&BigInt::from_bytes(&message[..])) + .result_bigint(); + let mut local_sig_vec = Vec::new(); + let mut s_vec = Vec::new(); + // each party computes s_i + for i in 0..ttag { + let local_sig = LocalSignature::phase7_local_sig( + &sign_keys_vec[i].k_i, + &message_bn, + &R_vec[i], + &sigma_vec[i], + &y, + ); + s_vec.push(local_sig.s_i.clone()); + local_sig_vec.push(local_sig); + } + + // test corrupted local s + if corrupt_step == 7 { + for i in 0..s_vec.len() { + if corrupted_parties.iter().any(|&x| x == i) { + s_vec[i] = &s_vec[i] + &s_vec[i]; + } + } + } + + let sig = local_sig_vec[0].output_signature(&s_vec[1..]); + + // test + assert_eq!(local_sig_vec[0].y, y); + //error in phase 7: + if sig.is_err() { + let global_state = GlobalStatePhase7 { + s_vec, + r: local_sig_vec[0].r.clone(), + R_dash_vec, + m: local_sig_vec[0].m.clone(), + R: local_sig_vec[0].R.clone(), + S_vec, + }; + global_state.phase7_blame()?; + } + //for testing purposes: checking with a second verifier: + + let sig = sig.unwrap(); + check_sig(&sig.r, &sig.s, &local_sig_vec[0].m, &y); + Ok(sig) +} + +fn check_sig(r: &Scalar, s: &Scalar, msg: &BigInt, pk: &Point) { + use secp256k1::{Message, PublicKey, Signature, SECP256K1}; + + let raw_msg = BigInt::to_bytes(msg); + let mut msg: Vec = Vec::new(); // padding + msg.extend(vec![0u8; 32 - raw_msg.len()]); + msg.extend(raw_msg.iter()); + + let msg = Message::from_slice(msg.as_slice()).unwrap(); + let slice = pk.to_bytes(false); + let mut raw_pk = Vec::new(); + if slice.len() != 65 { + // after curv's pk_to_key_slice return 65 bytes, this can be removed + raw_pk.insert(0, 4u8); + raw_pk.extend(vec![0u8; 64 - slice.len()]); + raw_pk.extend(slice.as_ref()); + } else { + raw_pk.extend(slice.as_ref()); + } + + assert_eq!(raw_pk.len(), 65); + + let pk = PublicKey::from_slice(&raw_pk).unwrap(); + + let mut compact: Vec = Vec::new(); + let bytes_r = &r.to_bytes()[..]; + compact.extend(vec![0u8; 32 - bytes_r.len()]); + compact.extend(bytes_r.iter()); + + let bytes_s = &s.to_bytes()[..]; + compact.extend(vec![0u8; 32 - bytes_s.len()]); + compact.extend(bytes_s.iter()); + + let secp_sig = Signature::from_compact(compact.as_slice()).unwrap(); + + let is_correct = SECP256K1.verify(&msg, &secp_sig, &pk).is_ok(); + assert!(is_correct); +} +#[test] +fn test_serialize_deserialize() { + use serde_json; + + let k = Keys::create(0); + let (commit, decommit) = k.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2(); + + let encoded = serde_json::to_string(&commit).unwrap(); + let decoded: KeyGenBroadcastMessage1 = serde_json::from_str(&encoded).unwrap(); + assert_eq!(commit.com, decoded.com); + + let encoded = serde_json::to_string(&decommit).unwrap(); + let decoded: KeyGenDecommitMessage1 = serde_json::from_str(&encoded).unwrap(); + assert_eq!(decommit.y_i, decoded.y_i); +} +#[test] +fn test_small_paillier() { + // parties shouldn't be able to choose small Paillier modulus + let mut k = Keys::create(0); + // creating 2046-bit Paillier + let (ek, dk) = Paillier::keypair_with_modulus_size(2046).keys(); + k.dk = dk; + k.ek = ek; + let (commit, decommit) = k.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2(); + assert!(k + .phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + &Parameters { + threshold: 0, + share_count: 1 + }, + &[decommit], + &[commit], + ) + .is_err()); +} diff --git a/src/protocols/multi_party_ecdsa/mod.rs b/src/protocols/multi_party_ecdsa/mod.rs new file mode 100644 index 0000000..43ded3a --- /dev/null +++ b/src/protocols/multi_party_ecdsa/mod.rs @@ -0,0 +1,18 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +pub mod gg_2018; +pub mod gg_2020; diff --git a/src/protocols/two_party_ecdsa/cclst_2019/mod.rs b/src/protocols/two_party_ecdsa/cclst_2019/mod.rs new file mode 100644 index 0000000..001cd97 --- /dev/null +++ b/src/protocols/two_party_ecdsa/cclst_2019/mod.rs @@ -0,0 +1,23 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +const SECURITY_BITS: usize = 256; + +pub mod party_one; +pub mod party_two; + +#[cfg(test)] +mod test; diff --git a/src/protocols/two_party_ecdsa/cclst_2019/party_one.rs b/src/protocols/two_party_ecdsa/cclst_2019/party_one.rs new file mode 100644 index 0000000..7ee1d57 --- /dev/null +++ b/src/protocols/two_party_ecdsa/cclst_2019/party_one.rs @@ -0,0 +1,419 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ +use std::cmp; + +use class_group::primitives::cl_dl_public_setup::{ + decrypt, verifiably_encrypt, CLDLProof, CLGroup, Ciphertext as CLCiphertext, PK, SK, +}; + +use curv::arithmetic::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_dlog::*; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::*; +use curv::cryptographic_primitives::proofs::ProofError; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use subtle::ConstantTimeEq; + +use super::party_two::EphKeyGenFirstMsg as Party2EphKeyGenFirstMessage; +use super::party_two::EphKeyGenSecondMsg as Party2EphKeyGenSecondMessage; +use super::SECURITY_BITS; +use crate::Error::{self, InvalidSig}; + +//****************** Begin: Party One structs ******************// +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CommWitness { + pub pk_commitment_blind_factor: BigInt, + pub zk_pok_blind_factor: BigInt, + pub public_share: Point, + pub d_log_proof: DLogProof, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenFirstMsg { + pub pk_commitment: BigInt, + pub zk_pok_commitment: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyGenSecondMsg { + pub comm_witness: CommWitness, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HSMCL { + pub public: PK, + pub secret: SK, + pub encrypted_share: CLCiphertext, + pub cl_group: CLGroup, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HSMCLPublic { + pub cl_pub_key: PK, + pub proof: CLDLProof, + pub encrypted_share: CLCiphertext, + pub cl_group: CLGroup, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SignatureRecid { + pub s: BigInt, + pub r: BigInt, + pub recid: u8, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Signature { + pub s: BigInt, + pub r: BigInt, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Party1Private { + x1: Scalar, + hsmcl_pub: PK, + hsmcl_priv: SK, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLFirstMessage { + pub c_hat: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLdecommit { + pub q_hat: Point, + pub blindness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLSecondMessage { + pub decommit: PDLdecommit, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphEcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EphKeyGenFirstMsg { + pub d_log_proof: ECDDHProof, + pub public_share: Point, + pub c: Point, //c = secret_share * base_point2 +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EphKeyGenSecondMsg {} + +//****************** End: Party One structs ******************// + +impl KeyGenFirstMsg { + pub fn create_commitments() -> (KeyGenFirstMsg, CommWitness, EcKeyPair) { + let base = Point::generator(); + + let secret_share = Scalar::::random(); + //in Lindell's protocol range proof works only for x1 = + Scalar::::from(&secret_share.to_bigint().div_floor(&BigInt::from(3))); + + let public_share = base * &secret_share; + + let d_log_proof = DLogProof::prove(&secret_share); + // we use hash based commitment + let pk_commitment_blind_factor = BigInt::sample(SECURITY_BITS); + let pk_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(public_share.to_bytes(true).as_ref()), + &pk_commitment_blind_factor, + ); + + let zk_pok_blind_factor = BigInt::sample(SECURITY_BITS); + let zk_pok_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(d_log_proof.pk_t_rand_commitment.to_bytes(true).as_ref()), + &zk_pok_blind_factor, + ); + let ec_key_pair = EcKeyPair { + public_share, + secret_share, + }; + ( + KeyGenFirstMsg { + pk_commitment, + zk_pok_commitment, + }, + CommWitness { + pk_commitment_blind_factor, + zk_pok_blind_factor, + public_share: ec_key_pair.public_share.clone(), + d_log_proof, + }, + ec_key_pair, + ) + } + + pub fn create_commitments_with_fixed_secret_share( + secret_share: Scalar, + ) -> (KeyGenFirstMsg, CommWitness, EcKeyPair) { + let base = Point::generator(); + let public_share = base * &secret_share; + + let d_log_proof = DLogProof::prove(&secret_share); + + let pk_commitment_blind_factor = BigInt::sample(SECURITY_BITS); + let pk_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(public_share.to_bytes(true).as_ref()), + &pk_commitment_blind_factor, + ); + + let zk_pok_blind_factor = BigInt::sample(SECURITY_BITS); + let zk_pok_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(d_log_proof.pk_t_rand_commitment.to_bytes(true).as_ref()), + &zk_pok_blind_factor, + ); + + let ec_key_pair = EcKeyPair { + public_share, + secret_share, + }; + ( + KeyGenFirstMsg { + pk_commitment, + zk_pok_commitment, + }, + CommWitness { + pk_commitment_blind_factor, + zk_pok_blind_factor, + public_share: ec_key_pair.public_share.clone(), + d_log_proof, + }, + ec_key_pair, + ) + } +} + +impl KeyGenSecondMsg { + pub fn verify_and_decommit( + comm_witness: CommWitness, + proof: &DLogProof, + ) -> Result { + DLogProof::verify(proof)?; + Ok(KeyGenSecondMsg { comm_witness }) + } +} + +pub fn compute_pubkey( + party_one_private: &Party1Private, + other_share_public_share: &Point, +) -> Point { + other_share_public_share * &party_one_private.x1 +} + +impl Party1Private { + pub fn set_private_key(ec_key: &EcKeyPair, hsmcl: &HSMCL) -> Party1Private { + Party1Private { + x1: ec_key.secret_share.clone(), + hsmcl_pub: hsmcl.public.clone(), + hsmcl_priv: hsmcl.secret.clone(), + } + } +} + +impl HSMCL { + pub fn generate_keypair_and_encrypted_share_and_proof( + keygen: &EcKeyPair, + seed: &BigInt, + ) -> (HSMCL, HSMCLPublic) { + let cl_group = CLGroup::new_from_setup(&1348, seed); + let (secret_key, public_key) = cl_group.keygen(); + let (ciphertext, proof) = verifiably_encrypt( + &cl_group, + &public_key, + (&keygen.secret_share, &keygen.public_share), + ); + + ( + HSMCL { + cl_group: cl_group.clone(), + public: public_key.clone(), + secret: secret_key, + encrypted_share: ciphertext.clone(), + }, + HSMCLPublic { + cl_pub_key: public_key, + proof, + encrypted_share: ciphertext, + cl_group, + }, + ) + } +} + +impl EphKeyGenFirstMsg { + pub fn create() -> (EphKeyGenFirstMsg, EphEcKeyPair) { + let base = Point::generator(); + let secret_share = Scalar::::random(); + let public_share = base * &secret_share; + let h = Point::::base_point2(); + let w = ECDDHWitness { + x: secret_share.clone(), + }; + let c = h * &secret_share; + let delta = ECDDHStatement { + g1: base.to_point(), + h1: public_share.clone(), + g2: h.clone(), + h2: c.clone(), + }; + let d_log_proof = ECDDHProof::prove(&w, &delta); + let ec_key_pair = EphEcKeyPair { + public_share: public_share.clone(), + secret_share, + }; + ( + EphKeyGenFirstMsg { + d_log_proof, + public_share, + c, + }, + ec_key_pair, + ) + } +} + +impl EphKeyGenSecondMsg { + pub fn verify_commitments_and_dlog_proof( + party_two_first_message: &Party2EphKeyGenFirstMessage, + party_two_second_message: &Party2EphKeyGenSecondMessage, + ) -> Result { + let party_two_pk_commitment = &party_two_first_message.pk_commitment; + let party_two_zk_pok_commitment = &party_two_first_message.zk_pok_commitment; + let party_two_zk_pok_blind_factor = + &party_two_second_message.comm_witness.zk_pok_blind_factor; + let party_two_public_share = &party_two_second_message.comm_witness.public_share; + let party_two_pk_commitment_blind_factor = &party_two_second_message + .comm_witness + .pk_commitment_blind_factor; + let party_two_d_log_proof = &party_two_second_message.comm_witness.d_log_proof; + let mut flag = true; + if party_two_pk_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(party_two_public_share.to_bytes(true).as_ref()), + party_two_pk_commitment_blind_factor, + ) + { + flag = false + } + if party_two_zk_pok_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &Sha256::new() + .chain_points([&party_two_d_log_proof.a1, &party_two_d_log_proof.a2]) + .result_bigint(), + party_two_zk_pok_blind_factor, + ) + { + flag = false + } + if !flag { + return Err(ProofError); + } + let delta = ECDDHStatement { + g1: Point::generator().to_point(), + h1: party_two_public_share.clone(), + g2: Point::::base_point2().clone(), + h2: party_two_second_message.comm_witness.c.clone(), + }; + party_two_d_log_proof.verify(&delta)?; + Ok(EphKeyGenSecondMsg {}) + } +} + +impl Signature { + pub fn compute( + hsmcl: &HSMCL, + party_one_private: &Party1Private, + partial_sig_c3: CLCiphertext, + ephemeral_local_share: &EphEcKeyPair, + ephemeral_other_public_share: &Point, + ) -> Signature { + //compute r = k2* R1 + let r = ephemeral_other_public_share * &ephemeral_local_share.secret_share; + + let rx = r + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + let k1 = &ephemeral_local_share.secret_share.to_bigint(); + let k1_inv = BigInt::mod_inv(k1, Scalar::::group_order()).unwrap(); + let s_tag = decrypt( + &hsmcl.cl_group, + &party_one_private.hsmcl_priv, + &partial_sig_c3, + ); + let s_tag_tag = BigInt::mod_mul( + &k1_inv, + &s_tag.to_bigint(), + Scalar::::group_order(), + ); + let s = cmp::min( + s_tag_tag.clone(), + Scalar::::group_order().clone() - s_tag_tag, + ); + Signature { s, r: rx } + } +} + +pub fn verify( + signature: &Signature, + pubkey: &Point, + message: &BigInt, +) -> Result<(), Error> { + let s_fe = Scalar::::from(&signature.s); + let rx_fe = Scalar::::from(&signature.r); + + let s_inv_fe = s_fe.invert().ok_or(Error::InvalidSig)?; + let e_fe: Scalar = + Scalar::::from(&message.mod_floor(Scalar::::group_order())); + let u1 = Point::generator() * e_fe * &s_inv_fe; + let u2 = pubkey * rx_fe * &s_inv_fe; + + // second condition is against malleability + let rx_bytes = &BigInt::to_bytes(&signature.r)[..]; + let u1_plus_u2_bytes = &BigInt::to_bytes(&(u1 + u2).x_coord().unwrap())[..]; + + if rx_bytes.ct_eq(u1_plus_u2_bytes).unwrap_u8() == 1 + && signature.s < Scalar::::group_order() - signature.s.clone() + { + Ok(()) + } else { + Err(InvalidSig) + } +} diff --git a/src/protocols/two_party_ecdsa/cclst_2019/party_two.rs b/src/protocols/two_party_ecdsa/cclst_2019/party_two.rs new file mode 100644 index 0000000..6e80e70 --- /dev/null +++ b/src/protocols/two_party_ecdsa/cclst_2019/party_two.rs @@ -0,0 +1,356 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ +use super::party_one::HSMCLPublic; +use class_group::primitives::cl_dl_public_setup::PK as HSMCLPK; +use class_group::primitives::cl_dl_public_setup::{ + encrypt, eval_scal, eval_sum, CLGroup, Ciphertext as CLCiphertext, +}; +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_dlog::*; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::*; +use curv::cryptographic_primitives::proofs::ProofError; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +use super::party_one::EphKeyGenFirstMsg as Party1EphKeyGenFirstMsg; +use super::party_one::KeyGenFirstMsg as Party1KeyGenFirstMessage; +use super::party_one::KeyGenSecondMsg as Party1KeyGenSecondMessage; +use super::SECURITY_BITS; + +//****************** Begin: Party Two structs ******************// + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenFirstMsg { + pub d_log_proof: DLogProof, + pub public_share: Point, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyGenSecondMsg {} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Party2Public { + pub group: CLGroup, + pub ek: HSMCLPK, + pub encrypted_secret_share: CLCiphertext, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PartialSig { + pub c3: CLCiphertext, +} + +#[derive(Serialize, Deserialize)] +pub struct Party2Private { + x2: Scalar, +} +#[allow(dead_code)] +pub struct PDLchallenge { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, + a: BigInt, + b: BigInt, + blindness: BigInt, + q_tag: Point, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLFirstMessage { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLdecommit { + pub a: BigInt, + pub b: BigInt, + pub blindness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLSecondMessage { + pub decommit: PDLdecommit, +} +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphEcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphCommWitness { + pub pk_commitment_blind_factor: BigInt, + pub zk_pok_blind_factor: BigInt, + pub public_share: Point, + pub d_log_proof: ECDDHProof, + pub c: Point, //c = secret_share * base_point2 +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphKeyGenFirstMsg { + pub pk_commitment: BigInt, + pub zk_pok_commitment: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EphKeyGenSecondMsg { + pub comm_witness: EphCommWitness, +} + +//****************** End: Party Two structs ******************// + +impl KeyGenFirstMsg { + pub fn create() -> (KeyGenFirstMsg, EcKeyPair) { + let base = Point::generator(); + let secret_share = Scalar::::random(); + let public_share = base * &secret_share; + let d_log_proof = DLogProof::prove(&secret_share); + let ec_key_pair = EcKeyPair { + public_share: public_share.clone(), + secret_share, + }; + ( + KeyGenFirstMsg { + d_log_proof, + public_share, + }, + ec_key_pair, + ) + } + + pub fn create_with_fixed_secret_share( + secret_share: Scalar, + ) -> (KeyGenFirstMsg, EcKeyPair) { + let base = Point::generator(); + let public_share = base * &secret_share; + let d_log_proof = DLogProof::prove(&secret_share); + let ec_key_pair = EcKeyPair { + public_share: public_share.clone(), + secret_share, + }; + ( + KeyGenFirstMsg { + d_log_proof, + public_share, + }, + ec_key_pair, + ) + } +} + +impl KeyGenSecondMsg { + pub fn verify_commitments_and_dlog_proof( + party_one_first_message: &Party1KeyGenFirstMessage, + party_one_second_message: &Party1KeyGenSecondMessage, + ) -> Result { + let party_one_pk_commitment = &party_one_first_message.pk_commitment; + let party_one_zk_pok_commitment = &party_one_first_message.zk_pok_commitment; + let party_one_zk_pok_blind_factor = + &party_one_second_message.comm_witness.zk_pok_blind_factor; + let party_one_public_share = &party_one_second_message.comm_witness.public_share; + let party_one_pk_commitment_blind_factor = &party_one_second_message + .comm_witness + .pk_commitment_blind_factor; + let party_one_d_log_proof = &party_one_second_message.comm_witness.d_log_proof; + + let mut flag = true; + if party_one_pk_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(party_one_public_share.to_bytes(true).as_ref()), + party_one_pk_commitment_blind_factor, + ) + { + flag = false + } + if party_one_zk_pok_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes( + party_one_d_log_proof + .pk_t_rand_commitment + .to_bytes(true) + .as_ref(), + ), + party_one_zk_pok_blind_factor, + ) + { + flag = false + } + + if !flag { + return Err(ProofError); + } + + DLogProof::verify(party_one_d_log_proof)?; + Ok(KeyGenSecondMsg {}) + } +} + +pub fn compute_pubkey( + local_share: &EcKeyPair, + other_share_public_share: &Point, +) -> Point { + other_share_public_share * &local_share.secret_share +} + +impl Party2Private { + pub fn set_private_key(ec_key: &EcKeyPair) -> Party2Private { + Party2Private { + x2: ec_key.secret_share.clone(), + } + } +} + +impl Party2Public { + pub fn verify_setup_and_zkcldl_proof( + hsmcl_public: &HSMCLPublic, + seed: &BigInt, + party1_ec_pubkey: &Point, + ) -> Result { + let setup_verify = hsmcl_public.cl_group.setup_verify(seed); + + let proof_verify = hsmcl_public.proof.verify( + &hsmcl_public.cl_group, + &hsmcl_public.cl_pub_key, + &hsmcl_public.encrypted_share, + party1_ec_pubkey, + ); + if proof_verify.is_ok() && setup_verify.is_ok() { + Ok(Party2Public { + group: hsmcl_public.cl_group.clone(), + ek: hsmcl_public.cl_pub_key.clone(), + encrypted_secret_share: hsmcl_public.encrypted_share.clone(), + }) + } else { + Err(ProofError) + } + } +} + +impl EphKeyGenFirstMsg { + pub fn create_commitments() -> (EphKeyGenFirstMsg, EphCommWitness, EphEcKeyPair) { + let base = Point::generator(); + + let secret_share = Scalar::::random(); + + let public_share = base * &secret_share; + + let h = Point::base_point2(); + let w = ECDDHWitness { + x: secret_share.clone(), + }; + let c = h * &secret_share; + let delta = ECDDHStatement { + g1: base.to_point(), + h1: public_share.clone(), + g2: h.clone(), + h2: c.clone(), + }; + let d_log_proof = ECDDHProof::prove(&w, &delta); + + // we use hash based commitment + let pk_commitment_blind_factor = BigInt::sample(SECURITY_BITS); + let pk_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(public_share.to_bytes(true).as_ref()), + &pk_commitment_blind_factor, + ); + + let zk_pok_blind_factor = BigInt::sample(SECURITY_BITS); + let zk_pok_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &Sha256::new() + .chain_points([&d_log_proof.a1, &d_log_proof.a2]) + .result_bigint(), + &zk_pok_blind_factor, + ); + + let ec_key_pair = EphEcKeyPair { + public_share, + secret_share, + }; + ( + EphKeyGenFirstMsg { + pk_commitment, + zk_pok_commitment, + }, + EphCommWitness { + pk_commitment_blind_factor, + zk_pok_blind_factor, + public_share: ec_key_pair.public_share.clone(), + d_log_proof, + c, + }, + ec_key_pair, + ) + } +} + +impl EphKeyGenSecondMsg { + pub fn verify_and_decommit( + comm_witness: EphCommWitness, + party_one_first_message: &Party1EphKeyGenFirstMsg, + ) -> Result { + let delta = ECDDHStatement { + g1: Point::generator().to_point(), + h1: party_one_first_message.public_share.clone(), + g2: Point::::base_point2().clone(), + h2: party_one_first_message.c.clone(), + }; + party_one_first_message.d_log_proof.verify(&delta)?; + Ok(EphKeyGenSecondMsg { comm_witness }) + } +} + +impl PartialSig { + pub fn compute( + party_two_public: Party2Public, + local_share: &Party2Private, + ephemeral_local_share: &EphEcKeyPair, + ephemeral_other_public_share: &Point, + message: &BigInt, + ) -> PartialSig { + let q = Scalar::::group_order(); + //compute r = k2* R1 + let r: Point = + ephemeral_other_public_share * &ephemeral_local_share.secret_share; + + let rx = r.x_coord().unwrap().mod_floor(q); + let k2 = &ephemeral_local_share.secret_share.to_bigint(); + let k2_inv = BigInt::mod_inv(k2, q).unwrap(); + let k2_inv_m = BigInt::mod_mul(&k2_inv, message, q); + let k2_inv_m_fe = Scalar::::from(&k2_inv_m); + let c1 = encrypt(&party_two_public.group, &party_two_public.ek, &k2_inv_m_fe); + let v = BigInt::mod_mul(&k2_inv, &local_share.x2.to_bigint(), q); + let v = BigInt::mod_mul(&v, &rx, q); + + let c2 = eval_scal(&party_two_public.encrypted_secret_share, &v); + let c3 = eval_sum(&c1.0, &c2); + + //c3: + PartialSig { c3 } + } +} diff --git a/src/protocols/two_party_ecdsa/cclst_2019/test.rs b/src/protocols/two_party_ecdsa/cclst_2019/test.rs new file mode 100644 index 0000000..87aad0d --- /dev/null +++ b/src/protocols/two_party_ecdsa/cclst_2019/test.rs @@ -0,0 +1,142 @@ +// For integration tests, please add your tests in /tests instead + +use super::*; +use curv::arithmetic::Converter; +use curv::elliptic::curves::*; +use curv::BigInt; + +#[test] +fn test_d_log_proof_party_two_party_one() { + let (party_one_first_message, comm_witness, _ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments(); + let (party_two_first_message, _ec_key_pair_party2) = party_two::KeyGenFirstMsg::create(); + let party_one_second_message = party_one::KeyGenSecondMsg::verify_and_decommit( + comm_witness, + &party_two_first_message.d_log_proof, + ) + .expect("failed to verify and decommit"); + + let _party_two_second_message = party_two::KeyGenSecondMsg::verify_commitments_and_dlog_proof( + &party_one_first_message, + &party_one_second_message, + ) + .expect("failed to verify commitments and DLog proof"); +} + +#[test] +fn test_full_key_gen() { + let (party_one_first_message, comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments_with_fixed_secret_share( + Scalar::::random(), + ); + let (party_two_first_message, _ec_key_pair_party2) = + party_two::KeyGenFirstMsg::create_with_fixed_secret_share(Scalar::::from( + &BigInt::from(10), + )); + let party_one_second_message = party_one::KeyGenSecondMsg::verify_and_decommit( + comm_witness, + &party_two_first_message.d_log_proof, + ) + .expect("failed to verify and decommit"); + + let _party_two_second_message = party_two::KeyGenSecondMsg::verify_commitments_and_dlog_proof( + &party_one_first_message, + &party_one_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + + // init HSMCL keypair: + let seed: BigInt = BigInt::from_str_radix( + "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848", + 10, + ).unwrap(); + let (hsmcl, hsmcl_public) = party_one::HSMCL::generate_keypair_and_encrypted_share_and_proof( + &ec_key_pair_party1, + &seed, + ); + + //P1 sends P2 hsmcl_public + let _party_one_private = party_one::Party1Private::set_private_key(&ec_key_pair_party1, &hsmcl); + + let _party_two_hsmcl_pub = party_two::Party2Public::verify_setup_and_zkcldl_proof( + &hsmcl_public, + &seed, + &party_one_second_message.comm_witness.public_share, + ) + .expect("proof error"); +} + +#[test] +fn test_two_party_sign() { + ////////// Simulate KeyGen ///////////////// + // assume party1 and party2 engaged with KeyGen in the past resulting in + // party1 owning private share and HSMCL key-pair + // party2 owning private share and HSMCL encryption of party1 share + let (_party_one_private_share_gen, comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments(); + let (party_two_private_share_gen, ec_key_pair_party2) = party_two::KeyGenFirstMsg::create(); + + //pi (nothing up my sleeve) + let seed: BigInt = BigInt::from_str_radix( + "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848", + 10, + ).unwrap(); + + let (party_one_hsmcl, hsmcl_public) = + party_one::HSMCL::generate_keypair_and_encrypted_share_and_proof( + &ec_key_pair_party1, + &seed, + ); + + let party1_private = + party_one::Party1Private::set_private_key(&ec_key_pair_party1, &party_one_hsmcl); + + let party_two_hsmcl_pub = party_two::Party2Public::verify_setup_and_zkcldl_proof( + &hsmcl_public, + &seed, + &comm_witness.public_share, + ) + .expect("proof error"); + + ////////// Start Signing ///////////////// + // creating the ephemeral private shares: + + let (eph_party_two_first_message, eph_comm_witness, eph_ec_key_pair_party2) = + party_two::EphKeyGenFirstMsg::create_commitments(); + let (eph_party_one_first_message, eph_ec_key_pair_party1) = + party_one::EphKeyGenFirstMsg::create(); + let eph_party_two_second_message = party_two::EphKeyGenSecondMsg::verify_and_decommit( + eph_comm_witness, + &eph_party_one_first_message, + ) + .expect("party1 DLog proof failed"); + + let _eph_party_one_second_message = + party_one::EphKeyGenSecondMsg::verify_commitments_and_dlog_proof( + &eph_party_two_first_message, + &eph_party_two_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + let party2_private = party_two::Party2Private::set_private_key(&ec_key_pair_party2); + let message = BigInt::from(1234); + + let partial_sig = party_two::PartialSig::compute( + party_two_hsmcl_pub, + &party2_private, + &eph_ec_key_pair_party2, + &eph_party_one_first_message.public_share, + &message, + ); + + let signature = party_one::Signature::compute( + &party_one_hsmcl, + &party1_private, + partial_sig.c3, + &eph_ec_key_pair_party1, + &eph_party_two_second_message.comm_witness.public_share, + ); + + let pubkey = + party_one::compute_pubkey(&party1_private, &party_two_private_share_gen.public_share); + party_one::verify(&signature, &pubkey, &message).expect("Invalid signature") +} diff --git a/src/protocols/two_party_ecdsa/lindell_2017/mod.rs b/src/protocols/two_party_ecdsa/lindell_2017/mod.rs new file mode 100644 index 0000000..001cd97 --- /dev/null +++ b/src/protocols/two_party_ecdsa/lindell_2017/mod.rs @@ -0,0 +1,23 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +const SECURITY_BITS: usize = 256; + +pub mod party_one; +pub mod party_two; + +#[cfg(test)] +mod test; diff --git a/src/protocols/two_party_ecdsa/lindell_2017/party_one.rs b/src/protocols/two_party_ecdsa/lindell_2017/party_one.rs new file mode 100644 index 0000000..072d32f --- /dev/null +++ b/src/protocols/two_party_ecdsa/lindell_2017/party_one.rs @@ -0,0 +1,607 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ +use std::cmp; + +use centipede::juggling::proof_system::{Helgamalsegmented, Witness}; +use centipede::juggling::segmentation::Msegmentation; +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_dlog::*; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::*; +use curv::cryptographic_primitives::proofs::ProofError; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::Paillier; +use paillier::{Decrypt, EncryptWithChosenRandomness, KeyGeneration}; +use paillier::{DecryptionKey, EncryptionKey, Randomness, RawCiphertext, RawPlaintext}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use subtle::ConstantTimeEq; +use zk_paillier::zkproofs::NiCorrectKeyProof; + +use super::party_two::EphKeyGenFirstMsg as Party2EphKeyGenFirstMessage; +use super::party_two::EphKeyGenSecondMsg as Party2EphKeyGenSecondMessage; +use super::SECURITY_BITS; + +use crate::utilities::mta::MessageB; +use crate::Error; + +use crate::utilities::zk_pdl_with_slack::PDLwSlackProof; +use crate::utilities::zk_pdl_with_slack::PDLwSlackStatement; +use crate::utilities::zk_pdl_with_slack::PDLwSlackWitness; +use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + +//****************** Begin: Party One structs ******************// +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CommWitness { + pub pk_commitment_blind_factor: BigInt, + pub zk_pok_blind_factor: BigInt, + pub public_share: Point, + pub d_log_proof: DLogProof, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenFirstMsg { + pub pk_commitment: BigInt, + pub zk_pok_commitment: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyGenSecondMsg { + pub comm_witness: CommWitness, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PaillierKeyPair { + pub ek: EncryptionKey, + dk: DecryptionKey, + pub encrypted_share: BigInt, + randomness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SignatureRecid { + pub s: BigInt, + pub r: BigInt, + pub recid: u8, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Signature { + pub s: BigInt, + pub r: BigInt, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Party1Private { + x1: Scalar, + paillier_priv: DecryptionKey, + c_key_randomness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLFirstMessage { + pub c_hat: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLdecommit { + pub q_hat: Point, + pub blindness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLSecondMessage { + pub decommit: PDLdecommit, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphEcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EphKeyGenFirstMsg { + pub d_log_proof: ECDDHProof, + pub public_share: Point, + pub c: Point, //c = secret_share * base_point2 +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EphKeyGenSecondMsg {} + +//****************** End: Party One structs ******************// + +impl KeyGenFirstMsg { + pub fn create_commitments() -> (KeyGenFirstMsg, CommWitness, EcKeyPair) { + let base = Point::generator(); + + let secret_share = Scalar::::random(); + + let public_share = base * &secret_share; + + let d_log_proof = DLogProof::::prove(&secret_share); + // we use hash based commitment + let pk_commitment_blind_factor = BigInt::sample(SECURITY_BITS); + let pk_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(public_share.to_bytes(true).as_ref()), + &pk_commitment_blind_factor, + ); + + let zk_pok_blind_factor = BigInt::sample(SECURITY_BITS); + let zk_pok_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(d_log_proof.pk_t_rand_commitment.to_bytes(true).as_ref()), + &zk_pok_blind_factor, + ); + let ec_key_pair = EcKeyPair { + public_share, + secret_share, + }; + ( + KeyGenFirstMsg { + pk_commitment, + zk_pok_commitment, + }, + CommWitness { + pk_commitment_blind_factor, + zk_pok_blind_factor, + public_share: ec_key_pair.public_share.clone(), + d_log_proof, + }, + ec_key_pair, + ) + } + + pub fn create_commitments_with_fixed_secret_share( + secret_share: Scalar, + ) -> (KeyGenFirstMsg, CommWitness, EcKeyPair) { + let base = Point::generator(); + let public_share = base * &secret_share; + + let d_log_proof = DLogProof::::prove(&secret_share); + + let pk_commitment_blind_factor = BigInt::sample(SECURITY_BITS); + let pk_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(public_share.to_bytes(true).as_ref()), + &pk_commitment_blind_factor, + ); + + let zk_pok_blind_factor = BigInt::sample(SECURITY_BITS); + let zk_pok_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(d_log_proof.pk_t_rand_commitment.to_bytes(true).as_ref()), + &zk_pok_blind_factor, + ); + + let ec_key_pair = EcKeyPair { + public_share, + secret_share, + }; + ( + KeyGenFirstMsg { + pk_commitment, + zk_pok_commitment, + }, + CommWitness { + pk_commitment_blind_factor, + zk_pok_blind_factor, + public_share: ec_key_pair.public_share.clone(), + d_log_proof, + }, + ec_key_pair, + ) + } +} + +impl KeyGenSecondMsg { + pub fn verify_and_decommit( + comm_witness: CommWitness, + proof: &DLogProof, + ) -> Result { + DLogProof::verify(proof)?; + Ok(KeyGenSecondMsg { comm_witness }) + } +} + +pub fn compute_pubkey( + party_one_private: &Party1Private, + other_share_public_share: &Point, +) -> Point { + other_share_public_share * &party_one_private.x1 +} + +impl Party1Private { + pub fn set_private_key(ec_key: &EcKeyPair, paillier_key: &PaillierKeyPair) -> Party1Private { + Party1Private { + x1: ec_key.secret_share.clone(), + paillier_priv: paillier_key.dk.clone(), + c_key_randomness: paillier_key.randomness.clone(), + } + } + pub fn refresh_private_key( + party_one_private: &Party1Private, + factor: &BigInt, + ) -> ( + EncryptionKey, + BigInt, + Party1Private, + NiCorrectKeyProof, + PDLwSlackStatement, + PDLwSlackProof, + CompositeDLogProof, + ) { + let (ek_new, dk_new) = Paillier::keypair().keys(); + let randomness = Randomness::sample(&ek_new); + let factor_fe = Scalar::::from(&*factor); + let x1_new = &party_one_private.x1 * factor_fe; + let c_key_new = Paillier::encrypt_with_chosen_randomness( + &ek_new, + RawPlaintext::from(x1_new.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + let correct_key_proof_new = NiCorrectKeyProof::proof(&dk_new, None); + + let paillier_key_pair = PaillierKeyPair { + ek: ek_new.clone(), + dk: dk_new.clone(), + encrypted_share: c_key_new.clone(), + randomness: randomness.0.clone(), + }; + + let party_one_private_new = Party1Private { + x1: x1_new, + paillier_priv: dk_new, + c_key_randomness: randomness.0, + }; + + let (pdl_statement, pdl_proof, composite_dlog_proof) = + PaillierKeyPair::pdl_proof(&party_one_private_new, &paillier_key_pair); + + ( + ek_new, + c_key_new, + party_one_private_new, + correct_key_proof_new, + pdl_statement, + pdl_proof, + composite_dlog_proof, + ) + } + + // used for verifiable recovery + pub fn to_encrypted_segment( + &self, + segment_size: usize, + num_of_segments: usize, + pub_ke_y: &Point, + g: &Point, + ) -> (Witness, Helgamalsegmented) { + Msegmentation::to_encrypted_segments(&self.x1, &segment_size, num_of_segments, pub_ke_y, g) + } + + // used to transform lindell master key to gg18 master key + pub fn to_mta_message_b( + &self, + message_b: MessageB, + ) -> Result<(Scalar, BigInt), Error> { + message_b.verify_proofs_get_alpha(&self.paillier_priv, &self.x1) + } +} + +impl PaillierKeyPair { + pub fn generate_keypair_and_encrypted_share(keygen: &EcKeyPair) -> PaillierKeyPair { + let (ek, dk) = Paillier::keypair().keys(); + let randomness = Randomness::sample(&ek); + + let encrypted_share = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(keygen.secret_share.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + + PaillierKeyPair { + ek, + dk, + encrypted_share, + randomness: randomness.0, + } + } + + pub fn generate_encrypted_share_from_fixed_paillier_keypair( + ek: &EncryptionKey, + dk: &DecryptionKey, + keygen: &EcKeyPair, + ) -> PaillierKeyPair { + let randomness = Randomness::sample(ek); + + let encrypted_share = Paillier::encrypt_with_chosen_randomness( + ek, + RawPlaintext::from(keygen.secret_share.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + + PaillierKeyPair { + ek: ek.clone(), + dk: dk.clone(), + encrypted_share, + randomness: randomness.0, + } + } + + pub fn generate_ni_proof_correct_key(paillier_context: &PaillierKeyPair) -> NiCorrectKeyProof { + NiCorrectKeyProof::proof(&paillier_context.dk, None) + } + + pub fn pdl_proof( + party1_private: &Party1Private, + paillier_key_pair: &PaillierKeyPair, + ) -> (PDLwSlackStatement, PDLwSlackProof, CompositeDLogProof) { + let (n_tilde, h1, h2, xhi) = generate_h1_h2_n_tilde(); + let dlog_statement = DLogStatement { + N: n_tilde, + g: h1, + ni: h2, + }; + let composite_dlog_proof = CompositeDLogProof::prove(&dlog_statement, &xhi); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: paillier_key_pair.encrypted_share.clone(), + ek: paillier_key_pair.ek.clone(), + Q: Point::generator() * &party1_private.x1, + G: Point::generator().to_point(), + h1: dlog_statement.g.clone(), + h2: dlog_statement.ni.clone(), + N_tilde: dlog_statement.N, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { + x: party1_private.x1.clone(), + r: party1_private.c_key_randomness.clone(), + }; + + let pdl_w_slack_proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + ( + pdl_w_slack_statement, + pdl_w_slack_proof, + composite_dlog_proof, + ) + } +} + +impl EphKeyGenFirstMsg { + pub fn create() -> (EphKeyGenFirstMsg, EphEcKeyPair) { + let base = Point::generator(); + let secret_share = Scalar::::random(); + let public_share = &*base * &secret_share; + let h = Point::::base_point2(); + + let c = h * &secret_share; + let w = ECDDHWitness { + x: secret_share.clone(), + }; + let delta = ECDDHStatement { + g1: base.to_point(), + h1: public_share.clone(), + g2: h.clone(), + h2: c.clone(), + }; + let d_log_proof = ECDDHProof::prove(&w, &delta); + let ec_key_pair = EphEcKeyPair { + public_share: public_share.clone(), + secret_share, + }; + ( + EphKeyGenFirstMsg { + d_log_proof, + public_share, + c, + }, + ec_key_pair, + ) + } +} + +impl EphKeyGenSecondMsg { + pub fn verify_commitments_and_dlog_proof( + party_two_first_message: &Party2EphKeyGenFirstMessage, + party_two_second_message: &Party2EphKeyGenSecondMessage, + ) -> Result { + let party_two_pk_commitment = &party_two_first_message.pk_commitment; + let party_two_zk_pok_commitment = &party_two_first_message.zk_pok_commitment; + let party_two_zk_pok_blind_factor = + &party_two_second_message.comm_witness.zk_pok_blind_factor; + let party_two_public_share = &party_two_second_message.comm_witness.public_share; + let party_two_pk_commitment_blind_factor = &party_two_second_message + .comm_witness + .pk_commitment_blind_factor; + let party_two_d_log_proof = &party_two_second_message.comm_witness.d_log_proof; + let mut flag = true; + if party_two_pk_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(party_two_public_share.to_bytes(true).as_ref()), + party_two_pk_commitment_blind_factor, + ) + { + flag = false + } + if party_two_zk_pok_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &Sha256::new() + .chain_points([&party_two_d_log_proof.a1, &party_two_d_log_proof.a2]) + .result_bigint(), + party_two_zk_pok_blind_factor, + ) + { + flag = false + } + + if !flag { + return Err(ProofError); + } + + let delta = ECDDHStatement { + g1: Point::generator().to_point(), + h1: party_two_public_share.clone(), + g2: Point::::base_point2().clone(), + h2: party_two_second_message.comm_witness.c.clone(), + }; + party_two_d_log_proof.verify(&delta)?; + Ok(EphKeyGenSecondMsg {}) + } +} + +impl Signature { + pub fn compute( + party_one_private: &Party1Private, + partial_sig_c3: &BigInt, + ephemeral_local_share: &EphEcKeyPair, + ephemeral_other_public_share: &Point, + ) -> Signature { + //compute r = k2* R1 + let r = ephemeral_other_public_share * &ephemeral_local_share.secret_share; + + let rx = r + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + + let k1_inv = ephemeral_local_share.secret_share.invert().unwrap(); + + let s_tag = Paillier::decrypt( + &party_one_private.paillier_priv, + &RawCiphertext::from(partial_sig_c3), + ) + .0; + let s_tag_fe = Scalar::::from(s_tag.as_ref()); + let s_tag_tag = s_tag_fe * k1_inv; + let s_tag_tag_bn = s_tag_tag.to_bigint(); + + let s = cmp::min( + s_tag_tag_bn.clone(), + Scalar::::group_order().clone() - s_tag_tag_bn, + ); + + Signature { s, r: rx } + } + + pub fn compute_with_recid( + party_one_private: &Party1Private, + partial_sig_c3: &BigInt, + ephemeral_local_share: &EphEcKeyPair, + ephemeral_other_public_share: &Point, + ) -> SignatureRecid { + //compute r = k2* R1 + let r = ephemeral_other_public_share * &ephemeral_local_share.secret_share; + + let rx = r + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + let ry = r + .y_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + let k1_inv = ephemeral_local_share.secret_share.invert().unwrap(); + + let s_tag = Paillier::decrypt( + &party_one_private.paillier_priv, + &RawCiphertext::from(partial_sig_c3), + ) + .0; + let s_tag_fe = Scalar::::from(s_tag.as_ref()); + let s_tag_tag = s_tag_fe * k1_inv; + let s_tag_tag_bn = s_tag_tag.to_bigint(); + let s = cmp::min( + s_tag_tag_bn.clone(), + Scalar::::group_order() - &s_tag_tag_bn, + ); + + /* + Calculate recovery id - it is not possible to compute the public key out of the signature + itself. Recovery id is used to enable extracting the public key uniquely. + 1. id = R.y & 1 + 2. if (s > curve.q / 2) id = id ^ 1 + */ + let is_ry_odd = ry.test_bit(0); + let mut recid = if is_ry_odd { 1 } else { 0 }; + if s_tag_tag_bn > Scalar::::group_order() - &s_tag_tag_bn { + recid ^= 1; + } + + SignatureRecid { s, r: rx, recid } + } +} + +pub fn verify( + signature: &Signature, + pubkey: &Point, + message: &BigInt, +) -> Result<(), Error> { + let s_fe = Scalar::::from(&signature.s); + let rx_fe = Scalar::::from(&signature.r); + + let s_inv_fe = s_fe.invert().unwrap(); + let e_fe: Scalar = + Scalar::::from(&message.mod_floor(Scalar::::group_order())); + let u1 = Point::generator() * e_fe * &s_inv_fe; + let u2 = &*pubkey * rx_fe * &s_inv_fe; + + // second condition is against malleability + let rx_bytes = &BigInt::to_bytes(&signature.r)[..]; + let u1_plus_u2_bytes = &BigInt::to_bytes(&(u1 + u2).x_coord().unwrap())[..]; + + if rx_bytes.ct_eq(u1_plus_u2_bytes).unwrap_u8() == 1 + && signature.s < Scalar::::group_order() - signature.s.clone() + { + Ok(()) + } else { + Err(Error::InvalidSig) + } +} + +pub fn generate_h1_h2_n_tilde() -> (BigInt, BigInt, BigInt, BigInt) { + //note, should be safe primes: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys();; + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let s = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&s); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + + (ek_tilde.n, h1, h2, xhi) +} diff --git a/src/protocols/two_party_ecdsa/lindell_2017/party_two.rs b/src/protocols/two_party_ecdsa/lindell_2017/party_two.rs new file mode 100644 index 0000000..9b255cd --- /dev/null +++ b/src/protocols/two_party_ecdsa/lindell_2017/party_two.rs @@ -0,0 +1,424 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +use centipede::juggling::proof_system::{Helgamalsegmented, Witness}; +use centipede::juggling::segmentation::Msegmentation; +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::cryptographic_primitives::proofs::sigma_dlog::*; +use curv::cryptographic_primitives::proofs::sigma_ec_ddh::*; +use curv::cryptographic_primitives::proofs::ProofError; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::Paillier; +use paillier::{Add, Encrypt, Mul}; +use paillier::{EncryptionKey, RawCiphertext, RawPlaintext}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use zk_paillier::zkproofs::{IncorrectProof, NiCorrectKeyProof}; + +use super::party_one::EphKeyGenFirstMsg as Party1EphKeyGenFirstMsg; +use super::party_one::KeyGenFirstMsg as Party1KeyGenFirstMessage; +use super::party_one::KeyGenSecondMsg as Party1KeyGenSecondMessage; +use super::SECURITY_BITS; +use crate::utilities::mta::{MessageA, MessageB}; + +use crate::utilities::zk_pdl_with_slack::PDLwSlackProof; +use crate::utilities::zk_pdl_with_slack::PDLwSlackStatement; +use thiserror::Error; +use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + +#[derive(Error, Debug)] +pub enum PartyTwoError { + #[error("party two pdl verify failed (lindell 2017)")] + PdlVerify, +} + +const PAILLIER_KEY_SIZE: usize = 2048; +//****************** Begin: Party Two structs ******************// + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenFirstMsg { + pub d_log_proof: DLogProof, + pub public_share: Point, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyGenSecondMsg {} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PaillierPublic { + pub ek: EncryptionKey, + pub encrypted_secret_share: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PartialSig { + pub c3: BigInt, +} + +#[derive(Serialize, Deserialize)] +pub struct Party2Private { + x2: Scalar, +} +#[allow(dead_code)] +pub struct PDLchallenge { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, + a: BigInt, + b: BigInt, + blindness: BigInt, + q_tag: Point, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLFirstMessage { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLdecommit { + pub a: BigInt, + pub b: BigInt, + pub blindness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PDLSecondMessage { + pub decommit: PDLdecommit, +} +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphEcKeyPair { + pub public_share: Point, + secret_share: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphCommWitness { + pub pk_commitment_blind_factor: BigInt, + pub zk_pok_blind_factor: BigInt, + pub public_share: Point, + pub d_log_proof: ECDDHProof, + pub c: Point, //c = secret_share * base_point2 +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EphKeyGenFirstMsg { + pub pk_commitment: BigInt, + pub zk_pok_commitment: BigInt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EphKeyGenSecondMsg { + pub comm_witness: EphCommWitness, +} + +//****************** End: Party Two structs ******************// + +impl KeyGenFirstMsg { + pub fn create() -> (KeyGenFirstMsg, EcKeyPair) { + let base = Point::generator(); + let secret_share = Scalar::::random(); + let public_share = base * &secret_share; + let d_log_proof = DLogProof::prove(&secret_share); + let ec_key_pair = EcKeyPair { + public_share: public_share.clone(), + secret_share, + }; + ( + KeyGenFirstMsg { + d_log_proof, + public_share, + }, + ec_key_pair, + ) + } + + pub fn create_with_fixed_secret_share( + secret_share: Scalar, + ) -> (KeyGenFirstMsg, EcKeyPair) { + let base = Point::generator(); + let public_share = base * &secret_share; + let d_log_proof = DLogProof::prove(&secret_share); + let ec_key_pair = EcKeyPair { + public_share: public_share.clone(), + secret_share, + }; + ( + KeyGenFirstMsg { + d_log_proof, + public_share, + }, + ec_key_pair, + ) + } +} + +impl KeyGenSecondMsg { + pub fn verify_commitments_and_dlog_proof( + party_one_first_message: &Party1KeyGenFirstMessage, + party_one_second_message: &Party1KeyGenSecondMessage, + ) -> Result { + let party_one_pk_commitment = &party_one_first_message.pk_commitment; + let party_one_zk_pok_commitment = &party_one_first_message.zk_pok_commitment; + let party_one_zk_pok_blind_factor = + &party_one_second_message.comm_witness.zk_pok_blind_factor; + let party_one_public_share = &party_one_second_message.comm_witness.public_share; + let party_one_pk_commitment_blind_factor = &party_one_second_message + .comm_witness + .pk_commitment_blind_factor; + let party_one_d_log_proof = &party_one_second_message.comm_witness.d_log_proof; + + let mut flag = true; + if party_one_pk_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(party_one_public_share.to_bytes(true).as_ref()), + party_one_pk_commitment_blind_factor, + ) + { + flag = false + } + if party_one_zk_pok_commitment + != &HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes( + party_one_d_log_proof + .pk_t_rand_commitment + .to_bytes(true) + .as_ref(), + ), + party_one_zk_pok_blind_factor, + ) + { + flag = false + } + + if !flag { + return Err(ProofError); + } + + DLogProof::verify(party_one_d_log_proof)?; + Ok(KeyGenSecondMsg {}) + } +} + +pub fn compute_pubkey( + local_share: &EcKeyPair, + other_share_public_share: &Point, +) -> Point { + let pubkey = other_share_public_share; + pubkey * &local_share.secret_share +} + +impl Party2Private { + pub fn set_private_key(ec_key: &EcKeyPair) -> Party2Private { + Party2Private { + x2: ec_key.secret_share.clone(), + } + } + + pub fn update_private_key(party_two_private: &Party2Private, factor: &BigInt) -> Party2Private { + let factor_fe = Scalar::::from(factor); + Party2Private { + x2: &party_two_private.x2 * &factor_fe, + } + } + + // used for verifiable recovery + pub fn to_encrypted_segment( + &self, + segment_size: usize, + num_of_segments: usize, + pub_ke_y: &Point, + g: &Point, + ) -> (Witness, Helgamalsegmented) { + Msegmentation::to_encrypted_segments(&self.x2, &segment_size, num_of_segments, pub_ke_y, g) + } + + // used to transform lindell master key to gg18 master key + pub fn to_mta_message_b( + &self, + ek: &EncryptionKey, + ciphertext: &BigInt, + ) -> (MessageB, Scalar) { + let message_a = MessageA { + c: ciphertext.clone(), + range_proofs: vec![], + }; + let (a, b, _, _) = MessageB::b(&self.x2, ek, message_a, &[]).unwrap(); + (a, b) + } +} + +impl PaillierPublic { + pub fn pdl_verify( + composite_dlog_proof: &CompositeDLogProof, + pdl_w_slack_statement: &PDLwSlackStatement, + pdl_w_slack_proof: &PDLwSlackProof, + paillier_public: &PaillierPublic, + q1: &Point, + ) -> Result<(), PartyTwoError> { + if pdl_w_slack_statement.ek != paillier_public.ek + || pdl_w_slack_statement.ciphertext != paillier_public.encrypted_secret_share + || &pdl_w_slack_statement.Q != q1 + { + return Err(PartyTwoError::PdlVerify); + } + let dlog_statement = DLogStatement { + N: pdl_w_slack_statement.N_tilde.clone(), + g: pdl_w_slack_statement.h1.clone(), + ni: pdl_w_slack_statement.h2.clone(), + }; + if composite_dlog_proof.verify(&dlog_statement).is_ok() + && pdl_w_slack_proof.verify(pdl_w_slack_statement).is_ok() + { + Ok(()) + } else { + Err(PartyTwoError::PdlVerify) + } + } + + pub fn verify_ni_proof_correct_key( + proof: NiCorrectKeyProof, + ek: &EncryptionKey, + ) -> Result<(), IncorrectProof> { + // + if ek.n.bit_length() < PAILLIER_KEY_SIZE - 1 { + return Err(IncorrectProof); + }; + proof.verify(ek, zk_paillier::zkproofs::SALT_STRING) + } +} + +impl EphKeyGenFirstMsg { + pub fn create_commitments() -> (EphKeyGenFirstMsg, EphCommWitness, EphEcKeyPair) { + let base = Point::generator(); + + let secret_share = Scalar::::random(); + + let public_share = base * &secret_share; + + let h = Point::::base_point2(); + + let c = h * &secret_share; + let w = ECDDHWitness { + x: secret_share.clone(), + }; + let delta = ECDDHStatement { + g1: base.to_point(), + h1: public_share.clone(), + g2: h.clone(), + h2: c.clone(), + }; + let d_log_proof = ECDDHProof::prove(&w, &delta); + + // we use hash based commitment + let pk_commitment_blind_factor = BigInt::sample(SECURITY_BITS); + let pk_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(public_share.to_bytes(true).as_ref()), + &pk_commitment_blind_factor, + ); + + let zk_pok_blind_factor = BigInt::sample(SECURITY_BITS); + let zk_pok_commitment = + HashCommitment::::create_commitment_with_user_defined_randomness( + &Sha256::new() + .chain_points([&d_log_proof.a1, &d_log_proof.a2]) + .result_bigint(), + &zk_pok_blind_factor, + ); + + let ec_key_pair = EphEcKeyPair { + public_share, + secret_share, + }; + ( + EphKeyGenFirstMsg { + pk_commitment, + zk_pok_commitment, + }, + EphCommWitness { + pk_commitment_blind_factor, + zk_pok_blind_factor, + public_share: ec_key_pair.public_share.clone(), + d_log_proof, + c, + }, + ec_key_pair, + ) + } +} + +impl EphKeyGenSecondMsg { + pub fn verify_and_decommit( + comm_witness: EphCommWitness, + party_one_first_message: &Party1EphKeyGenFirstMsg, + ) -> Result { + let delta = ECDDHStatement { + g1: Point::generator().to_point(), + h1: party_one_first_message.public_share.clone(), + g2: Point::::base_point2().clone(), + h2: party_one_first_message.c.clone(), + }; + party_one_first_message.d_log_proof.verify(&delta)?; + Ok(EphKeyGenSecondMsg { comm_witness }) + } +} + +impl PartialSig { + pub fn compute( + ek: &EncryptionKey, + encrypted_secret_share: &BigInt, + local_share: &Party2Private, + ephemeral_local_share: &EphEcKeyPair, + ephemeral_other_public_share: &Point, + message: &BigInt, + ) -> PartialSig { + let q = Scalar::::group_order(); + //compute r = k2* R1 + let r = ephemeral_other_public_share * &ephemeral_local_share.secret_share; + + let rx = r.x_coord().unwrap().mod_floor(q); + let rho = BigInt::sample_below(&q.pow(2)); + let k2_inv = BigInt::mod_inv(&ephemeral_local_share.secret_share.to_bigint(), q).unwrap(); + let partial_sig = rho * q + BigInt::mod_mul(&k2_inv, message, q); + + let c1 = Paillier::encrypt(ek, RawPlaintext::from(partial_sig)); + let v = BigInt::mod_mul( + &k2_inv, + &BigInt::mod_mul(&rx, &local_share.x2.to_bigint(), q), + q, + ); + let c2 = Paillier::mul( + ek, + RawCiphertext::from(encrypted_secret_share.clone()), + RawPlaintext::from(v), + ); + //c3: + PartialSig { + c3: Paillier::add(ek, c2, c1).0.into_owned(), + } + } +} diff --git a/src/protocols/two_party_ecdsa/lindell_2017/test.rs b/src/protocols/two_party_ecdsa/lindell_2017/test.rs new file mode 100644 index 0000000..60c142f --- /dev/null +++ b/src/protocols/two_party_ecdsa/lindell_2017/test.rs @@ -0,0 +1,137 @@ +// For integration tests, please add your tests in /tests instead + +use crate::protocols::two_party_ecdsa::lindell_2017::{party_one, party_two}; +use curv::arithmetic::traits::Samplable; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Scalar}; +use curv::BigInt; + +#[test] +fn test_d_log_proof_party_two_party_one() { + let (party_one_first_message, comm_witness, _ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments(); + let (party_two_first_message, _ec_key_pair_party2) = party_two::KeyGenFirstMsg::create(); + let party_one_second_message = party_one::KeyGenSecondMsg::verify_and_decommit( + comm_witness, + &party_two_first_message.d_log_proof, + ) + .expect("failed to verify and decommit"); + + let _party_two_second_message = party_two::KeyGenSecondMsg::verify_commitments_and_dlog_proof( + &party_one_first_message, + &party_one_second_message, + ) + .expect("failed to verify commitments and DLog proof"); +} + +#[test] + +fn test_full_key_gen() { + let (party_one_first_message, comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments_with_fixed_secret_share( + Scalar::::from(&BigInt::sample(253)), + ); + let (party_two_first_message, _ec_key_pair_party2) = + party_two::KeyGenFirstMsg::create_with_fixed_secret_share(Scalar::::from( + &BigInt::from(10), + )); + let party_one_second_message = party_one::KeyGenSecondMsg::verify_and_decommit( + comm_witness, + &party_two_first_message.d_log_proof, + ) + .expect("failed to verify and decommit"); + + let _party_two_second_message = party_two::KeyGenSecondMsg::verify_commitments_and_dlog_proof( + &party_one_first_message, + &party_one_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + + // init paillier keypair: + let paillier_key_pair = + party_one::PaillierKeyPair::generate_keypair_and_encrypted_share(&ec_key_pair_party1); + + let party_one_private = + party_one::Party1Private::set_private_key(&ec_key_pair_party1, &paillier_key_pair); + + let party_two_paillier = party_two::PaillierPublic { + ek: paillier_key_pair.ek.clone(), + encrypted_secret_share: paillier_key_pair.encrypted_share.clone(), + }; + + // zk proof of correct paillier key + let correct_key_proof = + party_one::PaillierKeyPair::generate_ni_proof_correct_key(&paillier_key_pair); + party_two::PaillierPublic::verify_ni_proof_correct_key( + correct_key_proof, + &party_two_paillier.ek, + ) + .expect("bad paillier key"); + + //zk_pdl + + let (pdl_statement, pdl_proof, composite_dlog_proof) = + party_one::PaillierKeyPair::pdl_proof(&party_one_private, &paillier_key_pair); + party_two::PaillierPublic::pdl_verify( + &composite_dlog_proof, + &pdl_statement, + &pdl_proof, + &party_two_paillier, + &party_one_second_message.comm_witness.public_share, + ) + .expect("PDL error"); +} + +#[test] +fn test_two_party_sign() { + // assume party1 and party2 engaged with KeyGen in the past resulting in + // party1 owning private share and paillier key-pair + // party2 owning private share and paillier encryption of party1 share + let (_party_one_private_share_gen, _comm_witness, ec_key_pair_party1) = + party_one::KeyGenFirstMsg::create_commitments(); + let (party_two_private_share_gen, ec_key_pair_party2) = party_two::KeyGenFirstMsg::create(); + + let keypair = + party_one::PaillierKeyPair::generate_keypair_and_encrypted_share(&ec_key_pair_party1); + + // creating the ephemeral private shares: + + let (eph_party_two_first_message, eph_comm_witness, eph_ec_key_pair_party2) = + party_two::EphKeyGenFirstMsg::create_commitments(); + let (eph_party_one_first_message, eph_ec_key_pair_party1) = + party_one::EphKeyGenFirstMsg::create(); + let eph_party_two_second_message = party_two::EphKeyGenSecondMsg::verify_and_decommit( + eph_comm_witness, + &eph_party_one_first_message, + ) + .expect("party1 DLog proof failed"); + + let _eph_party_one_second_message = + party_one::EphKeyGenSecondMsg::verify_commitments_and_dlog_proof( + &eph_party_two_first_message, + &eph_party_two_second_message, + ) + .expect("failed to verify commitments and DLog proof"); + let party2_private = party_two::Party2Private::set_private_key(&ec_key_pair_party2); + let message = BigInt::from(1234); + let partial_sig = party_two::PartialSig::compute( + &keypair.ek, + &keypair.encrypted_share, + &party2_private, + &eph_ec_key_pair_party2, + &eph_party_one_first_message.public_share, + &message, + ); + + let party1_private = party_one::Party1Private::set_private_key(&ec_key_pair_party1, &keypair); + + let signature = party_one::Signature::compute( + &party1_private, + &partial_sig.c3, + &eph_ec_key_pair_party1, + &eph_party_two_second_message.comm_witness.public_share, + ); + + let pubkey = + party_one::compute_pubkey(&party1_private, &party_two_private_share_gen.public_share); + party_one::verify(&signature, &pubkey, &message).expect("Invalid signature") +} diff --git a/src/protocols/two_party_ecdsa/mod.rs b/src/protocols/two_party_ecdsa/mod.rs new file mode 100644 index 0000000..d1785d2 --- /dev/null +++ b/src/protocols/two_party_ecdsa/mod.rs @@ -0,0 +1,24 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +// Fast Secure Two-Party ECDSA Signing by Yehuda Lindell (https://eprint.iacr.org/2017/552.pdf). + +pub mod lindell_2017; + +// Two-Party ECDSA from Hash Proof Systems and +//Efficient Instantiations (https://eprint.iacr.org/2019/503.pdf) +#[cfg(feature = "cclst")] +pub mod cclst_2019; diff --git a/src/utilities/mod.rs b/src/utilities/mod.rs new file mode 100644 index 0000000..867fce3 --- /dev/null +++ b/src/utilities/mod.rs @@ -0,0 +1,3 @@ +pub mod mta; +pub mod zk_pdl; +pub mod zk_pdl_with_slack; diff --git a/src/utilities/mta/mod.rs b/src/utilities/mta/mod.rs new file mode 100644 index 0000000..b5415d8 --- /dev/null +++ b/src/utilities/mta/mod.rs @@ -0,0 +1,214 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +/// MtA is described in https://eprint.iacr.org/2019/114.pdf section 3 +use curv::arithmetic::traits::Samplable; +use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::traits::EncryptWithChosenRandomness; +use paillier::{Add, Decrypt, Mul}; +use paillier::{DecryptionKey, EncryptionKey, Paillier, Randomness, RawCiphertext, RawPlaintext}; +use zk_paillier::zkproofs::DLogStatement; + +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +use crate::protocols::multi_party_ecdsa::gg_2018::party_i::PartyPrivate; +use crate::utilities::mta::range_proofs::AliceProof; +use crate::Error::{self, InvalidKey}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MessageA { + pub c: BigInt, // paillier encryption + pub range_proofs: Vec, // proofs (using other parties' h1,h2,N_tilde) that the plaintext is small +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MessageB { + pub c: BigInt, // paillier encryption + pub b_proof: DLogProof, + pub beta_tag_proof: DLogProof, +} + +impl MessageA { + /// Creates a new `messageA` using Alice's Paillier encryption key and `dlog_statements` + /// - other parties' `h1,h2,N_tilde`s for range proofs. + /// If range proofs are not needed (one example is identification of aborts where we + /// only want to reconstruct a ciphertext), `dlog_statements` can be an empty slice. + pub fn a( + a: &Scalar, + alice_ek: &EncryptionKey, + dlog_statements: &[DLogStatement], + ) -> (Self, BigInt) { + let randomness = BigInt::sample_below(&alice_ek.n); + let m_a = MessageA::a_with_predefined_randomness(a, alice_ek, &randomness, dlog_statements); + (m_a, randomness) + } + + pub fn a_with_predefined_randomness( + a: &Scalar, + alice_ek: &EncryptionKey, + randomness: &BigInt, + dlog_statements: &[DLogStatement], + ) -> Self { + let c_a = Paillier::encrypt_with_chosen_randomness( + alice_ek, + RawPlaintext::from(a.to_bigint()), + &Randomness::from(randomness.clone()), + ) + .0 + .clone() + .into_owned(); + let alice_range_proofs = dlog_statements + .iter() + .map(|dlog_statement| { + AliceProof::generate(&a.to_bigint(), &c_a, alice_ek, dlog_statement, randomness) + }) + .collect::>(); + + Self { + c: c_a, + range_proofs: alice_range_proofs, + } + } +} + +impl MessageB { + pub fn b( + b: &Scalar, + alice_ek: &EncryptionKey, + m_a: MessageA, + dlog_statements: &[DLogStatement], + ) -> Result<(Self, Scalar, BigInt, BigInt), Error> { + let beta_tag = BigInt::sample_below(&alice_ek.n); + let randomness = BigInt::sample_below(&alice_ek.n); + let (m_b, beta) = MessageB::b_with_predefined_randomness( + b, + alice_ek, + m_a, + &randomness, + &beta_tag, + dlog_statements, + )?; + + Ok((m_b, beta, randomness, beta_tag)) + } + + pub fn b_with_predefined_randomness( + b: &Scalar, + alice_ek: &EncryptionKey, + m_a: MessageA, + randomness: &BigInt, + beta_tag: &BigInt, + dlog_statements: &[DLogStatement], + ) -> Result<(Self, Scalar), Error> { + if m_a.range_proofs.len() != dlog_statements.len() { + return Err(InvalidKey); + } + // verify proofs + if !m_a + .range_proofs + .iter() + .zip(dlog_statements) + .map(|(proof, dlog_statement)| proof.verify(&m_a.c, alice_ek, dlog_statement)) + .all(|x| x) + { + return Err(InvalidKey); + }; + let beta_tag_fe = Scalar::::from(beta_tag); + let c_beta_tag = Paillier::encrypt_with_chosen_randomness( + alice_ek, + RawPlaintext::from(beta_tag), + &Randomness::from(randomness.clone()), + ); + + let b_bn = b.to_bigint(); + let b_c_a = Paillier::mul( + alice_ek, + RawCiphertext::from(m_a.c), + RawPlaintext::from(b_bn), + ); + let c_b = Paillier::add(alice_ek, b_c_a, c_beta_tag); + let beta = Scalar::::zero() - &beta_tag_fe; + let dlog_proof_b = DLogProof::prove(b); + let dlog_proof_beta_tag = DLogProof::prove(&beta_tag_fe); + + Ok(( + Self { + c: c_b.0.clone().into_owned(), + b_proof: dlog_proof_b, + beta_tag_proof: dlog_proof_beta_tag, + }, + beta, + )) + } + + pub fn verify_proofs_get_alpha( + &self, + dk: &DecryptionKey, + a: &Scalar, + ) -> Result<(Scalar, BigInt), Error> { + let alice_share = Paillier::decrypt(dk, &RawCiphertext::from(self.c.clone())); + let g = Point::generator(); + let alpha = Scalar::::from(alice_share.0.as_ref()); + let g_alpha = g * α + let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; + if DLogProof::verify(&self.b_proof).is_ok() + && DLogProof::verify(&self.beta_tag_proof).is_ok() + // we prove the correctness of the ciphertext using this check and the proof of knowledge of dlog of beta_tag + && ba_btag == g_alpha + { + Ok((alpha, alice_share.0.into_owned())) + } else { + Err(InvalidKey) + } + } + + // another version, supporting PartyPrivate therefore binding mta to gg18. + // with the regular version mta can be used in general + pub fn verify_proofs_get_alpha_gg18( + &self, + private: &PartyPrivate, + a: &Scalar, + ) -> Result, Error> { + let alice_share = private.decrypt(self.c.clone()); + let g = Point::generator(); + let alpha = Scalar::::from(alice_share.0.as_ref()); + let g_alpha = g * α + let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; + + if DLogProof::verify(&self.b_proof).is_ok() + && DLogProof::verify(&self.beta_tag_proof).is_ok() + && ba_btag == g_alpha + { + Ok(alpha) + } else { + Err(InvalidKey) + } + } + + pub fn verify_b_against_public( + public_gb: &Point, + mta_gb: &Point, + ) -> bool { + public_gb == mta_gb + } +} + +pub mod range_proofs; +#[cfg(test)] +mod test; diff --git a/src/utilities/mta/range_proofs.rs b/src/utilities/mta/range_proofs.rs new file mode 100644 index 0000000..4610a57 --- /dev/null +++ b/src/utilities/mta/range_proofs.rs @@ -0,0 +1,710 @@ +#![allow(non_snake_case)] + +//! This file is a modified version of ING bank's range proofs implementation: +//! https://github.com/ing-bank/threshold-signatures/blob/master/src/algorithms/zkp.rs +//! +//! Zero knowledge range proofs for MtA protocol are implemented here. +//! Formal description can be found in Appendix A of https://eprint.iacr.org/2019/114.pdf +//! There are some deviations from the original specification: +//! 1) In Bob's proofs `gamma` is sampled from `[0;q^2 * N]` and `tau` from `[0;q^3 * N_tilde]`. +//! 2) A non-interactive version is implemented, with challenge `e` computed via Fiat-Shamir. + +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use sha2::Sha256; + +use paillier::{EncryptionKey, Randomness}; +use zk_paillier::zkproofs::DLogStatement; + +use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; +use zeroize::Zeroize; + +/// Represents the first round of the interactive version of the proof +#[derive(Zeroize)] +#[zeroize(drop)] +struct AliceZkpRound1 { + alpha: BigInt, + beta: BigInt, + gamma: BigInt, + ro: BigInt, + z: BigInt, + u: BigInt, + w: BigInt, +} + +impl AliceZkpRound1 { + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + a: &BigInt, + q: &BigInt, + ) -> Self { + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let z = (BigInt::mod_pow(h1, a, N_tilde) * BigInt::mod_pow(h2, &ro, N_tilde)) % N_tilde; + let u = ((alpha.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + let w = + (BigInt::mod_pow(h1, &alpha, N_tilde) * BigInt::mod_pow(h2, &gamma, N_tilde)) % N_tilde; + Self { + alpha, + beta, + gamma, + ro, + z, + u, + w, + } + } +} + +/// Represents the second round of the interactive version of the proof +struct AliceZkpRound2 { + s: BigInt, + s1: BigInt, + s2: BigInt, +} + +impl AliceZkpRound2 { + fn from( + alice_ek: &EncryptionKey, + round1: &AliceZkpRound1, + e: &BigInt, + a: &BigInt, + r: &BigInt, + ) -> Self { + Self { + s: (BigInt::mod_pow(r, e, &alice_ek.n) * round1.beta.borrow()) % &alice_ek.n, + s1: (e * a) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.gamma.borrow(), + } + } +} + +/// Alice's proof +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AliceProof { + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, +} + +impl AliceProof { + /// verify Alice's proof using the proof and public keys + pub fn verify( + &self, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let Gen = alice_ek.n.borrow() + 1; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.z, &self.e, N_tilde), N_tilde); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let gs1 = (self.s1.borrow() * N + 1) % NN; + let cipher_e_inv = BigInt::mod_inv(&BigInt::mod_pow(cipher, &self.e, NN), NN); + let cipher_e_inv = match cipher_e_inv { + None => return false, + Some(c) => c, + }; + + let u = (gs1 * BigInt::mod_pow(&self.s, N, NN) * cipher_e_inv) % NN; + + let e = Sha256::new() + .chain_bigint(N) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&self.z) + .chain_bigint(&u) + .chain_bigint(&w) + .result_bigint(); + if e != self.e { + return false; + } + + true + } + /// Create the proof using Alice's Paillier private keys and public ZKP setup. + /// Requires randomness used for encrypting Alice's secret a. + /// It is assumed that secp256k1 curve is used. + pub fn generate( + a: &BigInt, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &BigInt, + ) -> Self { + let round1 = AliceZkpRound1::from( + alice_ek, + dlog_statement, + a, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let e = Sha256::new() + .chain_bigint(&alice_ek.n) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&round1.z) + .chain_bigint(&round1.u) + .chain_bigint(&round1.w) + .result_bigint(); + + let round2 = AliceZkpRound2::from(alice_ek, &round1, &e, a, r); + + Self { + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + } + } +} + +/// Represents first round of the interactive version of the proof +#[derive(Zeroize)] +#[zeroize(drop)] +struct BobZkpRound1 { + pub alpha: BigInt, + pub beta: BigInt, + pub gamma: BigInt, + pub ro: BigInt, + pub ro_prim: BigInt, + pub sigma: BigInt, + pub tau: BigInt, + pub z: BigInt, + pub z_prim: BigInt, + pub t: BigInt, + pub w: BigInt, + pub v: BigInt, +} + +impl BobZkpRound1 { + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `a_encrypted` - Alice's secret encrypted by Alice + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + b: &Scalar, + beta_prim: &BigInt, + a_encrypted: &BigInt, + q: &BigInt, + ) -> Self { + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let b_bn = b.to_bigint(); + + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(2) * &alice_ek.n)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let ro_prim = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let sigma = BigInt::sample_below(&(q * N_tilde)); + let tau = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let z = (BigInt::mod_pow(h1, &b_bn, N_tilde) * BigInt::mod_pow(h2, &ro, N_tilde)) % N_tilde; + let z_prim = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &ro_prim, N_tilde)) + % N_tilde; + let t = (BigInt::mod_pow(h1, beta_prim, N_tilde) * BigInt::mod_pow(h2, &sigma, N_tilde)) + % N_tilde; + let w = + (BigInt::mod_pow(h1, &gamma, N_tilde) * BigInt::mod_pow(h2, &tau, N_tilde)) % N_tilde; + let v = (BigInt::mod_pow(a_encrypted, &alpha, &alice_ek.nn) + * (gamma.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + Self { + alpha, + beta, + gamma, + ro, + ro_prim, + sigma, + tau, + z, + z_prim, + t, + w, + v, + } + } +} + +/// represents second round of the interactive version of the proof +struct BobZkpRound2 { + pub s: BigInt, + pub s1: BigInt, + pub s2: BigInt, + pub t1: BigInt, + pub t2: BigInt, +} + +impl BobZkpRound2 { + /// `e` - the challenge in interactive ZKP, the hash in non-interactive ZKP + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `r` - randomness used by Bob on Alice's public Paillier key to encrypt `beta_prim` in `MtA` + fn from( + alice_ek: &EncryptionKey, + round1: &BobZkpRound1, + e: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + r: &Randomness, + ) -> Self { + let b_bn = b.to_bigint(); + Self { + s: (BigInt::mod_pow(r.0.borrow(), e, &alice_ek.n) * round1.beta.borrow()) % &alice_ek.n, + s1: (e * b_bn) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.ro_prim.borrow(), + t1: (e * beta_prim) + round1.gamma.borrow(), + t2: (e * round1.sigma.borrow()) + round1.tau.borrow(), + } + } +} + +/// Additional fields in Bob's proof if MtA is run with check +pub struct BobCheck { + u: Point, + X: Point, +} + +/// Bob's regular proof +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProof { + t: BigInt, + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + t1: BigInt, + t2: BigInt, +} + +#[allow(clippy::too_many_arguments)] +impl BobProof { + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + check: Option<&BobCheck>, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.z, &self.e, N_tilde), N_tilde); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let z_prim = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let mta_e_inv = BigInt::mod_inv(&BigInt::mod_pow(mta_avc_out, &self.e, NN), NN); + let mta_e_inv = match mta_e_inv { + None => return false, + Some(c) => c, + }; + + let v = (BigInt::mod_pow(a_enc, &self.s1, NN) + * BigInt::mod_pow(&self.s, N, NN) + * (self.t1.borrow() * N + 1) + * mta_e_inv) + % NN; + + let t_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.t, &self.e, N_tilde), N_tilde); + let t_e_inv = match t_e_inv { + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.t1, N_tilde) + * BigInt::mod_pow(h2, &self.t2, N_tilde) + * t_e_inv) + % N_tilde; + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_enc, + mta_avc_out, + &self.z, + &z_prim, + &self.t, + &v, + &w, + ]; + let e = match check { + Some(_) => { + let X_x_coor = check.unwrap().X.x_coord().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = check.unwrap().X.y_coord().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = check.unwrap().u.x_coord().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = check.unwrap().u.y_coord().unwrap(); + values_to_hash.push(&u_y_coor); + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + } + None => values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint(), + }; + + if e != self.e { + return false; + } + + true + } + + pub fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + check: bool, + ) -> (BobProof, Option>) { + let round1 = BobZkpRound1::from( + alice_ek, + dlog_statement, + b, + beta_prim, + a_encrypted, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_encrypted, + mta_encrypted, + &round1.z, + &round1.z_prim, + &round1.t, + &round1.v, + &round1.w, + ]; + let mut check_u = None; + let e = if check { + let (X, u) = { + let ec_gen = Point::generator(); + let alpha = Scalar::::from(&round1.alpha); + (ec_gen * b, ec_gen * alpha) + }; + check_u = Some(u.clone()); + let X_x_coor = X.x_coord().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = X.y_coord().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = u.x_coord().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = u.y_coord().unwrap(); + values_to_hash.push(&u_y_coor); + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + } else { + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + }; + + let round2 = BobZkpRound2::from(alice_ek, &round1, &e, b, beta_prim, r); + + ( + BobProof { + t: round1.t.clone(), + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + t1: round2.t1, + t2: round2.t2, + }, + check_u, + ) + } +} + +/// Bob's extended proof, adds the knowledge of $`B = g^b \in \mathcal{G}`$ +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProofExt { + proof: BobProof, + u: Point, +} + +#[allow(clippy::too_many_arguments)] +impl BobProofExt { + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + X: &Point, + ) -> bool { + // check basic proof first + if !self.proof.verify( + a_enc, + mta_avc_out, + alice_ek, + dlog_statement, + Some(&BobCheck { + u: self.u.clone(), + X: X.clone(), + }), + ) { + return false; + } + + // fiddle with EC points + let (x1, x2) = { + let ec_gen = Point::generator(); + let s1 = Scalar::::from(&self.proof.s1); + let e = Scalar::::from(&self.proof.e); + (ec_gen * s1, (X * &e) + &self.u) + }; + + if x1 != x2 { + return false; + } + + true + } +} + +/// sample random value of an element of a multiplicative group +pub trait SampleFromMultiplicativeGroup { + fn from_modulo(N: &BigInt) -> BigInt; + fn from_paillier_key(ek: &EncryptionKey) -> BigInt; +} + +impl SampleFromMultiplicativeGroup for BigInt { + fn from_modulo(N: &BigInt) -> BigInt { + let One = BigInt::one(); + loop { + let r = Self::sample_below(N); + if r.gcd(N) == One { + return r; + } + } + } + + fn from_paillier_key(ek: &EncryptionKey) -> BigInt { + Self::from_modulo(ek.n.borrow()) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use paillier::traits::{Encrypt, EncryptWithChosenRandomness, KeyGeneration}; + use paillier::{Add, DecryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext}; + + fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + ) -> BobProofExt { + // proving a basic proof (with modified hash) + let (bob_proof, u) = BobProof::generate( + a_encrypted, + mta_encrypted, + b, + beta_prim, + alice_ek, + dlog_statement, + r, + true, + ); + + BobProofExt { + proof: bob_proof, + u: u.unwrap(), + } + } + + pub(crate) fn generate_init() -> (DLogStatement, EncryptionKey, DecryptionKey) { + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (xhi, _) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + + let (ek, dk) = Paillier::keypair().keys(); + let dlog_statement = DLogStatement { + g: h1, + ni: h2, + N: ek_tilde.n, + }; + (dlog_statement, ek, dk) + } + + #[test] + fn alice_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + // Alice's secret value + let a = Scalar::::random().to_bigint(); + let r = BigInt::from_paillier_key(&ek); + let cipher = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(a.clone()), + &Randomness::from(&r), + ) + .0 + .clone() + .into_owned(); + + let alice_proof = AliceProof::generate(&a, &cipher, &ek, &dlog_statement, &r); + + assert!(alice_proof.verify(&cipher, &ek, &dlog_statement)); + } + + #[test] + fn bob_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + (0..5).for_each(|_| { + let alice_public_key = &ek; + + // run MtA protocol with different inputs + (0..5).for_each(|_| { + // Simulate Alice + let a = Scalar::::random().to_bigint(); + let encrypted_a = Paillier::encrypt(alice_public_key, RawPlaintext::from(a)) + .0 + .clone() + .into_owned(); + + // Bob follows MtA + let b = Scalar::::random(); + // E(a) * b + let b_times_enc_a = Paillier::mul( + alice_public_key, + RawCiphertext::from(encrypted_a.clone()), + RawPlaintext::from(&b.to_bigint()), + ); + let beta_prim = BigInt::sample_below(&alice_public_key.n); + let r = Randomness::sample(alice_public_key); + let enc_beta_prim = Paillier::encrypt_with_chosen_randomness( + alice_public_key, + RawPlaintext::from(&beta_prim), + &r, + ); + + let mta_out = Paillier::add(alice_public_key, b_times_enc_a, enc_beta_prim); + + let (bob_proof, _) = BobProof::generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + false, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + None + )); + + // Bob follows MtAwc + let ec_gen = Point::generator(); + let X = ec_gen * &b; + let bob_proof = generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + &X + )); + }); + }); + } +} diff --git a/src/utilities/mta/test.rs b/src/utilities/mta/test.rs new file mode 100644 index 0000000..0602ea9 --- /dev/null +++ b/src/utilities/mta/test.rs @@ -0,0 +1,19 @@ +use crate::utilities::mta::range_proofs::tests::generate_init; +use crate::utilities::mta::{MessageA, MessageB}; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Scalar}; + +#[test] +fn test_mta() { + let alice_input = Scalar::::random(); + let (dlog_statement, ek_alice, dk_alice) = generate_init(); + let bob_input = Scalar::::random(); + let (m_a, _) = MessageA::a(&alice_input, &ek_alice, &[dlog_statement.clone()]); + let (m_b, beta, _, _) = MessageB::b(&bob_input, &ek_alice, m_a, &[dlog_statement]).unwrap(); + let alpha = m_b + .verify_proofs_get_alpha(&dk_alice, &alice_input) + .expect("wrong dlog or m_b"); + + let left = alpha.0 + beta; + let right = alice_input * bob_input; + assert_eq!(left, right); +} diff --git a/src/utilities/zk_pdl/mod.rs b/src/utilities/zk_pdl/mod.rs new file mode 100644 index 0000000..2d70352 --- /dev/null +++ b/src/utilities/zk_pdl/mod.rs @@ -0,0 +1,262 @@ +#![allow(non_snake_case)] +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +//! We use the proof as given in protocol 6.1 in https://eprint.iacr.org/2017/552.pdf +//! Statement: (c, pk, Q, G) +//! witness (x, r, sk) such that Q = xG, c = Enc(pk, x, r) and Dec(sk, c) = x. +//! note that because of the range proof, the proof is sound only for x < q/3 + +use std::ops::Shl; + +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; +use curv::cryptographic_primitives::commitments::traits::Commitment; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::Paillier; +use paillier::{Add, Decrypt, Encrypt, Mul}; +use paillier::{DecryptionKey, EncryptionKey, RawCiphertext, RawPlaintext}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use thiserror::Error; +use zk_paillier::zkproofs::IncorrectProof; +use zk_paillier::zkproofs::RangeProofNi; + +#[derive(Error, Debug)] +pub enum ZkPdlError { + #[error("zk pdl message2 failed")] + Message2, + #[error("zk pdl finalize failed")] + Finalize, +} + +#[derive(Clone)] +pub struct PDLStatement { + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, +} +#[derive(Clone)] +pub struct PDLWitness { + pub x: Scalar, + pub r: BigInt, + pub dk: DecryptionKey, +} + +#[derive(Debug, Clone)] +pub struct PDLVerifierState { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, + a: BigInt, + b: BigInt, + blindness: BigInt, + q_tag: Point, + c_hat: BigInt, +} + +#[derive(Debug, Clone)] +pub struct PDLProverState { + pub decommit: PDLProverDecommit, + pub alpha: BigInt, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLVerifierFirstMessage { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLProverFirstMessage { + pub c_hat: BigInt, + pub range_proof: RangeProofNi, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLVerifierSecondMessage { + pub a: BigInt, + pub b: BigInt, + pub blindness: BigInt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PDLProverDecommit { + pub q_hat: Point, + pub blindness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLProverSecondMessage { + pub decommit: PDLProverDecommit, +} + +pub struct Prover {} +pub struct Verifier {} + +impl Verifier { + pub fn message1(statement: &PDLStatement) -> (PDLVerifierFirstMessage, PDLVerifierState) { + let a_fe = Scalar::::random(); + let a = a_fe.to_bigint(); + let q = Scalar::::group_order(); + let q_sq = q.pow(2); + let b = BigInt::sample_below(&q_sq); + let b_fe = Scalar::::from(&b); + let b_enc = Paillier::encrypt(&statement.ek, RawPlaintext::from(b.clone())); + let ac = Paillier::mul( + &statement.ek, + RawCiphertext::from(statement.ciphertext.clone()), + RawPlaintext::from(a.clone()), + ); + let c_tag = Paillier::add(&statement.ek, ac, b_enc).0.into_owned(); + let ab_concat = a.clone() + b.clone().shl(a.bit_length()); + let blindness = BigInt::sample_below(q); + let c_tag_tag = HashCommitment::::create_commitment_with_user_defined_randomness( + &ab_concat, &blindness, + ); + let q_tag = &statement.Q * &a_fe + &statement.G * b_fe; + + ( + PDLVerifierFirstMessage { + c_tag: c_tag.clone(), + c_tag_tag: c_tag_tag.clone(), + }, + PDLVerifierState { + c_tag, + c_tag_tag, + a, + b, + blindness, + q_tag, + c_hat: BigInt::zero(), + }, + ) + } + + pub fn message2( + prover_first_messasge: &PDLProverFirstMessage, + statement: &PDLStatement, + state: &mut PDLVerifierState, + ) -> Result { + let decommit_message = PDLVerifierSecondMessage { + a: state.a.clone(), + b: state.b.clone(), + blindness: state.blindness.clone(), + }; + let range_proof_is_ok = + verify_range_proof(statement, &prover_first_messasge.range_proof).is_ok(); + state.c_hat = prover_first_messasge.c_hat.clone(); + if range_proof_is_ok { + Ok(decommit_message) + } else { + Err(ZkPdlError::Message2) + } + } + + pub fn finalize( + prover_first_message: &PDLProverFirstMessage, + prover_second_message: &PDLProverSecondMessage, + state: &PDLVerifierState, + ) -> Result<(), ZkPdlError> { + let c_hat_test = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(prover_second_message.decommit.q_hat.to_bytes(true).as_ref()), + &prover_second_message.decommit.blindness, + ); + + if prover_first_message.c_hat == c_hat_test + && prover_second_message.decommit.q_hat == state.q_tag + { + Ok(()) + } else { + Err(ZkPdlError::Finalize) + } + } +} + +impl Prover { + pub fn message1( + witness: &PDLWitness, + statement: &PDLStatement, + verifier_first_message: &PDLVerifierFirstMessage, + ) -> (PDLProverFirstMessage, PDLProverState) { + let c_tag = verifier_first_message.c_tag.clone(); + let alpha = Paillier::decrypt(&witness.dk, &RawCiphertext::from(c_tag)); + let alpha_fe = Scalar::::from(alpha.0.as_ref()); + let q_hat = &statement.G * alpha_fe; + let blindness = BigInt::sample_below(Scalar::::group_order()); + let c_hat = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(q_hat.to_bytes(true).as_ref()), + &blindness, + ); + // in parallel generate range proof: + let range_proof = generate_range_proof(statement, witness); + ( + PDLProverFirstMessage { c_hat, range_proof }, + PDLProverState { + decommit: PDLProverDecommit { blindness, q_hat }, + alpha: alpha.0.into_owned(), + }, + ) + } + + pub fn message2( + verifier_first_message: &PDLVerifierFirstMessage, + verifier_second_message: &PDLVerifierSecondMessage, + witness: &PDLWitness, + state: &PDLProverState, + ) -> Result { + let ab_concat = &verifier_second_message.a + + verifier_second_message + .b + .clone() + .shl(verifier_second_message.a.bit_length()); // b|a (in the paper it is a|b) + let c_tag_tag_test = + HashCommitment::::create_commitment_with_user_defined_randomness( + &ab_concat, + &verifier_second_message.blindness, + ); + let ax1 = &verifier_second_message.a * witness.x.to_bigint(); + let alpha_test = ax1 + &verifier_second_message.b; + if alpha_test == state.alpha && verifier_first_message.c_tag_tag == c_tag_tag_test { + Ok(PDLProverSecondMessage { + decommit: state.decommit.clone(), + }) + } else { + Err(ZkPdlError::Message2) + } + } +} + +fn generate_range_proof(statement: &PDLStatement, witness: &PDLWitness) -> RangeProofNi { + RangeProofNi::prove( + &statement.ek, + Scalar::::group_order(), + &statement.ciphertext, + &witness.x.to_bigint(), + &witness.r, + ) +} + +fn verify_range_proof( + statement: &PDLStatement, + range_proof: &RangeProofNi, +) -> Result<(), IncorrectProof> { + range_proof.verify(&statement.ek, &statement.ciphertext) +} + +#[cfg(test)] +mod test; diff --git a/src/utilities/zk_pdl/test.rs b/src/utilities/zk_pdl/test.rs new file mode 100644 index 0000000..3f4e699 --- /dev/null +++ b/src/utilities/zk_pdl/test.rs @@ -0,0 +1,58 @@ +#![allow(non_snake_case)] + +use curv::arithmetic::traits::*; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::core::Randomness; +use paillier::traits::{EncryptWithChosenRandomness, KeyGeneration}; +use paillier::Paillier; +use paillier::RawPlaintext; + +use crate::utilities::zk_pdl::{PDLStatement, PDLWitness, Prover, Verifier}; + +#[test] +fn test_zk_pdl() { + // pre-test: + + let (ek, dk) = Paillier::keypair().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + let x: Scalar = + Scalar::::from(&x.to_bigint().div_floor(&BigInt::from(3))); + + let Q = Point::generator() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + let statement = PDLStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + }; + let witness = PDLWitness { + x, + r: randomness.0, + dk, + }; + // + let (verifier_message1, mut verifier_state) = Verifier::message1(&statement); + let (prover_message1, prover_state) = + Prover::message1(&witness, &statement, &verifier_message1); + let verifier_message2 = + Verifier::message2(&prover_message1, &statement, &mut verifier_state).expect(""); + let prover_message2 = Prover::message2( + &verifier_message1, + &verifier_message2, + &witness, + &prover_state, + ) + .expect(""); + let result = Verifier::finalize(&prover_message1, &prover_message2, &verifier_state); + assert!(result.is_ok()); +} diff --git a/src/utilities/zk_pdl_with_slack/mod.rs b/src/utilities/zk_pdl_with_slack/mod.rs new file mode 100644 index 0000000..c9d2c70 --- /dev/null +++ b/src/utilities/zk_pdl_with_slack/mod.rs @@ -0,0 +1,202 @@ +#![allow(non_snake_case)] +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA 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. + + @license GPL-3.0+ +*/ + +//! We use the proof as given in proof PIi in https://eprint.iacr.org/2016/013.pdf. +//! This proof ws taken from the proof 6.3 (left side ) in https://www.cs.unc.edu/~reiter/papers/2004/IJIS.pdf +//! +//! Statement: (c, pk, Q, G) +//! witness (x, r) such that Q = xG, c = Enc(pk, x, r) +//! note that because of the range proof, the proof has a slack in the range: x in [-q^3, q^3] + +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::hashing::{Digest, DigestExt}; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::EncryptionKey; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ZkPdlWithSlackError { + #[error("zk pdl with slack verification failed")] + Verify, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PDLwSlackStatement { + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, + pub h1: BigInt, + pub h2: BigInt, + pub N_tilde: BigInt, +} +#[derive(Clone)] +pub struct PDLwSlackWitness { + pub x: Scalar, + pub r: BigInt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PDLwSlackProof { + z: BigInt, + u1: Point, + u2: BigInt, + u3: BigInt, + s1: BigInt, + s2: BigInt, + s3: BigInt, +} + +impl PDLwSlackProof { + pub fn prove(witness: &PDLwSlackWitness, statement: &PDLwSlackStatement) -> Self { + let q3 = Scalar::::group_order().pow(3); + let q_N_tilde = Scalar::::group_order() * &statement.N_tilde; + let q3_N_tilde = &q3 * &statement.N_tilde; + + let alpha = BigInt::sample_below(&q3); + let one = BigInt::one(); + let beta = BigInt::sample_range(&one, &(&statement.ek.n - &one)); + let rho = BigInt::sample_below(&q_N_tilde); + let gamma = BigInt::sample_below(&q3_N_tilde); + + let z = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &witness.x.to_bigint(), + &rho, + ); + let u1 = &statement.G * &Scalar::::from(&alpha); + let u2 = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &beta, + &statement.ek.nn, + &alpha, + &statement.ek.n, + ); + let u3 = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &alpha, + &gamma, + ); + + let e = Sha256::new() + .chain_bigint(&BigInt::from_bytes(statement.G.to_bytes(true).as_ref())) + .chain_bigint(&BigInt::from_bytes(statement.Q.to_bytes(true).as_ref())) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&z) + .chain_bigint(&BigInt::from_bytes(u1.to_bytes(true).as_ref())) + .chain_bigint(&u2) + .chain_bigint(&u3) + .result_bigint(); + + let s1 = &e * witness.x.to_bigint() + alpha; + let s2 = commitment_unknown_order(&witness.r, &beta, &statement.ek.n, &e, &BigInt::one()); + let s3 = &e * rho + gamma; + + PDLwSlackProof { + z, + u1, + u2, + u3, + s1, + s2, + s3, + } + } + + pub fn verify(&self, statement: &PDLwSlackStatement) -> Result<(), ZkPdlWithSlackError> { + let e = Sha256::new() + .chain_bigint(&BigInt::from_bytes(statement.G.to_bytes(true).as_ref())) + .chain_bigint(&BigInt::from_bytes(statement.Q.to_bytes(true).as_ref())) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&self.z) + .chain_bigint(&BigInt::from_bytes(self.u1.to_bytes(true).as_ref())) + .chain_bigint(&self.u2) + .chain_bigint(&self.u3) + .result_bigint(); + + let g_s1 = statement.G.clone() * &Scalar::::from(&self.s1); + let e_fe_neg: Scalar = + Scalar::::from(&(Scalar::::group_order() - &e)); + let y_minus_e = &statement.Q * &e_fe_neg; + let u1_test = g_s1 + y_minus_e; + + let u2_test_tmp = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &self.s2, + &statement.ek.nn, + &self.s1, + &statement.ek.n, + ); + let u2_test = commitment_unknown_order( + &u2_test_tmp, + &statement.ciphertext, + &statement.ek.nn, + &BigInt::one(), + &(-&e), + ); + + let u3_test_tmp = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &self.s1, + &self.s3, + ); + let u3_test = commitment_unknown_order( + &u3_test_tmp, + &self.z, + &statement.N_tilde, + &BigInt::one(), + &(-&e), + ); + + if self.u1 == u1_test && self.u2 == u2_test && self.u3 == u3_test { + Ok(()) + } else { + Err(ZkPdlWithSlackError::Verify) + } + } +} + +pub fn commitment_unknown_order( + h1: &BigInt, + h2: &BigInt, + N_tilde: &BigInt, + x: &BigInt, + r: &BigInt, +) -> BigInt { + let h1_x = BigInt::mod_pow(h1, x, N_tilde); + let h2_r = { + if r < &BigInt::zero() { + let h2_inv = BigInt::mod_inv(h2, N_tilde).unwrap(); + BigInt::mod_pow(&h2_inv, &(-r), N_tilde) + } else { + BigInt::mod_pow(h2, r, N_tilde) + } + }; + BigInt::mod_mul(&h1_x, &h2_r, N_tilde) +} + +#[cfg(test)] +mod test; diff --git a/src/utilities/zk_pdl_with_slack/test.rs b/src/utilities/zk_pdl_with_slack/test.rs new file mode 100644 index 0000000..fabcda4 --- /dev/null +++ b/src/utilities/zk_pdl_with_slack/test.rs @@ -0,0 +1,129 @@ +#![allow(non_snake_case)] +use crate::utilities::zk_pdl_with_slack::*; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}; +use curv::BigInt; +use paillier::core::Randomness; +use paillier::traits::{EncryptWithChosenRandomness, KeyGeneration}; +use paillier::Paillier; +use paillier::RawPlaintext; +use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + +#[test] +fn test_zk_pdl_with_slack() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair().keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + + let Q = Point::generator() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); +} + +#[test] +#[should_panic] +fn test_zk_pdl_with_slack_soundness() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair().keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + + let Q = Point::generator() * &x; + + // here we encrypt x + 1 instead of x: + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint() + BigInt::one()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); +}