├── .cargo └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── architecture.png ├── data_flow.png ├── manager_service.png └── signer_service.png ├── config └── sample.toml ├── run_signers.sh ├── src ├── auth.rs ├── bin │ ├── manager.rs │ └── signer.rs ├── common │ ├── mod.rs │ ├── secp256k1def.rs │ ├── signing_room.rs │ ├── types.rs │ └── utils.rs ├── config.rs ├── error │ ├── mod.rs │ └── tss_error.rs ├── lib.rs ├── manager │ ├── api.rs │ ├── constants.rs │ ├── handlers.rs │ ├── keygen.rs │ ├── mod.rs │ └── service.rs ├── queue │ ├── mod.rs │ └── rabbitmq.rs ├── signer │ ├── hd_keys.rs │ ├── mod.rs │ ├── secp256k1def.rs │ └── service.rs └── storage │ ├── mod.rs │ └── mongodb.rs └── tests └── manager_service_tests.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.aarch64-apple-darwin] 2 | rustflags = ["-L", "/opt/homebrew/lib"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /config/default.toml 3 | signer*.store 4 | /logs -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tss_network" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1.28", features = ["full"] } 8 | rocket = { version = "0.5.0-rc.2", features = ["json"] } 9 | serde = { version = "1.0", features = ["derive"] } 10 | serde_json = "1.0" 11 | mongodb = { version = "2.5", default-features = false, features = [ 12 | "async-std-runtime", 13 | ] } 14 | lapin = "2.1" 15 | futures = "0.3" 16 | futures-lite = "1.13" 17 | thiserror = "1.0" 18 | anyhow = "1.0" 19 | tracing = "0.1" 20 | tracing-subscriber = "0.3" 21 | config = "0.13" 22 | hex = "0.4" 23 | sha2 = "0.9" 24 | curv = { package = "curv-kzen", version = "0.9", default-features = false } 25 | paillier = { package = "kzen-paillier", version = "0.4" } 26 | multi-party-ecdsa = { git = "https://github.com/ZenGo-X/multi-party-ecdsa", branch = "master" } 27 | reqwest = { version = "0.11.7", features = ["json", "blocking"] } 28 | jsonwebtoken = "8.3" 29 | metrics = "0.23.0" 30 | metrics-exporter-prometheus = "0.11" 31 | lazy_static = "1.4" 32 | uuid = { version = "1.3", features = ["v4"] } 33 | aes-gcm = "0.9.4" 34 | zeroize = "1" 35 | hmac = "0.11" 36 | rand = "0.8.5" 37 | clap = {version = "4.5.21", features = ["derive"]} 38 | 39 | [dev-dependencies] 40 | tokio-test = "0.4" 41 | 42 | [[bin]] 43 | name = "manager" 44 | path = "src/bin/manager.rs" 45 | 46 | [[bin]] 47 | name = "signer" 48 | path = "src/bin/signer.rs" 49 | 50 | [lib] 51 | name = "tss_network" 52 | path = "src/lib.rs" 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TSS Network 2 | 3 | TSS Network is a robust implementation of Threshold Signature Scheme (TSS) for distributed key management and signing operations. 4 | Implemented protocol is GG18 for t/n threshold signing for ECDSA signatures 5 | 6 | ## Table of Contents 7 | 8 | - [TSS Network](#tss-network) 9 | - [Table of Contents](#table-of-contents) 10 | - [Features](#features) 11 | - [Architecture](#architecture) 12 | - [Components](#components) 13 | - [Manager Service](#manager-service) 14 | - [Signer Service](#signer-service) 15 | - [Common Components](#common-components) 16 | - [Application data flow](#application-data-flow) 17 | - [Installation](#installation) 18 | - [Configuration](#configuration) 19 | - [Usage](#usage) 20 | - [Starting the Manager Service](#starting-the-manager-service) 21 | - [Starting the Signer Service](#starting-the-signer-service) 22 | - [Test script](#test-script) 23 | - [API Endpoints](#api-endpoints) 24 | - [API Reference](#api-reference) 25 | - [Initiate Signing Request](#initiate-signing-request) 26 | - [Get Signature](#get-signature) 27 | - [How to test MPC](#how-to-test-mpc) 28 | - [Command to run test](#command-to-run-test) 29 | - [Security Considerations](#security-considerations) 30 | - [Contributing](#contributing) 31 | - [License](#license) 32 | 33 | ## Features 34 | 35 | - Distributed key generation and management 36 | - Threshold-based signing operations 37 | - Secure communication between signers 38 | - Fault tolerance (DONE) and Byzantine fault resistance (WIP) 39 | - Integration with RabbitMQ for message queuing 40 | - MongoDB storage for persistent data 41 | - RESTful API for easy integration 42 | - Comprehensive error handling and logging 43 | - Metrics and monitoring support (WIP) 44 | 45 | ## Architecture 46 | 47 | The TSS Network consists of two main components: 48 | 49 | 1. **Manager Service**: Coordinates the signing process, manages signing rooms, and handles API requests. 50 | 2. **Signer Service**: Participates in the distributed signing process and interacts with the Manager Service. 51 | 52 | ![architecture](assets/architecture.png) 53 | 54 | ## Components 55 | 56 | ### Manager Service 57 | 58 | The Manager Service is responsible for coordinating the signing process, managing signing rooms, and handling API requests. It is implemented in the following files: 59 | 60 | rust:src/manager/service.rs 61 | startLine: 1 62 | endLine: 86 63 | 64 | ![manager_service](assets/manager_service.png) 65 | 66 | ### Signer Service 67 | 68 | The Signer Service participates in the distributed signing process and interacts with the Manager Service. It is implemented in the following files: 69 | 70 | rust:src/signer/service.rs 71 | startLine: 1 72 | endLine: 883 73 | 74 | ![signer_service](assets/signer_service.png) 75 | 76 | ### Common Components 77 | 78 | The project includes several common components used by both the Manager and Signer services: 79 | 80 | rust:src/common/types.rs 81 | startLine: 53 82 | endLine: 92 83 | 84 | ### Application data flow 85 | ![data_flow](assets/data_flow.png) 86 | 87 | ## Installation 88 | 89 | 1. Clone the repository: 90 | ``` 91 | git clone https://github.com/your-username/tss-network.git 92 | cd tss-network 93 | ``` 94 | 95 | 2. Install Rust and Cargo (if not already installed): 96 | ``` 97 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 98 | ``` 99 | 100 | 3. Install dependencies: 101 | ``` 102 | cargo build 103 | ``` 104 | 105 | 4. Set up MongoDB and RabbitMQ (refer to their respective documentation for installation instructions). 106 | 107 | ## Configuration 108 | 109 | 1. Create a `config` directory in the project root. 110 | 111 | 2. Create configuration files for different environments: 112 | - `config/default.toml` 113 | - `config/development.toml` 114 | - `config/production.toml` 115 | 116 | 3. Set the required configuration parameters in these files. Example: 117 | 118 | ```toml 119 | mongodb_uri = "mongodb://localhost:27017" 120 | rabbitmq_uri = "amqp://localhost:5672" 121 | manager_url = "http://127.0.0.1" 122 | manager_port = 8080 123 | signing_timeout = 30 124 | threshold = 2 125 | total_parties = 3 126 | path = "0/1/2" 127 | signer_key_file = "" 128 | 129 | [security] 130 | jwt_secret = "development-secret-key-change-me-in-production" 131 | jwt_expiration = 3600 # 1 hour 132 | allowed_signer_ips = ["127.0.0.1", "127.0.0.1"] 133 | ``` 134 | 135 | 4. Set the `RUN_MODE` environment variable to specify the configuration to use: 136 | ``` 137 | export RUN_MODE=development 138 | ``` 139 | 140 | ## Usage 141 | 142 | ### Starting the Manager Service 143 | 144 | To start the Manager Service, run: 145 | ``` 146 | cargo run --bin manager 147 | ``` 148 | 149 | ### Starting the Signer Service 150 | 151 | To start the Signer Service, run: 152 | ``` 153 | cargo run --bin signer 154 | ``` 155 | 156 | ### Test script 157 | Script to run three signers for demonstration. 158 | ```bash 159 | ./run_signers.sh start all 160 | ``` 161 | 162 | ### API Endpoints 163 | 164 | The Manager Service exposes the following API endpoints: 165 | 166 | - `POST /sign`: Initiate a signing request 167 | - `GET /signing_result/`: Retrieve the signature for a completed request 168 | 169 | 170 | For detailed API usage, refer to the [API Reference](#api-reference) section. 171 | 172 | ## API Reference 173 | 174 | ### Initiate Signing Request 175 | 176 | **Endpoint:** `POST /sign` 177 | 178 | **Request Body:** 179 | 180 | ```json 181 | { 182 | "message": "Message to sign" // Any string message to sign 183 | } 184 | ``` 185 | 186 | **Response:** 187 | ```json 188 | { 189 | "request_id": "550e8400-e29b-41d4-a716-446655440000", 190 | "status": "Pending" 191 | } 192 | ``` 193 | 194 | 195 | ### Get Signature 196 | 197 | It will return signature when ```status``` is ```Completed```. 198 | 199 | **Endpoint:** `GET /signing_result/` 200 | 201 | **Response:** 202 | ```json 203 | { 204 | "request_id": "994ca821-8462-432a-a47e-97c898c8fe1b", 205 | "message": [ 206 | 83, 207 | 117, 208 | 110, 209 | 105, 210 | 108 211 | ], 212 | "status": "Completed", 213 | "signature": { 214 | "r": "ed5f91d15045f73ef7f1067b20f00914697cc09284deb72967ebe091b4e78f57", 215 | "s": "2fe1089e63086908dbf93b3ad43a6b672194ea94c53575a8a8210c01ccb04347", 216 | "status": "signature_ready", 217 | "recid": 1, 218 | "x": "e90afacf19e50498e886d2d2a5b22ca34ecfe0b3f063b8d7f1e5eabd37b5f8d8", 219 | "y": "aa5d7c0bbf991462d5884999b00b0826d1857dfdd6d07d0b7ce7fed47d5bbf77", 220 | "msg_int": [ 221 | 83, 222 | 117, 223 | 110, 224 | 105, 225 | 108 226 | ] 227 | } 228 | } 229 | ``` 230 | ### How to test MPC 231 | 232 | Make sure these services are running locally 233 | 234 | ``` 235 | // MongoDB 236 | "mongodb://localhost:27017" 237 | 238 | // RabbitMQ 239 | "amqp://localhost:5672" 240 | ``` 241 | #### Command to run test 242 | 243 | ``` 244 | cargo test --package tss_network --test manager_service_tests -- test_signing_flow --exact --show-output 245 | ``` 246 | 247 | 248 | ## Security Considerations (Posterity) 249 | 250 | 1. **Key Management**: Ensure that private key shares are securely stored and never transmitted in plain text. 251 | 2. **Network Security**: Use TLS/SSL for all network communications between components. 252 | 3. **Access Control**: Implement strong authentication and authorization mechanisms for API access. 253 | 4. **Secure Configuration**: Keep all configuration files, especially those containing sensitive information, secure and separate from the codebase. 254 | 5. **Monitoring and Alerting**: Implement comprehensive logging and monitoring to detect and respond to any suspicious activities. 255 | 6. **Regular Audits**: Conduct regular security audits and penetration testing of the system. (1 Audit done) 256 | 7. **Dependency Management**: Regularly update and patch all dependencies to address any known vulnerabilities. 257 | 258 | ## Contributing 259 | 260 | We welcome contributions to the TSS Network project. Please follow these steps to contribute: 261 | 262 | 1. Fork the repository 263 | 2. Create a new branch for your feature or bug fix 264 | 3. Make your changes and commit them with clear, descriptive messages 265 | 4. Push your changes to your fork 266 | 5. Submit a pull request to the main repository 267 | 268 | Please ensure that your code adheres to the existing style conventions and includes appropriate tests. 269 | 270 | ## Acknowledgements 271 | This work is heavy inspired from and is an extension of work done by ZenGo [here](https://github.com/ZenGo-X/multi-party-ecdsa/) 272 | 273 | ## License 274 | 275 | This project is licensed under the GPL v3 License. See the [LICENSE](LICENSE) file for details. 276 | -------------------------------------------------------------------------------- /assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerius-labs/tss-mpc-node/62831bb973f6d652c688b51d4d8007f7c6e6e974/assets/architecture.png -------------------------------------------------------------------------------- /assets/data_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerius-labs/tss-mpc-node/62831bb973f6d652c688b51d4d8007f7c6e6e974/assets/data_flow.png -------------------------------------------------------------------------------- /assets/manager_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerius-labs/tss-mpc-node/62831bb973f6d652c688b51d4d8007f7c6e6e974/assets/manager_service.png -------------------------------------------------------------------------------- /assets/signer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerius-labs/tss-mpc-node/62831bb973f6d652c688b51d4d8007f7c6e6e974/assets/signer_service.png -------------------------------------------------------------------------------- /config/sample.toml: -------------------------------------------------------------------------------- 1 | mongodb_uri = "" 2 | rabbitmq_uri = "" 3 | manager_url = "" 4 | manager_port = "" 5 | signing_timeout = "" 6 | threshold = "" 7 | total_parties = "" 8 | path = "" 9 | signer1_key_file = "" 10 | signer2_key_file = "" 11 | signer3_key_file = "" 12 | -------------------------------------------------------------------------------- /run_signers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Configuration 4 | KEYS_DIR="./" 5 | LOG_DIR="./logs" 6 | BINARY_NAME="tss_network" 7 | 8 | # Create necessary directories 9 | mkdir -p "$LOG_DIR" 10 | mkdir -p "$KEYS_DIR" 11 | 12 | # Function to validate key file 13 | validate_key_file() { 14 | local key_file=$1 15 | if [ ! -f "$key_file" ]; then 16 | echo "Error: Key file not found: $key_file" 17 | return 1 18 | fi 19 | if [ ! -r "$key_file" ]; then 20 | echo "Error: Key file not readable: $key_file" 21 | return 1 22 | fi 23 | return 0 24 | } 25 | 26 | # Function to start a signer 27 | start_signer() { 28 | local signer_id=$1 29 | local key_file="$KEYS_DIR/signer${signer_id}.store" 30 | local log_file="$LOG_DIR/signer${signer_id}.log" 31 | local pid_file="$LOG_DIR/signer${signer_id}.pid" 32 | 33 | # Validate key file 34 | if ! validate_key_file "$key_file"; then 35 | return 1 36 | fi 37 | 38 | # Check if signer is already running 39 | if [ -f "$pid_file" ]; then 40 | local pid=$(cat "$pid_file") 41 | if kill -0 "$pid" 2>/dev/null; then 42 | echo "Signer $signer_id is already running with PID: $pid" 43 | return 1 44 | else 45 | rm "$pid_file" 46 | fi 47 | fi 48 | 49 | echo "Starting signer $signer_id with key file: $key_file" 50 | 51 | # Start the signer with nohup 52 | RUST_LOG=info nohup cargo run --bin signer -- \ 53 | --key-file "$key_file" \ 54 | >> "$log_file" 2>&1 & 55 | 56 | # Save PID 57 | local pid=$! 58 | echo $pid > "$pid_file" 59 | echo "Signer $signer_id started with PID: $pid" 60 | echo "Log file: $log_file" 61 | } 62 | 63 | # Function to stop a signer 64 | stop_signer() { 65 | local signer_id=$1 66 | local pid_file="$LOG_DIR/signer${signer_id}.pid" 67 | 68 | if [ -f "$pid_file" ]; then 69 | local pid=$(cat "$pid_file") 70 | if kill -0 "$pid" 2>/dev/null; then 71 | echo "Stopping signer $signer_id (PID: $pid)" 72 | kill "$pid" 73 | rm "$pid_file" 74 | else 75 | echo "Signer $signer_id is not running" 76 | rm "$pid_file" 77 | fi 78 | else 79 | echo "No PID file found for signer $signer_id" 80 | fi 81 | } 82 | 83 | # Function to check status 84 | check_status() { 85 | local signer_id=$1 86 | local pid_file="$LOG_DIR/signer${signer_id}.pid" 87 | 88 | if [ -f "$pid_file" ]; then 89 | local pid=$(cat "$pid_file") 90 | if kill -0 "$pid" 2>/dev/null; then 91 | echo "Signer $signer_id is running (PID: $pid)" 92 | return 0 93 | else 94 | echo "Signer $signer_id is not running (stale PID file)" 95 | rm "$pid_file" 96 | return 1 97 | fi 98 | else 99 | echo "Signer $signer_id is not running" 100 | return 1 101 | fi 102 | } 103 | 104 | # Function to view logs 105 | view_logs() { 106 | local signer_id=$1 107 | local log_file="$LOG_DIR/signer${signer_id}.log" 108 | 109 | if [ -f "$log_file" ]; then 110 | tail -f "$log_file" 111 | else 112 | echo "No log file found for signer $signer_id" 113 | fi 114 | } 115 | 116 | # Function to start all signers 117 | start_all() { 118 | echo "Starting all signers..." 119 | for id in {1..3}; do 120 | start_signer $id 121 | sleep 2 # Small delay between starts 122 | done 123 | } 124 | 125 | # Function to stop all signers 126 | stop_all() { 127 | echo "Stopping all signers..." 128 | for id in {1..3}; do 129 | stop_signer $id 130 | done 131 | } 132 | 133 | # Main command processing 134 | case "$1" in 135 | start) 136 | if [ "$2" = "all" ]; then 137 | start_all 138 | elif [ -n "$2" ]; then 139 | start_signer "$2" 140 | else 141 | echo "Usage: $0 start {all|}" 142 | fi 143 | ;; 144 | stop) 145 | if [ "$2" = "all" ]; then 146 | stop_all 147 | elif [ -n "$2" ]; then 148 | stop_signer "$2" 149 | else 150 | echo "Usage: $0 stop {all|}" 151 | fi 152 | ;; 153 | restart) 154 | if [ -z "$2" ]; then 155 | echo "Usage: $0 restart " 156 | exit 1 157 | fi 158 | stop_signer "$2" 159 | sleep 2 160 | start_signer "$2" 161 | ;; 162 | status) 163 | if [ -z "$2" ]; then 164 | echo "Status of all signers:" 165 | for id in {1..3}; do 166 | check_status $id 167 | done 168 | else 169 | check_status "$2" 170 | fi 171 | ;; 172 | logs) 173 | if [ -z "$2" ]; then 174 | echo "Usage: $0 logs " 175 | exit 1 176 | fi 177 | view_logs "$2" 178 | ;; 179 | *) 180 | echo "Usage: $0 {start|stop|restart|status|logs} {all|}" 181 | exit 1 182 | ;; 183 | esac 184 | 185 | exit 0 -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; 2 | use rocket::http::Status; 3 | use rocket::request::{FromRequest, Outcome}; 4 | use rocket::{Request, State}; 5 | use serde::{Deserialize, Serialize}; 6 | use std::sync::Arc; 7 | use std::time::{SystemTime, UNIX_EPOCH}; 8 | use tracing::{debug, warn}; 9 | 10 | use crate::config::Settings; 11 | use crate::error::TssError; 12 | 13 | #[derive(Debug, Serialize, Deserialize, PartialEq)] 14 | pub enum Role { 15 | Public, // For general API access 16 | Signer, // For signer-specific endpoints 17 | Admin, // For administrative functions 18 | } 19 | #[derive(Debug, Serialize, Deserialize)] 20 | pub struct Claims { 21 | pub sub: String, // Subject (user ID) 22 | pub exp: usize, // Expiration time 23 | pub role: Role, // User role 24 | pub iat: usize, // Issued at 25 | } 26 | 27 | #[derive(Debug)] 28 | pub enum AuthError { 29 | Missing, 30 | Invalid, 31 | Expired, 32 | WrongRole, 33 | IpNotAllowed, 34 | } 35 | 36 | pub struct AuthenticatedUser { 37 | pub user_id: String, 38 | pub role: Role, 39 | } 40 | 41 | // New struct for IP-based authentication 42 | pub struct SignerAuth; 43 | 44 | // New struct for IP-based authentication 45 | #[rocket::async_trait] 46 | impl<'r> FromRequest<'r> for SignerAuth { 47 | type Error = AuthError; 48 | 49 | async fn from_request(request: &'r Request<'_>) -> Outcome { 50 | let settings = request 51 | .guard::<&State>>() 52 | .await 53 | .expect("Settings not found in request state"); 54 | 55 | if let Some(client_ip) = request.client_ip() { 56 | debug!("Signer API request from IP: {}", client_ip); 57 | if settings.is_ip_whitelisted(client_ip) { 58 | Outcome::Success(SignerAuth) 59 | } else { 60 | warn!( 61 | "Unauthorized signer API access attempt from IP: {}", 62 | client_ip 63 | ); 64 | Outcome::Error((Status::Unauthorized, AuthError::IpNotAllowed)) 65 | } 66 | } else { 67 | warn!("No client IP found in signer API request"); 68 | Outcome::Error((Status::Unauthorized, AuthError::IpNotAllowed)) 69 | } 70 | } 71 | } 72 | 73 | #[rocket::async_trait] 74 | impl<'r> FromRequest<'r> for AuthenticatedUser { 75 | type Error = AuthError; 76 | 77 | async fn from_request(request: &'r Request<'_>) -> Outcome { 78 | let settings = request 79 | .guard::<&State>>() 80 | .await 81 | .expect("Settings not found in request state"); 82 | 83 | // Get and validate JWT token 84 | let token = request 85 | .headers() 86 | .get_one("Authorization") 87 | .map(|value| value.replace("Bearer ", "")); 88 | 89 | match token { 90 | Some(token) => match validate_token(&token, &settings.inner().security.jwt_secret) { 91 | Ok(claims) => { 92 | // Verify role permissions for endpoint 93 | if !has_permission_for_endpoint( 94 | &claims.role, 95 | request.uri().path().to_string().as_str(), 96 | ) { 97 | return Outcome::Error((Status::Forbidden, AuthError::WrongRole)); 98 | } 99 | 100 | Outcome::Success(AuthenticatedUser { 101 | user_id: claims.sub, 102 | role: claims.role, 103 | }) 104 | } 105 | Err(_) => Outcome::Error((Status::Unauthorized, AuthError::Invalid)), 106 | }, 107 | None => Outcome::Error((Status::Unauthorized, AuthError::Missing)), 108 | } 109 | } 110 | } 111 | 112 | fn has_permission_for_endpoint(role: &Role, path: &str) -> bool { 113 | match (role, path) { 114 | // Public endpoints 115 | (Role::Public, "/sign") => true, 116 | (Role::Public, path) if path.starts_with("/signing_result/") => true, 117 | 118 | // Signer endpoints 119 | (Role::Signer, _) => false, 120 | 121 | // Admin endpoints 122 | (Role::Admin, _) => true, 123 | 124 | _ => false, 125 | } 126 | } 127 | 128 | pub fn validate_token(token: &str, secret: &str) -> Result { 129 | let validation = Validation::default(); 130 | let token_data = decode::( 131 | token, 132 | &DecodingKey::from_secret(secret.as_ref()), 133 | &validation, 134 | )?; 135 | 136 | Ok(token_data.claims) 137 | } 138 | 139 | pub fn create_token(user_id: &str, role: Role, settings: &Settings) -> Result { 140 | match role { 141 | Role::Signer => { 142 | return Err(TssError::AuthError( 143 | "Cannot create token for Signer role".into(), 144 | )) 145 | } 146 | _ => (), 147 | } 148 | 149 | let now = SystemTime::now() 150 | .duration_since(UNIX_EPOCH) 151 | .unwrap() 152 | .as_secs() as usize; 153 | 154 | let claims = Claims { 155 | sub: user_id.to_string(), 156 | exp: now + 3600, // Expires in 1 hour 157 | role, 158 | iat: now, 159 | }; 160 | 161 | encode( 162 | &Header::default(), 163 | &claims, 164 | &EncodingKey::from_secret(settings.security.jwt_secret.as_ref()), 165 | ) 166 | .map_err(|e| TssError::JWTError(e.to_string())) 167 | } 168 | -------------------------------------------------------------------------------- /src/bin/manager.rs: -------------------------------------------------------------------------------- 1 | use rocket::routes; 2 | use rocket::{figment::Figment, Config}; 3 | use std::sync::Arc; 4 | use tss_network::config::Settings; 5 | use tss_network::manager::api::{ 6 | generate_keys, generate_test_token, get_key_gen_result, get_signing_result, sign, 7 | }; 8 | use tss_network::manager::handlers::{get, set, signup_keygen, signup_sign, update_signing_result}; 9 | use tss_network::manager::service::ManagerService; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<(), Box> { 13 | // Initialize tracing 14 | tracing_subscriber::fmt::init(); 15 | 16 | // Load configuration 17 | let settings = Settings::new().expect("Failed to load configuration"); 18 | 19 | // Initialize ManagerService 20 | let manager_service: Arc = Arc::new( 21 | ManagerService::new( 22 | &settings.mongodb_uri, 23 | &settings.rabbitmq_uri, 24 | settings.threshold, 25 | settings.total_parties, 26 | ) 27 | .await?, 28 | ); 29 | 30 | let manager_service_for_rocket = manager_service.clone(); 31 | 32 | let ip = settings 33 | .manager_url 34 | .split("://") 35 | .nth(1) 36 | .unwrap_or(&settings.manager_url); 37 | let figment = Figment::from(Config::default()) 38 | .merge(("address", ip)) 39 | .merge(("port", settings.manager_port)); 40 | let config: rocket::Config = figment.extract().expect("Failed to extract Rocket config"); 41 | 42 | let rocket_future = rocket::custom(config) 43 | .manage(manager_service_for_rocket) 44 | .manage(Arc::new(settings)) 45 | .mount( 46 | "/", 47 | routes![ 48 | sign, 49 | signup_sign, 50 | set, 51 | get, 52 | get_signing_result, 53 | update_signing_result, 54 | generate_keys, 55 | signup_keygen, 56 | get_key_gen_result, 57 | generate_test_token 58 | ], 59 | ) 60 | .launch(); 61 | 62 | tokio::select! { 63 | _ = rocket_future => println!("Rocket server shut down"), 64 | } 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /src/bin/signer.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::{path::PathBuf, sync::Arc}; 3 | use tss_network::config::Settings; 4 | use tss_network::signer::service::SignerService; 5 | 6 | #[derive(Parser, Debug)] 7 | #[command(author, version, about, long_about = None)] 8 | struct Args { 9 | /// Path to the key file 10 | #[arg(short, long)] 11 | key_file: Option, 12 | } 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<(), Box> { 16 | tracing_subscriber::fmt::init(); 17 | 18 | // Parse command line arguments 19 | let args = Args::parse(); 20 | let settings = Settings::new().expect("Failed to load configuration"); 21 | 22 | // Get and validate key file 23 | let key_file = get_key_file(&args, &settings).map_err(|e| e)?; 24 | 25 | let signer_service: Arc = Arc::new( 26 | SignerService::new( 27 | &settings.manager_url, 28 | &settings.manager_port, 29 | &settings.rabbitmq_uri, 30 | &key_file, 31 | &settings.threshold, 32 | &settings.total_parties, 33 | &settings.path, 34 | ) 35 | .await?, 36 | ); 37 | 38 | signer_service.run().await?; 39 | Ok(()) 40 | } 41 | 42 | fn get_key_file(args: &Args, settings: &Settings) -> Result { 43 | // First try CLI argument 44 | if let Some(key_path) = &args.key_file { 45 | // Validate the CLI provided path 46 | if !key_path.exists() { 47 | return Err(format!( 48 | "Key file from CLI argument not found: {}", 49 | key_path.display() 50 | )); 51 | } 52 | return Ok(key_path 53 | .to_str() 54 | .ok_or("Invalid path encoding")? 55 | .to_string()); 56 | } 57 | 58 | // Then try config file 59 | if !settings.signer_key_file.is_empty() { 60 | let config_path = PathBuf::from(&settings.signer_key_file); 61 | if !config_path.exists() { 62 | return Err(format!( 63 | "Key file from config not found: {}", 64 | settings.signer_key_file 65 | )); 66 | } 67 | return Ok(settings.signer_key_file.clone()); 68 | } 69 | 70 | // If neither is provided, return error 71 | Err( 72 | "No key file provided. Please specify either in config file or via --key-file argument" 73 | .to_string(), 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod secp256k1def; 2 | pub mod signing_room; 3 | pub mod types; 4 | pub mod utils; 5 | 6 | pub use signing_room::*; 7 | pub use types::*; 8 | pub use utils::*; 9 | -------------------------------------------------------------------------------- /src/common/secp256k1def.rs: -------------------------------------------------------------------------------- 1 | use curv::elliptic::curves::{Point, Scalar, Secp256k1}; 2 | 3 | pub type FE = Scalar; 4 | pub type GE = Point; 5 | -------------------------------------------------------------------------------- /src/common/signing_room.rs: -------------------------------------------------------------------------------- 1 | use crate::common::types::*; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | use std::env; 5 | use std::time::{SystemTime, UNIX_EPOCH}; 6 | use uuid::Uuid; 7 | 8 | pub const SIGNUP_TIMEOUT_ENV: &str = "TSS_MANAGER_SIGNUP_TIMEOUT"; 9 | pub const SIGNUP_TIMEOUT_DEFAULT: &str = "2"; 10 | 11 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 12 | pub struct SigningRoom { 13 | pub room_id: String, 14 | pub room_uuid: String, 15 | pub room_size: u16, 16 | pub member_info: HashMap, 17 | pub last_stage: String, 18 | } 19 | 20 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 21 | pub struct SigningPartyInfo { 22 | pub party_id: String, 23 | pub party_order: u16, 24 | pub last_ping: u64, 25 | } 26 | 27 | impl SigningRoom { 28 | pub fn new(room_id: String, size: u16) -> Self { 29 | SigningRoom { 30 | room_size: size, 31 | member_info: Default::default(), 32 | room_id, 33 | last_stage: "signup".to_string(), 34 | room_uuid: Uuid::new_v4().to_string(), 35 | } 36 | } 37 | 38 | fn new_sign_party(party_order: u16) -> SigningPartySignup { 39 | SigningPartySignup { 40 | party_order, 41 | room_uuid: "".to_string(), 42 | party_uuid: Uuid::new_v4().to_string(), 43 | total_joined: 0, 44 | } 45 | } 46 | 47 | pub fn is_full(&self) -> bool { 48 | self.member_info.len() >= usize::from(self.room_size) 49 | } 50 | 51 | fn is_timeout(party: &SigningPartyInfo) -> bool { 52 | let now = SystemTime::now() 53 | .duration_since(UNIX_EPOCH) 54 | .unwrap() 55 | .as_secs(); 56 | let timeout = u64::from_str_radix( 57 | env::var(SIGNUP_TIMEOUT_ENV) 58 | .unwrap_or(SIGNUP_TIMEOUT_DEFAULT.to_string()) 59 | .as_str(), 60 | 10, 61 | ) 62 | .unwrap(); 63 | 64 | party.last_ping < now - timeout 65 | } 66 | 67 | pub fn add_party(&mut self, party_number: u16) -> SigningPartySignup { 68 | let party_signup = 69 | SigningRoom::new_sign_party(u16::try_from(self.member_info.len()).unwrap() + 1); 70 | self.member_info.insert( 71 | party_number, 72 | SigningPartyInfo { 73 | party_id: party_signup.party_uuid.clone(), 74 | party_order: party_signup.party_order, 75 | last_ping: SystemTime::now() 76 | .duration_since(SystemTime::UNIX_EPOCH) 77 | .unwrap() 78 | .as_secs(), 79 | }, 80 | ); 81 | 82 | party_signup.clone() 83 | } 84 | 85 | pub fn replace_party(&mut self, party_number: u16) -> SigningPartySignup { 86 | let old_party = self.member_info.get(&party_number).unwrap(); 87 | let party_signup = SigningRoom::new_sign_party(old_party.party_order); 88 | *self.member_info.get_mut(&party_number).unwrap() = SigningPartyInfo { 89 | party_id: party_signup.party_uuid.clone(), 90 | party_order: party_signup.party_order, 91 | last_ping: SystemTime::now() 92 | .duration_since(SystemTime::UNIX_EPOCH) 93 | .unwrap() 94 | .as_secs(), 95 | }; 96 | 97 | party_signup.clone() 98 | } 99 | 100 | pub fn are_all_members_active(&self) -> bool { 101 | self.member_info 102 | .values() 103 | .all(|x| !SigningRoom::is_timeout(x)) 104 | } 105 | 106 | pub fn are_all_members_inactive(&self) -> bool { 107 | self.is_full() && self.member_info.values().all(SigningRoom::is_timeout) 108 | } 109 | 110 | pub fn is_member_active(&self, party_number: u16) -> bool { 111 | let party_data = self.member_info.get(&party_number).unwrap(); 112 | 113 | !SigningRoom::is_timeout(party_data) 114 | } 115 | 116 | pub fn update_ping(&mut self, party_number: u16) -> SigningPartySignup { 117 | let party_data = self.member_info.get_mut(&party_number).unwrap(); 118 | party_data.last_ping = SystemTime::now() 119 | .duration_since(SystemTime::UNIX_EPOCH) 120 | .unwrap() 121 | .as_secs(); 122 | if self.is_full() && self.active_members().len() >= usize::from(self.room_size) { 123 | self.close_signup_window(); 124 | } 125 | self.get_signup_info(party_number) 126 | } 127 | 128 | fn close_signup_window(&mut self) { 129 | let active_members = self.active_members(); 130 | if self.is_full() && active_members.len() >= usize::from(self.room_size) { 131 | self.last_stage = "terminated".to_string(); 132 | let mut new_order = 1; 133 | for (key, value) in self.member_info.iter_mut() { 134 | if active_members.contains_key(key) { 135 | value.party_order = new_order; 136 | new_order += 1; 137 | } 138 | } 139 | } 140 | } 141 | 142 | pub fn has_member(&self, party_number: u16, party_uuid: String) -> bool { 143 | self.member_info.contains_key(&party_number) 144 | && self.member_info.get(&party_number).unwrap().party_id == party_uuid 145 | } 146 | 147 | fn active_members(&self) -> HashMap { 148 | self.member_info 149 | .clone() 150 | .into_iter() 151 | .filter(|(_key, x)| !SigningRoom::is_timeout(x)) 152 | .collect() 153 | } 154 | 155 | pub fn get_signup_info(&self, party_number: u16) -> SigningPartySignup { 156 | let member_info = self.member_info.get(&party_number).unwrap(); 157 | let room_uuid = if self.last_stage == "signup" { 158 | "".to_string() 159 | } else { 160 | self.room_uuid.clone() 161 | }; 162 | SigningPartySignup { 163 | party_order: member_info.party_order, 164 | party_uuid: member_info.party_id.clone(), 165 | room_uuid, 166 | total_joined: u16::try_from(self.active_members().len()).unwrap(), 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/common/types.rs: -------------------------------------------------------------------------------- 1 | use mongodb::bson::Bson; 2 | use serde::{Deserialize, Serialize}; 3 | use std::fmt; 4 | use std::fmt::{Display, Formatter}; 5 | use std::str::FromStr; 6 | 7 | pub type Key = String; 8 | 9 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 10 | pub struct MessageToSignStored { 11 | pub request_id: String, 12 | pub message: Vec, 13 | pub status: MessageStatus, 14 | pub signature: Option, 15 | } 16 | 17 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 18 | pub enum MessageStatus { 19 | Pending, 20 | InProgress, 21 | Completed, 22 | } 23 | 24 | impl From for Bson { 25 | fn from(status: MessageStatus) -> Self { 26 | match status { 27 | MessageStatus::Pending => Bson::String("Pending".to_string()), 28 | MessageStatus::InProgress => Bson::String("InProgress".to_string()), 29 | MessageStatus::Completed => Bson::String("Completed".to_string()), 30 | } 31 | } 32 | } 33 | 34 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 35 | pub struct SignatureData { 36 | r: String, 37 | s: String, 38 | status: String, 39 | recid: i32, 40 | x: String, 41 | y: String, 42 | msg_int: Vec, 43 | } 44 | 45 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 46 | pub struct SignerResult { 47 | pub request_id: String, 48 | pub signature: SignatureData, 49 | } 50 | 51 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 52 | pub struct AEAD { 53 | pub ciphertext: Vec, 54 | pub tag: Vec, 55 | pub nonce: Vec, 56 | } 57 | 58 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 59 | pub struct PartySignup { 60 | pub number: u16, 61 | pub uuid: String, 62 | } 63 | 64 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 65 | pub struct Index { 66 | pub key: Key, 67 | } 68 | 69 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 70 | pub struct Entry { 71 | pub key: Key, 72 | pub value: String, 73 | } 74 | 75 | #[derive(Debug, Serialize, Deserialize)] 76 | pub struct Params { 77 | pub parties: u16, 78 | pub threshold: u16, 79 | pub path: String, 80 | } 81 | 82 | #[derive(Clone, Debug, Serialize, Deserialize)] 83 | pub struct KeyGenParams { 84 | pub parties: u16, 85 | pub threshold: u16, 86 | } 87 | 88 | #[derive(Clone, Debug, Serialize, Deserialize)] 89 | pub struct KeysToStore { 90 | pub request_id: String, 91 | pub status: MessageStatus, 92 | pub key_gen_params: KeyGenParams, 93 | pub keys: Option>, 94 | } 95 | 96 | #[derive(Clone, Debug, Serialize, Deserialize)] 97 | pub struct KeyGenRequest { 98 | pub id: String, 99 | pub keygen_params: KeyGenParams, 100 | } 101 | 102 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 103 | pub struct PartySignupRequestBody { 104 | pub threshold: u16, 105 | pub room_id: String, 106 | pub party_number: u16, 107 | pub party_uuid: String, 108 | } 109 | 110 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 111 | pub struct SigningPartySignup { 112 | pub party_order: u16, 113 | pub party_uuid: String, 114 | pub room_uuid: String, 115 | pub total_joined: u16, 116 | } 117 | 118 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 119 | pub struct ManagerError { 120 | pub error: String, 121 | } 122 | 123 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 124 | pub struct SigningRequest { 125 | pub id: String, 126 | pub message: Vec, 127 | // pub threshold: usize, 128 | // pub total_parties: usize, 129 | } 130 | 131 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 132 | pub struct SigningResult { 133 | pub request_id: String, 134 | pub signature: Option>, 135 | pub status: SigningStatus, 136 | } 137 | 138 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 139 | pub enum SigningStatus { 140 | Pending, 141 | InProgress, 142 | Completed, 143 | Failed, 144 | } 145 | 146 | impl Display for SigningStatus { 147 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 148 | match self { 149 | SigningStatus::Pending => write!(f, "Pending"), 150 | SigningStatus::InProgress => write!(f, "InProgress"), 151 | SigningStatus::Completed => write!(f, "Completed"), 152 | SigningStatus::Failed => write!(f, "Failed"), 153 | } 154 | } 155 | } 156 | 157 | impl FromStr for SigningStatus { 158 | type Err = String; 159 | 160 | fn from_str(s: &str) -> Result { 161 | match s { 162 | "Pending" => Ok(SigningStatus::Pending), 163 | "InProgress" => Ok(SigningStatus::InProgress), 164 | "Completed" => Ok(SigningStatus::Completed), 165 | "Failed" => Ok(SigningStatus::Failed), 166 | _ => Err(format!("Invalid SigningStatus: {}", s)), 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/common/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{common::types::*, manager::constants::NONCE_SIZE}; 2 | use aes_gcm::{ 3 | aead::{Aead, NewAead, Payload}, 4 | Aes256Gcm, Nonce, 5 | }; 6 | use anyhow::{anyhow, Result}; 7 | use rand::{rngs::OsRng, RngCore}; 8 | use reqwest::Client; 9 | use serde_json::Value; 10 | use sha2::{Digest, Sha256}; 11 | use std::{thread, time}; 12 | 13 | pub fn aes_encrypt(key: &[u8], plaintext: &[u8]) -> AEAD { 14 | let mut key_sized = [0u8; 32]; 15 | key_sized[(32 - key.len())..].copy_from_slice(key); 16 | let aes_key = aes_gcm::Key::from_slice(&key_sized); 17 | let cipher = Aes256Gcm::new(aes_key); 18 | 19 | let mut nonce_bytes: [u8; NONCE_SIZE] = [0u8; NONCE_SIZE]; 20 | OsRng.fill_bytes(&mut nonce_bytes); 21 | let nonce = Nonce::from_slice(&nonce_bytes); 22 | 23 | let out_tag = [0u8; 16]; 24 | 25 | let text_payload = Payload { 26 | msg: plaintext, 27 | aad: &out_tag.as_slice(), // out tag is set to default value, no authentication data 28 | }; 29 | 30 | let ciphertext = cipher 31 | .encrypt(nonce, text_payload) 32 | .expect("encryption failure!"); 33 | 34 | AEAD { 35 | ciphertext, 36 | tag: out_tag.to_vec(), 37 | nonce: nonce.to_vec(), 38 | } 39 | } 40 | 41 | pub fn aes_decrypt(key: &[u8], aead_pack: AEAD) -> Vec { 42 | let mut key_sized = [0u8; 32]; 43 | key_sized[(32 - key.len())..].copy_from_slice(key); 44 | let aes_key = aes_gcm::Key::from_slice(&key_sized); 45 | let gcm = Aes256Gcm::new(aes_key); 46 | 47 | let nonce = Nonce::from_slice(&aead_pack.nonce); 48 | 49 | let text_payload = Payload { 50 | msg: aead_pack.ciphertext.as_slice(), 51 | aad: aead_pack.tag.as_slice(), 52 | }; 53 | 54 | let out = gcm.decrypt(nonce, text_payload); 55 | out.unwrap_or_default() 56 | } 57 | 58 | pub async fn postb(addr: &str, client: &Client, path: &str, body: T) -> Option 59 | where 60 | T: serde::ser::Serialize, 61 | { 62 | let retries = 3; 63 | let retry_delay = time::Duration::from_millis(250); 64 | let endpoint = format!("{}/{}", addr, path); 65 | for _ in 0..retries { 66 | match client.post(&endpoint).json(&body).send().await { 67 | Ok(response) => return Some(response.text().await.unwrap()), 68 | Err(_) => thread::sleep(retry_delay), 69 | } 70 | } 71 | None 72 | } 73 | 74 | pub async fn broadcast( 75 | addr: &str, 76 | client: &Client, 77 | party_num: u16, 78 | round: &str, 79 | data: String, 80 | sender_uuid: String, 81 | ) -> anyhow::Result<()> { 82 | let key = format!("{}-{}-{}", party_num, round, sender_uuid); 83 | let entry = Entry { 84 | key: key.clone(), 85 | value: data, 86 | }; 87 | 88 | let res_body = postb(addr, client, "set", entry).await.unwrap(); 89 | let parsed: Value = serde_json::from_str(&res_body) 90 | .map_err(|err| anyhow!("Failed to parse JSON response: {}", err))?; 91 | match parsed { 92 | Value::Object(map) if map.contains_key("Ok") => Ok(()), 93 | _ => Err(anyhow!("Unexpected response structure: {}", res_body)), 94 | } 95 | } 96 | 97 | pub async fn sendp2p( 98 | addr: &str, 99 | client: &Client, 100 | party_from: u16, 101 | party_to: u16, 102 | round: &str, 103 | data: String, 104 | sender_uuid: String, 105 | ) -> Result<(), ()> { 106 | let key = format!("{}-{}-{}-{}", party_from, party_to, round, sender_uuid); 107 | let entry = Entry { 108 | key: key.clone(), 109 | value: data, 110 | }; 111 | 112 | let res_body = postb(addr, client, "set", entry).await.unwrap(); 113 | println!("res_body: {}", res_body); 114 | serde_json::from_str(&res_body).unwrap() 115 | } 116 | 117 | pub async fn poll_for_broadcasts( 118 | addr: &str, 119 | client: &Client, 120 | party_num: u16, 121 | n: u16, 122 | delay: std::time::Duration, 123 | round: &str, 124 | sender_uuid: String, 125 | ) -> Vec { 126 | let mut ans_vec = Vec::new(); 127 | for i in 1..=n { 128 | if i != party_num { 129 | let key = format!("{}-{}-{}", i, round, sender_uuid); 130 | let index = Index { key }; 131 | loop { 132 | thread::sleep(delay); 133 | let res_body = postb(addr, client, "get", index.clone()).await.unwrap(); 134 | let answer: Result = serde_json::from_str(&res_body).unwrap(); 135 | match answer { 136 | Ok(entry) => { 137 | ans_vec.push(entry.value); 138 | break; 139 | } 140 | Err(_) => continue, 141 | } 142 | } 143 | } 144 | } 145 | ans_vec 146 | } 147 | 148 | pub async fn poll_for_p2p( 149 | addr: &str, 150 | client: &Client, 151 | party_num: u16, 152 | n: u16, 153 | delay: std::time::Duration, 154 | round: &str, 155 | sender_uuid: String, 156 | ) -> Vec { 157 | let mut ans_vec = Vec::new(); 158 | for i in 1..=n { 159 | if i != party_num { 160 | let key = format!("{}-{}-{}-{}", i, party_num, round, sender_uuid); 161 | let index = Index { key }; 162 | loop { 163 | thread::sleep(delay); 164 | let res_body = postb(addr, client, "get", index.clone()).await.unwrap(); 165 | let answer: Result = serde_json::from_str(&res_body).unwrap(); 166 | match answer { 167 | Ok(entry) => { 168 | ans_vec.push(entry.value); 169 | break; 170 | } 171 | Err(_) => continue, 172 | } 173 | } 174 | } 175 | } 176 | ans_vec 177 | } 178 | 179 | pub fn sha256_digest(input: &[u8]) -> String { 180 | let mut hasher = Sha256::new(); 181 | hasher.update(input); 182 | hex::encode(hasher.finalize()) 183 | } 184 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | use config::{Config, ConfigError, Environment, File}; 4 | use serde::Deserialize; 5 | 6 | #[derive(Debug, Deserialize)] 7 | pub struct SecurityConfig { 8 | pub jwt_secret: String, 9 | pub jwt_expiration: u64, 10 | pub allowed_signer_ips: Vec, 11 | } 12 | #[derive(Debug, Deserialize)] 13 | pub struct Settings { 14 | pub mongodb_uri: String, 15 | pub rabbitmq_uri: String, 16 | pub manager_url: String, 17 | pub manager_port: u16, 18 | pub signing_timeout: u64, 19 | pub threshold: u16, 20 | pub total_parties: u16, 21 | pub path: String, 22 | pub signer_key_file: String, 23 | // New secuirty configuration section 24 | pub security: SecurityConfig, 25 | } 26 | 27 | impl Settings { 28 | pub fn new() -> Result { 29 | let mut builder = Config::builder(); 30 | 31 | builder = builder.add_source(File::with_name("config/default")); 32 | 33 | let env = std::env::var("RUN_MODE").unwrap_or_else(|_| "development".into()); 34 | builder = builder.add_source(File::with_name(&format!("config/{}", env)).required(false)); 35 | 36 | builder = builder.add_source(File::with_name("config/local").required(false)); 37 | 38 | builder = builder.add_source(Environment::with_prefix("app")); 39 | builder.build()?.try_deserialize() 40 | } 41 | 42 | // Helper method to validate IP against whitelist 43 | pub fn is_ip_whitelisted(&self, ip: IpAddr) -> bool { 44 | self.security 45 | .allowed_signer_ips 46 | .iter() 47 | .any(|allowed_ip| allowed_ip.parse::().ok() == Some(ip)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/error/mod.rs: -------------------------------------------------------------------------------- 1 | mod tss_error; 2 | 3 | pub use tss_error::TssError; 4 | -------------------------------------------------------------------------------- /src/error/tss_error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum TssError { 5 | #[error("Database error: {0}")] 6 | DatabaseError(#[from] mongodb::error::Error), 7 | 8 | #[error("Queue error: {0}")] 9 | QueueError(String), 10 | 11 | #[error("Serialization error: {0}")] 12 | SerializationError(#[from] serde_json::Error), 13 | 14 | #[error("HTTP client error: {0}")] 15 | HttpClientError(#[from] reqwest::Error), 16 | 17 | #[error("Signing error: {0}")] 18 | SigningError(String), 19 | 20 | #[error("Configuration error: {0}")] 21 | ConfigError(#[from] config::ConfigError), 22 | 23 | #[error("I/O error: {0}")] 24 | IoError(#[from] std::io::Error), 25 | 26 | #[error("JWT error: {0}")] 27 | JWTError(String), 28 | 29 | #[error("Authentication error: {0}")] 30 | AuthError(String), 31 | 32 | #[error("Authorization error: {0}")] 33 | AuthorizationError(String), 34 | 35 | #[error("Timeout error")] 36 | TimeoutError, 37 | 38 | #[error("Invalid party ID: {0}")] 39 | InvalidPartyId(u16), 40 | 41 | #[error("Invalid threshold: {0}")] 42 | InvalidThreshold(u16), 43 | 44 | #[error("Invalid number of parties: {0}")] 45 | InvalidNumberOfParties(u16), 46 | 47 | #[error("Unexpected error: {0}")] 48 | UnexpectedError(String), 49 | 50 | #[error("Message too large")] 51 | MessageTooLarge, 52 | 53 | #[error("Invalid Uuid: {0}")] 54 | InvalidUuid(String), 55 | 56 | #[error("Not found error: {0}")] 57 | NotFoundError(String), 58 | } 59 | 60 | impl From for TssError { 61 | fn from(err: lapin::Error) -> Self { 62 | TssError::QueueError(err.to_string()) 63 | } 64 | } 65 | 66 | impl From for TssError { 67 | fn from(err: anyhow::Error) -> Self { 68 | TssError::UnexpectedError(err.to_string()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod manager; 3 | pub mod queue; 4 | pub mod signer; 5 | pub mod storage; 6 | 7 | mod auth; 8 | pub mod config; 9 | pub mod error; 10 | -------------------------------------------------------------------------------- /src/manager/api.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::auth::{AuthenticatedUser, Role}; 4 | use crate::common::types::SigningRequest; 5 | use crate::common::{KeyGenParams, KeyGenRequest, KeysToStore, MessageToSignStored}; 6 | use crate::error::TssError; 7 | use crate::manager::service::ManagerService; 8 | use crate::{auth::create_token, config::Settings}; 9 | use anyhow::Context; 10 | use rocket::http::Status; 11 | use rocket::response::status::Created; 12 | use rocket::serde::json::Json; 13 | use rocket::{get, post, State}; 14 | use serde::{Deserialize, Serialize}; 15 | 16 | use super::constants::MAX_MESSAGE_SIZE; 17 | 18 | #[derive(Deserialize)] 19 | pub struct SigningRequestDTO { 20 | pub message: String, 21 | } 22 | 23 | #[derive(Serialize, Deserialize)] 24 | pub struct SigningResponseDTO { 25 | pub request_id: String, 26 | pub status: String, 27 | } 28 | 29 | #[derive(Deserialize)] 30 | pub struct KeyGenRequestDTO { 31 | pub manager_url: String, 32 | pub threshold: u16, 33 | pub total_parties: u16, 34 | } 35 | 36 | #[derive(Serialize, Deserialize)] 37 | pub struct KeyGenResponseDTO { 38 | pub request_id: String, 39 | pub keys: Vec, 40 | } 41 | 42 | #[derive(Serialize)] 43 | pub struct TokenResponse { 44 | token: String, 45 | expires_in: u64, 46 | token_type: String, 47 | } 48 | 49 | #[post("/sign", format = "json", data = "")] 50 | pub async fn sign( 51 | auth: AuthenticatedUser, 52 | manager: &State>, 53 | request: Json, 54 | ) -> Result>, Status> { 55 | // Verify that we have a public role 56 | if auth.role != Role::Public { 57 | return Err(Status::Forbidden); 58 | } 59 | 60 | // validate messgage size 61 | if request.message.len() > MAX_MESSAGE_SIZE { 62 | return Err(Status::PayloadTooLarge); 63 | } 64 | 65 | let message: Vec = request.message.as_bytes().to_vec(); 66 | let signing_request = SigningRequest { 67 | id: uuid::Uuid::new_v4().to_string(), 68 | message, 69 | }; 70 | 71 | match manager 72 | .process_signing_request(signing_request.clone()) 73 | .await 74 | { 75 | Ok(_) => { 76 | let response = SigningResponseDTO { 77 | request_id: signing_request.id, 78 | status: "Pending".to_string(), 79 | }; 80 | Ok(Created::new("/").body(Json(response))) 81 | } 82 | Err(_) => Err(Status::InternalServerError), 83 | } 84 | } 85 | 86 | #[get("/signing_result/")] 87 | pub async fn get_signing_result( 88 | auth: AuthenticatedUser, 89 | manager: &State>, 90 | request_id: String, 91 | ) -> Result>, Status> { 92 | // Verify that we have a public role 93 | if auth.role != Role::Public { 94 | return Err(Status::Forbidden); 95 | } 96 | 97 | match manager.get_signing_result(&request_id).await { 98 | Ok(result) => Ok(Json(result)), 99 | Err(_) => Err(Status::InternalServerError), 100 | } 101 | } 102 | 103 | // For testing and development purposes 104 | // Only compile these endpoints in debug/development mode 105 | #[cfg(debug_assertions)] 106 | #[get("/generate_test_token/")] 107 | pub async fn generate_test_token( 108 | role: String, 109 | settings: &rocket::State>, 110 | ) -> Result, rocket::http::Status> { 111 | let role = match role.to_lowercase().as_str() { 112 | "public" => Role::Public, 113 | "signer" => Role::Signer, 114 | "admin" => Role::Admin, 115 | _ => return Err(rocket::http::Status::BadRequest), 116 | }; 117 | 118 | match create_token("test-user", role, settings) { 119 | Ok(token) => Ok(Json(TokenResponse { 120 | token, 121 | expires_in: settings.security.jwt_expiration, 122 | token_type: "Bearer".to_string(), 123 | })), 124 | Err(_) => Err(rocket::http::Status::InternalServerError), 125 | } 126 | } 127 | 128 | #[post("/key_gen_request", format = "json", data = "")] 129 | pub async fn generate_keys( 130 | manager: &State>, 131 | request: Json, 132 | ) -> Result>, Status> { 133 | let threshold = request.threshold; 134 | let total_parties = request.total_parties; 135 | let manger_addr = request.manager_url.clone(); 136 | 137 | if threshold > total_parties { 138 | return Err(Status::BadRequest); 139 | } 140 | 141 | let keygen_request = KeyGenRequest { 142 | id: uuid::Uuid::new_v4().to_string(), 143 | keygen_params: KeyGenParams { 144 | parties: total_parties, 145 | threshold, 146 | }, 147 | }; 148 | 149 | let result = manager 150 | .process_keygen_request(keygen_request.clone(), &manger_addr) 151 | .await 152 | .context("Failed to process key generation request") 153 | .map_err(|_| Status::InternalServerError)?; 154 | 155 | let response = KeyGenResponseDTO { 156 | request_id: keygen_request.id, 157 | keys: result, 158 | }; 159 | 160 | Ok(Created::new("/").body(Json(response))) 161 | } 162 | 163 | #[get("/key_gen_result/")] 164 | pub async fn get_key_gen_result( 165 | manager: &State>, 166 | request_id: String, 167 | ) -> Result>, TssError> { 168 | let result = manager.get_key_gen_result(&request_id).await?; 169 | Ok(Json(result)) 170 | } 171 | -------------------------------------------------------------------------------- /src/manager/constants.rs: -------------------------------------------------------------------------------- 1 | pub const MAX_MESSAGE_SIZE: usize = 1024 * 1024; // 1MB 2 | pub const NONCE_SIZE: usize = 12; 3 | -------------------------------------------------------------------------------- /src/manager/handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::auth::SignerAuth; 2 | use crate::common::{ 3 | Entry, Index, KeyGenParams, ManagerError, PartySignup, PartySignupRequestBody, SignerResult, 4 | SigningPartySignup, SigningRoom, 5 | }; 6 | use crate::error::TssError; 7 | use crate::manager::ManagerService; 8 | use rocket::http::Status; 9 | use rocket::post; 10 | use rocket::response::Responder; 11 | use rocket::serde::json::Json; 12 | use rocket::{Request, State}; 13 | use std::sync::Arc; 14 | 15 | #[post("/get", format = "json", data = "")] 16 | pub async fn get( 17 | _auth: SignerAuth, 18 | manager: &State>, 19 | request: Json, 20 | ) -> Json> { 21 | let index: Index = request.into_inner(); 22 | let signing_rooms = manager.signing_rooms.read().await; 23 | match signing_rooms.get(&index.key) { 24 | Some(value) => { 25 | let entry = Entry { 26 | key: index.key.clone(), 27 | value: value.clone().to_string(), 28 | }; 29 | Json(Ok(entry)) 30 | } 31 | None => Json(Err(ManagerError { 32 | error: "Key not found".to_string(), 33 | })), 34 | } 35 | } 36 | 37 | #[post("/set", format = "json", data = "")] 38 | pub async fn set( 39 | _auth: SignerAuth, 40 | manager: &State>, 41 | request: Json, 42 | ) -> Json> { 43 | let entry: Entry = request.into_inner(); 44 | let mut signing_rooms = manager.signing_rooms.write().await; 45 | signing_rooms.insert(entry.key.clone(), entry.value.clone()); 46 | Json(Ok(())) 47 | } 48 | 49 | #[post("/signupkeygen", format = "json", data = "")] 50 | pub async fn signup_keygen( 51 | _auth: SignerAuth, 52 | manager: &State>, 53 | request: Json, 54 | ) -> Json> { 55 | let parties = request.parties; 56 | let key = "signup-keygen".to_string(); 57 | let mut hm = manager.signing_rooms.write().await; 58 | 59 | let client_signup = match hm.get(&key) { 60 | Some(o) => serde_json::from_str(o).unwrap(), 61 | None => PartySignup { 62 | number: 0, 63 | uuid: uuid::Uuid::new_v4().to_string(), 64 | }, 65 | }; 66 | 67 | let party_signup = { 68 | if client_signup.number < parties { 69 | PartySignup { 70 | number: client_signup.number + 1, 71 | uuid: client_signup.uuid, 72 | } 73 | } else { 74 | PartySignup { 75 | number: 1, 76 | uuid: uuid::Uuid::new_v4().to_string(), 77 | } 78 | } 79 | }; 80 | 81 | hm.insert(key, serde_json::to_string(&party_signup).unwrap()); 82 | Json(Ok(party_signup)) 83 | } 84 | 85 | #[post("/signupsign", format = "json", data = "")] 86 | pub async fn signup_sign( 87 | _auth: SignerAuth, 88 | manager: &State>, 89 | request: Json, 90 | ) -> Json> { 91 | let req = request.into_inner(); 92 | let threshold = req.threshold; 93 | let room_id = req.room_id.clone(); 94 | let party_uuid = req.party_uuid.clone(); 95 | let new_signup_request = party_uuid.is_empty(); 96 | let party_number = req.party_number; 97 | let mut key = "signup-sign-".to_owned(); 98 | key.push_str(&room_id); 99 | 100 | let mut signing_rooms = manager.signing_rooms.write().await; 101 | 102 | let mut signing_room = match signing_rooms.get(&key) { 103 | Some(room) => serde_json::from_str(room).unwrap(), 104 | None => SigningRoom::new(room_id.clone(), threshold + 1), 105 | }; 106 | 107 | if signing_room.last_stage != "signup" { 108 | if signing_room.has_member(party_number, party_uuid.clone()) { 109 | return Json(Ok(signing_room.get_signup_info(party_number))); 110 | } 111 | 112 | if signing_room.are_all_members_inactive() { 113 | signing_room = SigningRoom::new(room_id, threshold + 1); 114 | } else { 115 | return Json(Err(ManagerError { 116 | error: "Room signup phase is terminated".to_string(), 117 | })); 118 | } 119 | } 120 | 121 | if signing_room.is_full() && signing_room.are_all_members_active() && new_signup_request { 122 | return Json(Err(ManagerError { 123 | error: "Room is full, all members active".to_string(), 124 | })); 125 | } 126 | 127 | let party_signup = if !new_signup_request { 128 | if !signing_room.has_member(party_number, party_uuid.clone()) { 129 | return Json(Err(ManagerError { 130 | error: "No party found with the given uuid, probably replaced due to timeout" 131 | .to_string(), 132 | })); 133 | } 134 | signing_room.update_ping(party_number) 135 | } else if signing_room.member_info.contains_key(&party_number) { 136 | if signing_room.is_member_active(party_number) { 137 | return Json(Err(ManagerError { 138 | error: "Received a re-signup request for an active party. Request ignored" 139 | .to_string(), 140 | })); 141 | } 142 | println!( 143 | "Received a re-signup request for a timed-out party {:?}, thus UUID is renewed", 144 | party_number 145 | ); 146 | signing_room.replace_party(party_number) 147 | } else { 148 | signing_room.add_party(party_number) 149 | }; 150 | 151 | signing_rooms.insert(key.clone(), serde_json::to_string(&signing_room).unwrap()); 152 | Json(Ok(party_signup)) 153 | } 154 | 155 | #[post("/update_signing_result", format = "json", data = "")] 156 | pub async fn update_signing_result( 157 | _auth: SignerAuth, 158 | manager: &State>, 159 | result: Json, 160 | ) -> Json> { 161 | match manager.update_signing_result(result.into_inner()).await { 162 | Ok(_) => {} 163 | Err(e) => { 164 | return Json(Err(ManagerError { 165 | error: e.to_string(), 166 | })); 167 | } 168 | }; 169 | Json(Ok(())) 170 | } 171 | 172 | impl<'r> Responder<'r, 'static> for TssError { 173 | fn respond_to(self, _: &'r Request<'_>) -> rocket::response::Result<'static> { 174 | Err(Status::InternalServerError) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/manager/keygen.rs: -------------------------------------------------------------------------------- 1 | use std::time; 2 | 3 | use crate::common::{ 4 | secp256k1def::{FE, GE}, 5 | KeyGenParams, KeyGenRequest, 6 | }; 7 | use anyhow::Result; 8 | use curv::elliptic::curves::Secp256k1; 9 | use curv::{ 10 | arithmetic::traits::Converter, 11 | cryptographic_primitives::{ 12 | proofs::sigma_dlog::DLogProof, secret_sharing::feldman_vss::VerifiableSS, 13 | }, 14 | BigInt, 15 | }; 16 | use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2018::party_i::{ 17 | KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, Parameters, 18 | }; 19 | use paillier::EncryptionKey; 20 | use reqwest::Client; 21 | use sha2::Sha256; 22 | 23 | use crate::common::{ 24 | aes_decrypt, aes_encrypt, broadcast, poll_for_broadcasts, poll_for_p2p, postb, sendp2p, 25 | PartySignup, AEAD, 26 | }; 27 | 28 | #[allow(non_snake_case)] 29 | pub async fn run_keygen(addr: &String, keygen_request: &KeyGenRequest) -> Result { 30 | let params = keygen_request.keygen_params.clone(); 31 | let THRESHOLD: u16 = params.threshold; 32 | let PARTIES: u16 = params.parties; 33 | let client = Client::new(); 34 | 35 | // delay: 36 | let delay = time::Duration::from_millis(25); 37 | let params = Parameters { 38 | threshold: THRESHOLD, 39 | share_count: PARTIES, 40 | }; 41 | 42 | //signup: 43 | let tn_params = KeyGenParams { 44 | threshold: THRESHOLD, 45 | parties: PARTIES, 46 | }; 47 | let (party_num_int, uuid) = match keygen_signup(&addr, &client, tn_params).await.unwrap() { 48 | PartySignup { number, uuid } => (number, uuid), 49 | }; 50 | 51 | let party_keys = Keys::create(party_num_int); 52 | let (bc_i, decom_i) = party_keys.phase1_broadcast_phase3_proof_of_correct_key(); 53 | 54 | // send commitment to ephemeral public keys, get round 1 commitments of other parties 55 | assert!(broadcast( 56 | &addr, 57 | &client, 58 | party_num_int, 59 | "round1", 60 | serde_json::to_string(&bc_i).unwrap(), 61 | uuid.clone(), 62 | ) 63 | .await 64 | .is_ok()); 65 | let round1_ans_vec = poll_for_broadcasts( 66 | &addr, 67 | &client, 68 | party_num_int, 69 | PARTIES, 70 | delay, 71 | "round1", 72 | uuid.clone(), 73 | ) 74 | .await; 75 | 76 | let mut bc1_vec = round1_ans_vec 77 | .iter() 78 | .map(|m| serde_json::from_str::(m).unwrap()) 79 | .collect::>(); 80 | 81 | bc1_vec.insert(party_num_int as usize - 1, bc_i); 82 | 83 | // send ephemeral public keys and check commitments correctness 84 | assert!(broadcast( 85 | &addr, 86 | &client, 87 | party_num_int, 88 | "round2", 89 | serde_json::to_string(&decom_i).unwrap(), 90 | uuid.clone(), 91 | ) 92 | .await 93 | .is_ok()); 94 | let round2_ans_vec = poll_for_broadcasts( 95 | &addr, 96 | &client, 97 | party_num_int, 98 | PARTIES, 99 | delay, 100 | "round2", 101 | uuid.clone(), 102 | ) 103 | .await; 104 | 105 | let mut j = 0; 106 | let mut point_vec: Vec = Vec::new(); 107 | let mut decom_vec: Vec = Vec::new(); 108 | let mut enc_keys: Vec = Vec::new(); 109 | for i in 1..=PARTIES { 110 | if i == party_num_int { 111 | point_vec.push(decom_i.y_i.clone()); 112 | decom_vec.push(decom_i.clone()); 113 | } else { 114 | let decom_j: KeyGenDecommitMessage1 = serde_json::from_str(&round2_ans_vec[j]).unwrap(); 115 | point_vec.push(decom_j.y_i.clone()); 116 | decom_vec.push(decom_j.clone()); 117 | enc_keys.push((decom_j.y_i * &party_keys.u_i).x_coord().unwrap()); 118 | j = j + 1; 119 | } 120 | } 121 | 122 | let (head, tail) = point_vec.split_at(1); 123 | let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x); 124 | 125 | let (vss_scheme, secret_shares, _index) = party_keys 126 | .phase1_verify_com_phase3_verify_correct_key_phase2_distribute( 127 | ¶ms, &decom_vec, &bc1_vec, 128 | ) 129 | .expect("invalid key"); 130 | 131 | ////////////////////////////////////////////////////////////////////////////// 132 | 133 | let mut j = 0; 134 | for (k, i) in (1..=PARTIES).enumerate() { 135 | if i != party_num_int { 136 | // prepare encrypted ss for party i: 137 | let key_i = BigInt::to_bytes(&enc_keys[j]); 138 | let plaintext = BigInt::to_bytes(&secret_shares[k].to_bigint()); 139 | let aead_pack_i = aes_encrypt(&key_i, &plaintext); 140 | assert!(sendp2p( 141 | &addr, 142 | &client, 143 | party_num_int, 144 | i, 145 | "round3", 146 | serde_json::to_string(&aead_pack_i).unwrap(), 147 | uuid.clone(), 148 | ) 149 | .await 150 | .is_ok()); 151 | j += 1; 152 | } 153 | } 154 | 155 | let round3_ans_vec = poll_for_p2p( 156 | &addr, 157 | &client, 158 | party_num_int, 159 | PARTIES, 160 | delay, 161 | "round3", 162 | uuid.clone(), 163 | ) 164 | .await; 165 | 166 | let mut j = 0; 167 | let mut party_shares: Vec = Vec::new(); 168 | for i in 1..=PARTIES { 169 | if i == party_num_int { 170 | party_shares.push(secret_shares[(i - 1) as usize].clone()); 171 | } else { 172 | let aead_pack: AEAD = serde_json::from_str(&round3_ans_vec[j]).unwrap(); 173 | let key_i = BigInt::to_bytes(&enc_keys[j]); 174 | let out = aes_decrypt(&key_i, aead_pack); 175 | let out_bn = BigInt::from_bytes(&out); 176 | let out_fe = FE::from(&out_bn); 177 | party_shares.push(out_fe); 178 | 179 | j += 1; 180 | } 181 | } 182 | 183 | // round 4: send vss commitments 184 | assert!(broadcast( 185 | &addr, 186 | &client, 187 | party_num_int, 188 | "round4", 189 | serde_json::to_string(&vss_scheme).unwrap(), 190 | uuid.clone(), 191 | ) 192 | .await 193 | .is_ok()); 194 | let round4_ans_vec = poll_for_broadcasts( 195 | &addr, 196 | &client, 197 | party_num_int, 198 | PARTIES, 199 | delay, 200 | "round4", 201 | uuid.clone(), 202 | ) 203 | .await; 204 | 205 | let mut j = 0; 206 | let mut vss_scheme_vec: Vec> = Vec::new(); 207 | for i in 1..=PARTIES { 208 | if i == party_num_int { 209 | vss_scheme_vec.push(vss_scheme.clone()); 210 | } else { 211 | let vss_scheme_j: VerifiableSS = 212 | serde_json::from_str(&round4_ans_vec[j]).unwrap(); 213 | vss_scheme_vec.push(vss_scheme_j); 214 | j += 1; 215 | } 216 | } 217 | 218 | let (shared_keys, dlog_proof) = party_keys 219 | .phase2_verify_vss_construct_keypair_phase3_pok_dlog( 220 | ¶ms, 221 | &point_vec, 222 | &party_shares, 223 | &vss_scheme_vec, 224 | party_num_int, 225 | ) 226 | .expect("invalid vss"); 227 | 228 | // round 5: send dlog proof 229 | assert!(broadcast( 230 | &addr, 231 | &client, 232 | party_num_int, 233 | "round5", 234 | serde_json::to_string(&dlog_proof).unwrap(), 235 | uuid.clone(), 236 | ) 237 | .await 238 | .is_ok()); 239 | let round5_ans_vec = poll_for_broadcasts( 240 | &addr, 241 | &client, 242 | party_num_int, 243 | PARTIES, 244 | delay, 245 | "round5", 246 | uuid.clone(), 247 | ) 248 | .await; 249 | 250 | let mut j = 0; 251 | let mut dlog_proof_vec: Vec> = Vec::new(); 252 | for i in 1..=PARTIES { 253 | if i == party_num_int { 254 | dlog_proof_vec.push(dlog_proof.clone()); 255 | } else { 256 | let dlog_proof_j: DLogProof = 257 | serde_json::from_str(&round5_ans_vec[j]).unwrap(); 258 | dlog_proof_vec.push(dlog_proof_j); 259 | j += 1; 260 | } 261 | } 262 | Keys::verify_dlog_proofs(¶ms, &dlog_proof_vec, &point_vec).expect("bad dlog proof"); 263 | 264 | //save key to file: 265 | let paillier_key_vec = (0..PARTIES) 266 | .map(|i| bc1_vec[i as usize].e.clone()) 267 | .collect::>(); 268 | 269 | let keygen_json = serde_json::to_string(&( 270 | party_keys, 271 | shared_keys, 272 | party_num_int, 273 | vss_scheme_vec, 274 | paillier_key_vec, 275 | y_sum, 276 | )) 277 | .unwrap(); 278 | Ok(keygen_json) 279 | } 280 | 281 | pub async fn keygen_signup( 282 | addr: &String, 283 | client: &Client, 284 | params: KeyGenParams, 285 | ) -> Result { 286 | let res_body = postb::(&addr, &client, "signupkeygen", params) 287 | .await 288 | .unwrap(); 289 | serde_json::from_str(&res_body).unwrap() 290 | } 291 | -------------------------------------------------------------------------------- /src/manager/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod constants; 3 | pub mod handlers; 4 | pub mod keygen; 5 | pub mod service; 6 | 7 | pub use service::ManagerService; 8 | -------------------------------------------------------------------------------- /src/manager/service.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ 2 | Key, KeyGenRequest, KeysToStore, MessageToSignStored, SignerResult, SigningRequest, 3 | }; 4 | use crate::queue::rabbitmq::RabbitMQService; 5 | use crate::storage::mongodb::MongoDBStorage; 6 | use anyhow::Result; 7 | use futures::future::join_all; 8 | use std::collections::HashMap; 9 | use std::sync::Arc; 10 | use tokio::sync::RwLock; 11 | use tokio::task; 12 | use tracing::info; 13 | 14 | use super::keygen::run_keygen; 15 | 16 | pub struct ManagerService { 17 | pub storage: MongoDBStorage, 18 | pub queue: RabbitMQService, 19 | pub(crate) signing_rooms: Arc>>, 20 | pub threshold: u16, 21 | pub total_parties: u16, 22 | } 23 | 24 | impl ManagerService { 25 | pub async fn new( 26 | mongodb_uri: &str, 27 | rabbitmq_uri: &str, 28 | threshold: u16, 29 | total_parties: u16, 30 | ) -> Result { 31 | let storage = MongoDBStorage::new(mongodb_uri, "tss_network").await?; 32 | let queue = RabbitMQService::new(rabbitmq_uri).await?; 33 | 34 | Ok(Self { 35 | storage, 36 | queue, 37 | signing_rooms: Arc::new(RwLock::new(HashMap::new())), 38 | threshold, 39 | total_parties, 40 | }) 41 | } 42 | 43 | pub async fn run(&self) -> Result<()> { 44 | info!("Starting ManagerService"); 45 | loop { 46 | // match self.queue.receive_signing_request().await { 47 | // Ok(request) => { 48 | // if let Err(e) = self.handle_signing_request(request).await { 49 | // error!("Failed to handle signing request: {:?}", e); 50 | // } 51 | // } 52 | // Err(e) => { 53 | // error!("Failed to receive signing request: {:?}", e); 54 | // } 55 | // } 56 | } 57 | } 58 | 59 | pub async fn get_signing_result( 60 | &self, 61 | request_id: &str, 62 | ) -> Result> { 63 | self.storage.get_signing_result(request_id).await 64 | } 65 | 66 | pub async fn update_signing_result(&self, result: SignerResult) -> Result<()> { 67 | self.storage.update_signing_result(&result).await 68 | } 69 | 70 | pub async fn process_signing_request(&self, request: SigningRequest) -> Result<()> { 71 | self.storage.insert_request(&request).await?; 72 | self.queue.publish_signing_request(&request).await?; 73 | Ok(()) 74 | } 75 | 76 | pub async fn process_keygen_request( 77 | &self, 78 | request: KeyGenRequest, 79 | manager_addr: &String, 80 | ) -> Result> { 81 | self.storage.insert_key_gen_request(&request).await?; 82 | let total_parties = request.keygen_params.parties; 83 | let tasks: Vec<_> = (0..total_parties) 84 | .map(|_| { 85 | let manager_addr = manager_addr.clone(); 86 | let request = request.clone(); 87 | task::spawn(async move { run_keygen(&manager_addr, &request).await }) 88 | }) 89 | .collect(); 90 | 91 | let results = join_all(tasks).await; 92 | 93 | // Collect successful results and aggregate errors 94 | let mut successful_results = Vec::new(); 95 | let mut errors = Vec::new(); 96 | 97 | for (index, res) in results.into_iter().enumerate() { 98 | match res { 99 | Ok(Ok(json_str)) => successful_results.push(json_str), 100 | Ok(Err(e)) => errors.push(format!("Error in party {}: {}", index, e)), 101 | Err(e) => errors.push(format!("Task error in party {}: {}", index, e)), 102 | } 103 | } 104 | 105 | if !errors.is_empty() { 106 | eprintln!("Errors occurred during key generation: {:?}", errors); 107 | } 108 | 109 | self.storage 110 | .update_key_gen_result(&request.id, successful_results.clone()) 111 | .await?; 112 | Ok(successful_results) 113 | } 114 | 115 | pub async fn get_key_gen_result(&self, request_id: &str) -> Result> { 116 | self.storage.get_key_gen_result(request_id).await 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/queue/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rabbitmq; 2 | 3 | pub use rabbitmq::RabbitMQService; 4 | -------------------------------------------------------------------------------- /src/queue/rabbitmq.rs: -------------------------------------------------------------------------------- 1 | use crate::common::types::SigningRequest; 2 | use crate::error::TssError; 3 | use anyhow::Result; 4 | use futures_lite::stream::StreamExt; 5 | use lapin::{ 6 | options::*, types::FieldTable, BasicProperties, Channel, Connection, ConnectionProperties, 7 | ExchangeKind, 8 | }; 9 | 10 | pub struct RabbitMQService { 11 | request_channel: Channel, 12 | request_exchange: String, 13 | } 14 | 15 | impl RabbitMQService { 16 | pub async fn new(uri: &str) -> Result { 17 | let conn = Connection::connect(uri, ConnectionProperties::default()).await?; 18 | let request_channel = conn.create_channel().await?; 19 | let result_channel = conn.create_channel().await?; 20 | let request_exchange = "signing_requests_exchange".to_string(); 21 | let result_queue = "signing_results".to_string(); 22 | 23 | request_channel 24 | .exchange_declare( 25 | &request_exchange, 26 | ExchangeKind::Fanout, 27 | ExchangeDeclareOptions::default(), 28 | FieldTable::default(), 29 | ) 30 | .await?; 31 | 32 | result_channel 33 | .queue_declare( 34 | &result_queue, 35 | QueueDeclareOptions::default(), 36 | FieldTable::default(), 37 | ) 38 | .await?; 39 | 40 | Ok(Self { 41 | request_channel, 42 | request_exchange, 43 | }) 44 | } 45 | 46 | pub async fn publish_signing_request(&self, request: &SigningRequest) -> Result<()> { 47 | let payload = serde_json::to_vec(request)?; 48 | self.request_channel 49 | .basic_publish( 50 | &self.request_exchange, 51 | "", 52 | BasicPublishOptions::default(), 53 | &payload, 54 | BasicProperties::default(), 55 | ) 56 | .await?; 57 | 58 | Ok(()) 59 | } 60 | 61 | pub async fn receive_signing_request(&self) -> Result { 62 | let queue_name = self 63 | .request_channel 64 | .queue_declare( 65 | "", 66 | QueueDeclareOptions { 67 | exclusive: true, 68 | ..QueueDeclareOptions::default() 69 | }, 70 | FieldTable::default(), 71 | ) 72 | .await? 73 | .name() 74 | .to_string(); 75 | 76 | self.request_channel 77 | .queue_bind( 78 | &queue_name, 79 | &self.request_exchange, 80 | "", 81 | QueueBindOptions::default(), 82 | FieldTable::default(), 83 | ) 84 | .await?; 85 | 86 | let mut consumer = self 87 | .request_channel 88 | .basic_consume( 89 | &queue_name, 90 | "signing_consumer", 91 | BasicConsumeOptions::default(), 92 | FieldTable::default(), 93 | ) 94 | .await?; 95 | 96 | if let Some(delivery) = consumer.next().await { 97 | match delivery { 98 | Ok(delivery) => { 99 | let request: SigningRequest = serde_json::from_slice(&delivery.data)?; 100 | delivery.ack(BasicAckOptions::default()).await?; 101 | Ok(request) 102 | } 103 | Err(err) => Err(err.into()), 104 | } 105 | } else { 106 | Err(TssError::QueueError("No message received".into()).into()) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/signer/hd_keys.rs: -------------------------------------------------------------------------------- 1 | extern crate curv; 2 | 3 | use curv::arithmetic::traits::Converter; 4 | 5 | use crate::signer::secp256k1def::{FE, GE}; 6 | use curv::{ 7 | arithmetic::{BasicOps, One}, 8 | BigInt, 9 | }; 10 | use hmac::{Hmac, Mac, NewMac}; 11 | use sha2::Sha512; 12 | use zeroize::Zeroize; 13 | 14 | pub fn get_hd_key(y_sum: &GE, path_vector: Vec) -> (GE, FE) { 15 | // generate a random but shared chain code, this will do 16 | let chain_code = GE::generator().as_point(); 17 | // println!("chain code {:?}", chain_code); 18 | // derive a new pubkey and LR sequence, y_sum becomes a new child pub key 19 | let (y_sum_child, f_l_new, _cc_new) = hd_key( 20 | path_vector, 21 | y_sum, 22 | &BigInt::from_bytes(chain_code.to_bytes(true).as_ref()), 23 | ); 24 | let y_sum = y_sum_child.clone(); 25 | // println!("New public key: {:?}", &y_sum); 26 | // println!("Public key X: {:?}", &y_sum.x_coord()); 27 | // println!("Public key Y: {:?}", &y_sum.y_coord()); 28 | (y_sum, f_l_new) 29 | } 30 | 31 | pub fn hd_key( 32 | mut location_in_hir: Vec, 33 | pubkey: &GE, 34 | chain_code_bi: &BigInt, 35 | ) -> (GE, FE, GE) { 36 | let mask = BigInt::from(2).pow(256) - BigInt::one(); 37 | // let public_key = self.public.q.clone(); 38 | 39 | // calc first element: 40 | let first = location_in_hir.remove(0); 41 | let pub_key_bi = &BigInt::from_bytes(pubkey.to_bytes(true).as_ref()); 42 | let f = create_hmac(chain_code_bi, &[pub_key_bi, &first]); 43 | let f_l = &f >> 256; 44 | let f_r = &f & &mask; 45 | let f_l_fe: FE = FE::from(&f_l); 46 | let f_r_fe: FE = FE::from(&f_r); 47 | 48 | let bn_to_slice = BigInt::to_bytes(chain_code_bi); 49 | let chain_code = GE::from_bytes(&bn_to_slice[0..33]).unwrap() * &f_r_fe; 50 | let g: GE = GE::generator().to_point(); 51 | let pub_key = pubkey + &g * &f_l_fe; 52 | 53 | let (public_key_new_child, f_l_new, cc_new) = 54 | location_in_hir 55 | .iter() 56 | .fold((pub_key, f_l_fe, chain_code), |acc, index| { 57 | let pub_key_bi = &BigInt::from_bytes(acc.0.to_bytes(true).as_ref()); 58 | let f = create_hmac( 59 | &BigInt::from_bytes(acc.2.to_bytes(true).as_ref()), 60 | &[pub_key_bi, index], 61 | ); 62 | let f_l = &f >> 256; 63 | let f_r = &f & &mask; 64 | let f_l_fe: FE = FE::from(&f_l); 65 | let f_r_fe: FE = FE::from(&f_r); 66 | 67 | (acc.0 + &g * &f_l_fe, f_l_fe + &acc.1, &acc.2 * &f_r_fe) 68 | }); 69 | (public_key_new_child, f_l_new, cc_new) 70 | } 71 | 72 | fn create_hmac(key: &BigInt, data: &[&BigInt]) -> BigInt { 73 | let mut key_bytes = key.to_bytes(); 74 | 75 | let mut hmac = Hmac::::new_from_slice(&key_bytes).expect(""); 76 | 77 | for value in data { 78 | hmac.update(&BigInt::to_bytes(value)); 79 | } 80 | key_bytes.zeroize(); 81 | let result = hmac.finalize(); 82 | let code = result.into_bytes(); 83 | 84 | BigInt::from_bytes(code.as_slice()) 85 | } 86 | -------------------------------------------------------------------------------- /src/signer/mod.rs: -------------------------------------------------------------------------------- 1 | mod hd_keys; 2 | mod secp256k1def; 3 | pub mod service; 4 | 5 | pub use service::SignerService; 6 | -------------------------------------------------------------------------------- /src/signer/secp256k1def.rs: -------------------------------------------------------------------------------- 1 | use curv::elliptic::curves::{Point, Scalar, Secp256k1}; 2 | 3 | pub type FE = Scalar; 4 | pub type GE = Point; 5 | -------------------------------------------------------------------------------- /src/signer/service.rs: -------------------------------------------------------------------------------- 1 | use crate::queue::rabbitmq::RabbitMQService; 2 | use anyhow::{anyhow, Result}; 3 | use curv::arithmetic::{BasicOps, Converter, Modulo}; 4 | use curv::cryptographic_primitives::proofs::sigma_correct_homomorphic_elgamal_enc::HomoELGamalProof; 5 | use curv::cryptographic_primitives::proofs::sigma_dlog::DLogProof; 6 | use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; 7 | use curv::elliptic::curves::Secp256k1; 8 | use curv::BigInt; 9 | use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2018::party_i::*; 10 | use multi_party_ecdsa::utilities::mta::{MessageA, MessageB}; 11 | use paillier::EncryptionKey; 12 | use reqwest::Client; 13 | use serde_json::{json, Value}; 14 | use sha2::Sha256; 15 | use std::fs::File; 16 | use std::io::Read; 17 | use std::{thread, time}; 18 | use tracing::{error, info}; 19 | 20 | use crate::common::{ 21 | broadcast, poll_for_broadcasts, poll_for_p2p, postb, sendp2p, sha256_digest, ManagerError, 22 | Params, PartySignup, PartySignupRequestBody, SignatureData, SignerResult, SigningPartySignup, 23 | SigningRequest, 24 | }; 25 | use crate::signer::hd_keys; 26 | use crate::signer::secp256k1def::{FE, GE}; 27 | 28 | struct SignerData { 29 | party_keys: Keys, 30 | shared_keys: SharedKeys, 31 | party_id: u16, 32 | vss_scheme_vec: Vec>, 33 | paillier_key_vector: Vec, 34 | y_sum: GE, 35 | } 36 | 37 | pub struct SignerService { 38 | queue: RabbitMQService, 39 | manager_url: String, 40 | manager_port: String, 41 | signer_data: SignerData, 42 | threshold: u16, 43 | total_parties: u16, 44 | path: String, 45 | } 46 | 47 | #[allow(non_snake_case)] 48 | impl SignerService { 49 | pub async fn new( 50 | manager_url: &str, 51 | manager_port: &u16, 52 | rabbitmq_uri: &str, 53 | key_file: &str, 54 | threshold: &u16, 55 | total_parties: &u16, 56 | path: &str, 57 | ) -> Result { 58 | let queue = RabbitMQService::new(rabbitmq_uri).await?; 59 | let mut file = File::open(key_file)?; 60 | let mut contents = String::new(); 61 | file.read_to_string(&mut contents)?; 62 | 63 | let (party_keys, shared_keys, party_id, vss_scheme_vec, paillier_key_vector, y_sum): ( 64 | Keys, 65 | SharedKeys, 66 | u16, 67 | Vec>, 68 | Vec, 69 | GE, 70 | ) = serde_json::from_str(&contents).unwrap(); 71 | 72 | Ok(Self { 73 | queue, 74 | manager_url: manager_url.to_string(), 75 | manager_port: manager_port.to_string(), 76 | signer_data: SignerData { 77 | party_keys, 78 | shared_keys, 79 | party_id, 80 | vss_scheme_vec, 81 | paillier_key_vector, 82 | y_sum, 83 | }, 84 | threshold: *threshold, 85 | total_parties: *total_parties, 86 | path: path.to_string(), 87 | }) 88 | } 89 | 90 | pub async fn run(&self) -> Result<()> { 91 | info!( 92 | "Starting SignerService for party {}", 93 | self.signer_data.party_id 94 | ); 95 | loop { 96 | match self.queue.receive_signing_request().await { 97 | Ok(request) => { 98 | if let Err(e) = self.handle_signing_request(request).await { 99 | error!("Error handling signing request: {:?}", e); 100 | } 101 | } 102 | Err(e) => { 103 | error!("Error receiving signing request: {:?}", e); 104 | } 105 | } 106 | } 107 | } 108 | 109 | pub async fn handle_signing_request(&self, request: SigningRequest) -> Result<()> { 110 | // this can be dynamic as well 111 | let params = Params { 112 | threshold: self.threshold, 113 | parties: self.total_parties, 114 | path: self.path.clone(), 115 | }; 116 | self.sign(&request.message, &request.id, ¶ms).await; 117 | Ok(()) 118 | } 119 | 120 | pub async fn sign(&self, message: &[u8], request_id: &str, params: &Params) { 121 | let client = Client::new(); 122 | let delay = time::Duration::from_millis(250); 123 | let room_id = sha256_digest(message); 124 | let path_is_empty = params.path.is_empty(); 125 | let (f_l_new, y_sum) = match path_is_empty { 126 | true => (FE::zero(), self.signer_data.y_sum.clone()), 127 | false => call_hd_key(¶ms.path, self.signer_data.y_sum.clone()), 128 | }; 129 | 130 | let addr = format!("{}:{}", self.manager_url, self.manager_port); 131 | let party_keys = self.signer_data.party_keys.clone(); 132 | let shared_keys = self.signer_data.shared_keys.clone(); 133 | let party_id = self.signer_data.party_id; 134 | let mut vss_scheme_vec = self.signer_data.vss_scheme_vec.clone(); 135 | let paillier_key_vector = self.signer_data.paillier_key_vector.clone(); 136 | let sign_at_path = !path_is_empty; 137 | 138 | // Signup 139 | let (party_num_int, uuid, total_parties) = 140 | match Self::signup(&addr, &client, self.threshold, room_id, party_id) 141 | .await 142 | .unwrap() 143 | { 144 | (PartySignup { number, uuid }, total_parties) => (number, uuid, total_parties), 145 | }; 146 | 147 | let debug = json!({"manager_addr": &addr, "party_num": party_num_int, "uuid": uuid}); 148 | println!("{}", serde_json::to_string_pretty(&debug).unwrap()); 149 | 150 | // round 0: collect signers IDs 151 | assert!(broadcast( 152 | &addr, 153 | &client, 154 | party_num_int, 155 | "round0", 156 | serde_json::to_string(&party_id).unwrap(), 157 | uuid.clone(), 158 | ) 159 | .await 160 | .is_ok()); 161 | 162 | let round0_ans_vec = poll_for_broadcasts( 163 | &addr, 164 | &client, 165 | party_num_int, 166 | total_parties, 167 | delay, 168 | "round0", 169 | uuid.clone(), 170 | ) 171 | .await; 172 | 173 | let mut j = 0; 174 | let mut signers_vec: Vec = Vec::new(); 175 | for i in 1..=total_parties { 176 | if i == party_num_int { 177 | signers_vec.push(party_id - 1); 178 | } else { 179 | let signer_j: u16 = serde_json::from_str(&round0_ans_vec[j]).unwrap(); 180 | signers_vec.push(signer_j - 1); 181 | j += 1; 182 | } 183 | } 184 | 185 | if sign_at_path { 186 | // optimize! 187 | let g: GE = GE::generator().to_point(); 188 | // apply on first commitment for leader (leader is party with num=1) 189 | let com_zero_new = &vss_scheme_vec[0].commitments[0] + g * &f_l_new; 190 | // println!("old zero: {:?}, new zero: {:?}", vss_scheme_vec[0].commitments[0], com_zero_new); 191 | // get iterator of all commitments and skip first zero commitment 192 | let mut com_iter_unchanged = vss_scheme_vec[0].commitments.iter(); 193 | com_iter_unchanged.next().unwrap(); 194 | // iterate commitments and inject changed commitments in the beginning then aggregate into vector 195 | let com_vec_new = (0..vss_scheme_vec[1].commitments.len()) 196 | .map(|i| { 197 | if i == 0 { 198 | com_zero_new.clone() 199 | } else { 200 | com_iter_unchanged.next().unwrap().clone() 201 | } 202 | }) 203 | .collect::>(); 204 | let new_vss = VerifiableSS { 205 | parameters: vss_scheme_vec[0].parameters.clone(), 206 | commitments: com_vec_new, 207 | }; 208 | // replace old vss_scheme for leader with new one at position 0 209 | // println!("comparing vectors: \n{:?} \nand \n{:?}", vss_scheme_vec[0], new_vss); 210 | 211 | vss_scheme_vec.remove(0); 212 | vss_scheme_vec.insert(0, new_vss); 213 | // println!("NEW VSS VECTOR: {:?}", vss_scheme_vec); 214 | } 215 | 216 | let mut private = PartyPrivate::set_private(party_keys.clone(), shared_keys); 217 | 218 | if sign_at_path { 219 | if party_num_int == 1 { 220 | // update u_i and x_i for leader 221 | private = private.update_private_key(&f_l_new, &f_l_new); 222 | } else { 223 | // only update x_i for non-leaders 224 | private = private.update_private_key(&FE::zero(), &f_l_new); 225 | } 226 | } 227 | 228 | let sign_keys = SignKeys::create( 229 | &private, 230 | &vss_scheme_vec[signers_vec[(party_num_int - 1) as usize] as usize], 231 | signers_vec[(party_num_int - 1) as usize], 232 | &signers_vec, 233 | ); 234 | 235 | ////////////////////////////////////////////////////////////////////////////// 236 | let (com, decommit) = sign_keys.phase1_broadcast(); 237 | let (m_a_k, _) = MessageA::a(&sign_keys.k_i, &party_keys.ek, &[]); 238 | assert!(broadcast( 239 | &addr, 240 | &client, 241 | party_num_int, 242 | "round1", 243 | serde_json::to_string(&(com.clone(), m_a_k.clone())).unwrap(), 244 | uuid.clone(), 245 | ) 246 | .await 247 | .is_ok()); 248 | let round1_ans_vec = poll_for_broadcasts( 249 | &addr, 250 | &client, 251 | party_num_int, 252 | total_parties, 253 | delay, 254 | "round1", 255 | uuid.clone(), 256 | ) 257 | .await; 258 | 259 | let mut j = 0; 260 | let mut bc1_vec: Vec = Vec::new(); 261 | let mut m_a_vec: Vec = Vec::new(); 262 | 263 | for i in 1..total_parties + 1 { 264 | if i == party_num_int { 265 | bc1_vec.push(com.clone()); 266 | // m_a_vec.push(m_a_k.clone()); 267 | } else { 268 | // if signers_vec.contains(&(i as usize)) { 269 | let (bc1_j, m_a_party_j): (SignBroadcastPhase1, MessageA) = 270 | serde_json::from_str(&round1_ans_vec[j]).unwrap(); 271 | bc1_vec.push(bc1_j); 272 | m_a_vec.push(m_a_party_j); 273 | 274 | j += 1; 275 | // } 276 | } 277 | } 278 | assert_eq!(signers_vec.len(), bc1_vec.len()); 279 | 280 | ////////////////////////////////////////////////////////////////////////////// 281 | let mut m_b_gamma_send_vec: Vec = Vec::new(); 282 | let mut beta_vec: Vec = Vec::new(); 283 | let mut m_b_w_send_vec: Vec = Vec::new(); 284 | let mut ni_vec: Vec = Vec::new(); 285 | let mut j = 0; 286 | for i in 1..(total_parties as usize) + 1 { 287 | if i != party_num_int as usize { 288 | let (m_b_gamma, beta_gamma, _, _) = MessageB::b( 289 | &sign_keys.gamma_i, 290 | &paillier_key_vector[signers_vec[i - 1] as usize], 291 | m_a_vec[j].clone(), 292 | &[], 293 | ) 294 | .unwrap(); 295 | let (m_b_w, beta_wi, _, _) = MessageB::b( 296 | &sign_keys.w_i, 297 | &paillier_key_vector[signers_vec[i - 1] as usize], 298 | m_a_vec[j].clone(), 299 | &[], 300 | ) 301 | .unwrap(); 302 | m_b_gamma_send_vec.push(m_b_gamma); 303 | m_b_w_send_vec.push(m_b_w); 304 | beta_vec.push(beta_gamma); 305 | ni_vec.push(beta_wi); 306 | j += 1; 307 | } 308 | } 309 | 310 | let mut j = 0; 311 | for i in 1..total_parties + 1 { 312 | if i != party_num_int { 313 | assert!(sendp2p( 314 | &addr, 315 | &client, 316 | party_num_int, 317 | i, 318 | "round2", 319 | serde_json::to_string(&( 320 | m_b_gamma_send_vec[j].clone(), 321 | m_b_w_send_vec[j].clone() 322 | )) 323 | .unwrap(), 324 | uuid.clone(), 325 | ) 326 | .await 327 | .is_ok()); 328 | j += 1; 329 | } 330 | } 331 | 332 | let round2_ans_vec = poll_for_p2p( 333 | &addr, 334 | &client, 335 | party_num_int, 336 | total_parties, 337 | delay, 338 | "round2", 339 | uuid.clone(), 340 | ) 341 | .await; 342 | 343 | let mut m_b_gamma_rec_vec: Vec = Vec::new(); 344 | let mut m_b_w_rec_vec: Vec = Vec::new(); 345 | 346 | for i in 0..total_parties - 1 { 347 | // if signers_vec.contains(&(i as usize)) { 348 | let (m_b_gamma_i, m_b_w_i): (MessageB, MessageB) = 349 | serde_json::from_str(&round2_ans_vec[i as usize]).unwrap(); 350 | m_b_gamma_rec_vec.push(m_b_gamma_i); 351 | m_b_w_rec_vec.push(m_b_w_i); 352 | // } 353 | } 354 | 355 | let mut alpha_vec: Vec = Vec::new(); 356 | let mut miu_vec: Vec = Vec::new(); 357 | 358 | let xi_com_vec = Keys::get_commitments_to_xi(&vss_scheme_vec); 359 | let mut j = 0; 360 | for i in 1..(total_parties as usize) + 1 { 361 | // println!("mbproof p={}, i={}, j={}", party_num_int, i, j); 362 | if i != party_num_int as usize { 363 | // println!("verifying: p={}, i={}, j={}", party_num_int, i, j); 364 | let m_b = m_b_gamma_rec_vec[j].clone(); 365 | 366 | let alpha_ij_gamma = m_b 367 | .verify_proofs_get_alpha(&party_keys.dk, &sign_keys.k_i) 368 | .expect("wrong dlog or m_b"); 369 | let m_b = m_b_w_rec_vec[j].clone(); 370 | let alpha_ij_wi = m_b 371 | .verify_proofs_get_alpha(&party_keys.dk, &sign_keys.k_i) 372 | .expect("wrong dlog or m_b"); 373 | alpha_vec.push(alpha_ij_gamma.0); 374 | miu_vec.push(alpha_ij_wi.0); 375 | let g_w_i = Keys::update_commitments_to_xi( 376 | &xi_com_vec[signers_vec[i - 1] as usize], 377 | &vss_scheme_vec[signers_vec[i - 1] as usize], 378 | signers_vec[i - 1], 379 | &signers_vec, 380 | ); 381 | //println!("Verifying client {}", party_num_int); 382 | assert_eq!(m_b.b_proof.pk.clone(), g_w_i); 383 | //println!("Verified client {}", party_num_int); 384 | j += 1; 385 | } 386 | } 387 | ////////////////////////////////////////////////////////////////////////////// 388 | let delta_i = sign_keys.phase2_delta_i(&alpha_vec, &beta_vec); 389 | let sigma = sign_keys.phase2_sigma_i(&miu_vec, &ni_vec); 390 | 391 | assert!(broadcast( 392 | &addr, 393 | &client, 394 | party_num_int, 395 | "round3", 396 | serde_json::to_string(&delta_i).unwrap(), 397 | uuid.clone(), 398 | ) 399 | .await 400 | .is_ok()); 401 | let round3_ans_vec = poll_for_broadcasts( 402 | &addr, 403 | &client, 404 | party_num_int, 405 | total_parties, 406 | delay, 407 | "round3", 408 | uuid.clone(), 409 | ) 410 | .await; 411 | let mut delta_vec: Vec = Vec::new(); 412 | format_vec_from_reads( 413 | &round3_ans_vec, 414 | party_num_int as usize, 415 | delta_i, 416 | &mut delta_vec, 417 | ); 418 | let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); 419 | 420 | ////////////////////////////////////////////////////////////////////////////// 421 | // decommit to gamma_i 422 | assert!(broadcast( 423 | &addr, 424 | &client, 425 | party_num_int, 426 | "round4", 427 | serde_json::to_string(&decommit).unwrap(), 428 | uuid.clone(), 429 | ) 430 | .await 431 | .is_ok()); 432 | let round4_ans_vec = poll_for_broadcasts( 433 | &addr, 434 | &client, 435 | party_num_int, 436 | total_parties, 437 | delay, 438 | "round4", 439 | uuid.clone(), 440 | ) 441 | .await; 442 | 443 | let mut decommit_vec: Vec = Vec::new(); 444 | format_vec_from_reads( 445 | &round4_ans_vec, 446 | party_num_int as usize, 447 | decommit, 448 | &mut decommit_vec, 449 | ); 450 | let decomm_i = decommit_vec.remove((party_num_int - 1) as usize); 451 | bc1_vec.remove((party_num_int - 1) as usize); 452 | let b_proof_vec = (0..m_b_gamma_rec_vec.len()) 453 | .map(|i| &m_b_gamma_rec_vec[i].b_proof) 454 | .collect::>>(); 455 | 456 | let R = SignKeys::phase4(&delta_inv, &b_proof_vec, decommit_vec, &bc1_vec) 457 | .expect("bad gamma_i decommit"); 458 | 459 | // adding local g_gamma_i 460 | let R = R + decomm_i.g_gamma_i * &delta_inv; 461 | 462 | // we assume the message is already hashed (by the signer). 463 | let message_bn = BigInt::from_bytes(message); 464 | // println!("message_bn INT: {}", message_bn); 465 | let message_int = BigInt::from_bytes(message); 466 | let two = BigInt::from(2); 467 | let message_bn = message_bn.modulus(&two.pow(256)); 468 | let local_sig = 469 | LocalSignature::phase5_local_sig(&sign_keys.k_i, &message_bn, &R, &sigma, &y_sum); 470 | 471 | let (phase5_com, phase_5a_decom, helgamal_proof, dlog_proof_rho) = 472 | local_sig.phase5a_broadcast_5b_zkproof(); 473 | 474 | //phase (5A) broadcast commit 475 | assert!(broadcast( 476 | &addr, 477 | &client, 478 | party_num_int, 479 | "round5", 480 | serde_json::to_string(&phase5_com).unwrap(), 481 | uuid.clone(), 482 | ) 483 | .await 484 | .is_ok()); 485 | let round5_ans_vec = poll_for_broadcasts( 486 | &addr, 487 | &client, 488 | party_num_int, 489 | total_parties, 490 | delay, 491 | "round5", 492 | uuid.clone(), 493 | ) 494 | .await; 495 | 496 | let mut commit5a_vec: Vec = Vec::new(); 497 | format_vec_from_reads( 498 | &round5_ans_vec, 499 | party_num_int as usize, 500 | phase5_com, 501 | &mut commit5a_vec, 502 | ); 503 | 504 | //phase (5B) broadcast decommit and (5B) ZK proof 505 | assert!(broadcast( 506 | &addr, 507 | &client, 508 | party_num_int, 509 | "round6", 510 | serde_json::to_string(&( 511 | phase_5a_decom.clone(), 512 | helgamal_proof.clone(), 513 | dlog_proof_rho.clone() 514 | )) 515 | .unwrap(), 516 | uuid.clone(), 517 | ) 518 | .await 519 | .is_ok()); 520 | let round6_ans_vec = poll_for_broadcasts( 521 | &addr, 522 | &client, 523 | party_num_int, 524 | total_parties, 525 | delay, 526 | "round6", 527 | uuid.clone(), 528 | ) 529 | .await; 530 | 531 | let mut decommit5a_and_elgamal_and_dlog_vec: Vec<( 532 | Phase5ADecom1, 533 | HomoELGamalProof, 534 | DLogProof, 535 | )> = Vec::new(); 536 | format_vec_from_reads( 537 | &round6_ans_vec, 538 | party_num_int as usize, 539 | ( 540 | phase_5a_decom.clone(), 541 | helgamal_proof.clone(), 542 | dlog_proof_rho.clone(), 543 | ), 544 | &mut decommit5a_and_elgamal_and_dlog_vec, 545 | ); 546 | let decommit5a_and_elgamal_vec_includes_i = decommit5a_and_elgamal_and_dlog_vec.clone(); 547 | decommit5a_and_elgamal_and_dlog_vec.remove((party_num_int - 1) as usize); 548 | commit5a_vec.remove((party_num_int - 1) as usize); 549 | let phase_5a_decomm_vec = (0..total_parties - 1) 550 | .map(|i| decommit5a_and_elgamal_and_dlog_vec[i as usize].0.clone()) 551 | .collect::>(); 552 | let phase_5a_elgamal_vec = (0..total_parties - 1) 553 | .map(|i| decommit5a_and_elgamal_and_dlog_vec[i as usize].1.clone()) 554 | .collect::>>(); 555 | let phase_5a_dlog_vec = (0..total_parties - 1) 556 | .map(|i| decommit5a_and_elgamal_and_dlog_vec[i as usize].2.clone()) 557 | .collect::>>(); 558 | let (phase5_com2, phase_5d_decom2) = local_sig 559 | .phase5c( 560 | &phase_5a_decomm_vec, 561 | &commit5a_vec, 562 | &phase_5a_elgamal_vec, 563 | &phase_5a_dlog_vec, 564 | &phase_5a_decom.V_i, 565 | &R.clone(), 566 | ) 567 | .expect("error phase5"); 568 | 569 | ////////////////////////////////////////////////////////////////////////////// 570 | assert!(broadcast( 571 | &addr, 572 | &client, 573 | party_num_int, 574 | "round7", 575 | serde_json::to_string(&phase5_com2).unwrap(), 576 | uuid.clone(), 577 | ) 578 | .await 579 | .is_ok()); 580 | let round7_ans_vec = poll_for_broadcasts( 581 | &addr, 582 | &client, 583 | party_num_int, 584 | total_parties, 585 | delay, 586 | "round7", 587 | uuid.clone(), 588 | ) 589 | .await; 590 | 591 | let mut commit5c_vec: Vec = Vec::new(); 592 | format_vec_from_reads( 593 | &round7_ans_vec, 594 | party_num_int as usize, 595 | phase5_com2, 596 | &mut commit5c_vec, 597 | ); 598 | 599 | //phase (5B) broadcast decommit and (5B) ZK proof 600 | assert!(broadcast( 601 | &addr, 602 | &client, 603 | party_num_int, 604 | "round8", 605 | serde_json::to_string(&phase_5d_decom2).unwrap(), 606 | uuid.clone(), 607 | ) 608 | .await 609 | .is_ok()); 610 | let round8_ans_vec = poll_for_broadcasts( 611 | &addr, 612 | &client, 613 | party_num_int, 614 | total_parties, 615 | delay, 616 | "round8", 617 | uuid.clone(), 618 | ) 619 | .await; 620 | 621 | let mut decommit5d_vec: Vec = Vec::new(); 622 | format_vec_from_reads( 623 | &round8_ans_vec, 624 | party_num_int as usize, 625 | phase_5d_decom2.clone(), 626 | &mut decommit5d_vec, 627 | ); 628 | 629 | let phase_5a_decomm_vec_includes_i = (0..total_parties) 630 | .map(|i| decommit5a_and_elgamal_vec_includes_i[i as usize].0.clone()) 631 | .collect::>(); 632 | let s_i = local_sig 633 | .phase5d( 634 | &decommit5d_vec, 635 | &commit5c_vec, 636 | &phase_5a_decomm_vec_includes_i, 637 | ) 638 | .expect("bad com 5d"); 639 | 640 | ////////////////////////////////////////////////////////////////////////////// 641 | assert!(broadcast( 642 | &addr, 643 | &client, 644 | party_num_int, 645 | "round9", 646 | serde_json::to_string(&s_i).unwrap(), 647 | uuid.clone(), 648 | ) 649 | .await 650 | .is_ok()); 651 | let round9_ans_vec = poll_for_broadcasts( 652 | &addr, 653 | &client, 654 | party_num_int, 655 | total_parties, 656 | delay, 657 | "round9", 658 | uuid.clone(), 659 | ) 660 | .await; 661 | 662 | let mut s_i_vec: Vec = Vec::new(); 663 | format_vec_from_reads(&round9_ans_vec, party_num_int as usize, s_i, &mut s_i_vec); 664 | 665 | s_i_vec.remove((party_num_int - 1) as usize); 666 | let sig = local_sig 667 | .output_signature(&s_i_vec) 668 | .expect("verification failed"); 669 | // println!(" \n"); 670 | // println!("party {:?} Output Signature: \n", party_num_int); 671 | // println!("SIG msg: {:?}", sig.m); 672 | // println!("R: {:?}", sig.r); 673 | // println!("s: {:?} \n", sig.s); 674 | // println!("child pubkey: {:?} \n", y_sum); 675 | 676 | // println!("pubkey: {:?} \n", y_sum); 677 | // println!("verifying signature with public key"); 678 | verify(&sig, &y_sum, &message_bn).expect("false"); 679 | // println!("verifying signature with child pub key"); 680 | // verify(&sig, &new_key, &message_bn).expect("false"); 681 | 682 | // println!("{:?}", sig.recid.clone()); 683 | // print(sig.recid.clone() 684 | 685 | let ret_dict = json!({ 686 | "r": (BigInt::from_bytes(sig.r.to_bytes().as_ref())).to_str_radix(16), 687 | "s": (BigInt::from_bytes(sig.s.to_bytes().as_ref())).to_str_radix(16), 688 | "status": "signature_ready", 689 | "recid": sig.recid.clone(), 690 | "x": &y_sum.x_coord().unwrap().to_hex(), 691 | "y": &y_sum.y_coord().unwrap().to_hex(), 692 | "msg_int": message_int, 693 | }); 694 | let signature: SignatureData = serde_json::from_value(ret_dict).unwrap(); 695 | match self 696 | .send_signature_to_manager(&addr, &client, &signature, request_id) 697 | .await 698 | { 699 | Ok(_) => info!("Signature sent to manager"), 700 | Err(e) => error!("Error sending signature to manager: {:?}", e), 701 | } 702 | // fs::write("signature".to_string(), sign_json).expect("Unable to save !"); 703 | 704 | // println!("Public key Y: {:?}", to_bitcoin_public_key(y_sum.get_element()).to_bytes()); 705 | // println!("Public child key X: {:?}", &new_key.x_coord()); 706 | // println!("Public child key Y: {:?}", &new_key.y_coord()); 707 | // println!("Public key big int: {:?}", &y_sum.bytes_compressed_to_big_int()); 708 | // println!("Public key ge: {:?}", &y_sum.get_element().serialize()); 709 | // println!("Public key ge: {:?}", PK::serialize_uncompressed(&y_sum.get_element())); 710 | // println!("New public key: {:?}", &y_sum.x_coor); 711 | } 712 | 713 | async fn signup( 714 | addr: &String, 715 | client: &Client, 716 | threshold: u16, 717 | room_id: String, 718 | party_id: u16, 719 | ) -> Result<(PartySignup, u16), ()> { 720 | let mut request_body = PartySignupRequestBody { 721 | threshold, 722 | room_id: room_id.clone(), 723 | party_number: party_id, 724 | party_uuid: "".to_string(), 725 | }; 726 | let path = "signupsign"; 727 | let delay = time::Duration::from_millis(100); 728 | let timeout = std::env::var("TSS_CLI_SIGNUP_TIMEOUT") 729 | .unwrap_or("30".to_string()) 730 | .parse::() 731 | .unwrap(); 732 | let res_body: String = postb(addr, client, path, request_body.clone()) 733 | .await 734 | .unwrap(); 735 | 736 | let answer: Result = 737 | serde_json::from_str(&res_body).unwrap(); 738 | let (output, total_parties) = match answer { 739 | Ok(SigningPartySignup { 740 | party_order, 741 | party_uuid, 742 | room_uuid, 743 | total_joined, 744 | }) => { 745 | println!( 746 | "Signed up, party order: {:?}, joined so far: {:?}, waiting for room uuid", 747 | party_order, total_joined 748 | ); 749 | let mut now = time::SystemTime::now(); 750 | let mut last_total_joined = total_joined; 751 | let mut party_signup = PartySignup { 752 | number: party_order, 753 | uuid: room_uuid, 754 | }; 755 | while party_signup.uuid.is_empty() { 756 | thread::sleep(delay); 757 | request_body.party_uuid = party_uuid.clone(); 758 | let res_body = postb(addr, client, path, request_body.clone()) 759 | .await 760 | .unwrap(); 761 | let answer: Result = 762 | serde_json::from_str(&res_body).unwrap(); 763 | match answer { 764 | Ok(SigningPartySignup { 765 | party_order, 766 | party_uuid, 767 | room_uuid, 768 | total_joined, 769 | }) => { 770 | request_body.party_uuid = party_uuid; 771 | if party_signup.number != party_order { 772 | party_signup.number = party_order; 773 | } 774 | party_signup.uuid = room_uuid; 775 | if total_joined != last_total_joined { 776 | last_total_joined = total_joined; 777 | //Reset the signup timeout 778 | now = time::SystemTime::now(); 779 | } 780 | } 781 | Err(ManagerError { error }) => { 782 | panic!("{}", error); 783 | } 784 | }; 785 | if now.elapsed().unwrap().as_secs() > timeout { 786 | break; 787 | } 788 | } 789 | if party_signup.uuid.is_empty() { 790 | panic!( 791 | "Could not get room uuid after {:?} seconds of tries", 792 | timeout 793 | ); 794 | } 795 | (party_signup, last_total_joined) 796 | } 797 | Err(ManagerError { error }) => { 798 | panic!("{}", error); 799 | } 800 | }; 801 | 802 | Ok((output, total_parties)) 803 | } 804 | 805 | async fn send_signature_to_manager( 806 | &self, 807 | addr: &str, 808 | client: &Client, 809 | signature: &SignatureData, 810 | request_id: &str, 811 | ) -> Result<()> { 812 | let signer_result = SignerResult { 813 | request_id: request_id.to_string(), 814 | signature: signature.clone(), 815 | }; 816 | let res_body = postb::(addr, client, "update_signing_result", signer_result) 817 | .await 818 | .unwrap(); 819 | let parsed: Value = serde_json::from_str(&res_body) 820 | .map_err(|err| anyhow!("Failed to parse response from manager: {:?}", err))?; 821 | 822 | match parsed { 823 | Value::Object(map) if map.contains_key("Ok") => { 824 | info!("Signature sent to manager"); 825 | Ok(()) 826 | } 827 | _ => Err(anyhow!("Failed to send signature to manager: {:?}", parsed)), 828 | } 829 | } 830 | } 831 | 832 | fn call_hd_key(path: &str, public_key: GE) -> (FE, GE) { 833 | let path_vector: Vec = path 834 | .split('/') 835 | .map(|s| BigInt::from_str_radix(s.trim(), 10).unwrap()) 836 | .collect(); 837 | let (public_key_child, f_l_new) = hd_keys::get_hd_key(&public_key, path_vector.clone()); 838 | (f_l_new, public_key_child.clone()) 839 | } 840 | 841 | fn format_vec_from_reads<'a, T: serde::Deserialize<'a> + Clone>( 842 | ans_vec: &'a Vec, 843 | party_num: usize, 844 | value_i: T, 845 | new_vec: &'a mut Vec, 846 | ) { 847 | let mut j = 0; 848 | for i in 1..ans_vec.len() + 2 { 849 | if i == party_num { 850 | new_vec.push(value_i.clone()); 851 | } else { 852 | let value_j: T = serde_json::from_str(&ans_vec[j]).unwrap(); 853 | new_vec.push(value_j); 854 | j += 1; 855 | } 856 | } 857 | } 858 | -------------------------------------------------------------------------------- /src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod mongodb; 2 | -------------------------------------------------------------------------------- /src/storage/mongodb.rs: -------------------------------------------------------------------------------- 1 | use crate::common::types::SigningRequest; 2 | use crate::common::{KeyGenRequest, KeysToStore, MessageStatus, MessageToSignStored, SignerResult}; 3 | use crate::error::TssError; 4 | use crate::manager::constants::MAX_MESSAGE_SIZE; 5 | use anyhow::Result; 6 | use mongodb::bson::{doc, to_document, Bson}; 7 | use mongodb::options::UpdateOptions; 8 | use mongodb::{Client, Collection}; 9 | 10 | pub struct MongoDBStorage { 11 | requests: Collection, 12 | keys_gen_requests: Collection, 13 | } 14 | 15 | impl MongoDBStorage { 16 | pub async fn new(uri: &str, db_name: &str) -> Result { 17 | let client = Client::with_uri_str(uri).await?; 18 | let db = client.database(db_name); 19 | 20 | Ok(Self { 21 | requests: db.collection::("messages_to_sign"), 22 | keys_gen_requests: db.collection::("keys_gen_requests"), 23 | }) 24 | } 25 | 26 | pub async fn insert_request(&self, request: &SigningRequest) -> Result<()> { 27 | if request.message.len() > MAX_MESSAGE_SIZE { 28 | return Err(TssError::MessageTooLarge.into()); 29 | } 30 | 31 | let message_to_sign = MessageToSignStored { 32 | request_id: request.id.clone(), 33 | message: request.message.clone(), 34 | status: MessageStatus::Pending, 35 | signature: None, 36 | }; 37 | self.requests.insert_one(message_to_sign, None).await?; 38 | Ok(()) 39 | } 40 | 41 | pub async fn insert_key_gen_request(&self, request: &KeyGenRequest) -> Result<()> { 42 | let keys_to_store = KeysToStore { 43 | request_id: request.id.clone(), 44 | status: MessageStatus::Pending, 45 | key_gen_params: request.keygen_params.clone(), 46 | keys: None, 47 | }; 48 | self.keys_gen_requests 49 | .insert_one(keys_to_store, None) 50 | .await?; 51 | Ok(()) 52 | } 53 | 54 | pub async fn update_key_gen_result(&self, request_id: &str, keys: Vec) -> Result<()> { 55 | let filter = doc! { "request_id": request_id }; 56 | if let Some(mut stored_keys) = self 57 | .keys_gen_requests 58 | .find_one(filter.clone(), None) 59 | .await? 60 | { 61 | if stored_keys.status == MessageStatus::Pending { 62 | stored_keys.keys = Some(keys); 63 | stored_keys.status = MessageStatus::Completed; 64 | let update_doc = to_document(&stored_keys)?; 65 | self.keys_gen_requests 66 | .update_one(filter, doc! { "$set": update_doc }, None) 67 | .await?; 68 | } 69 | } 70 | Ok(()) 71 | } 72 | 73 | pub async fn get_key_gen_result(&self, request_id: &str) -> Result> { 74 | // Validate UUID 75 | if uuid::Uuid::parse_str(&request_id).is_err() { 76 | return Err(TssError::InvalidUuid(request_id.to_string()).into()); 77 | } 78 | let filter = doc! { "request_id": request_id }; 79 | if let Some(doc) = self.keys_gen_requests.find_one(filter, None).await? { 80 | Ok(Some(doc)) 81 | } else { 82 | Ok(None) 83 | } 84 | } 85 | 86 | pub async fn get_signing_result(&self, id: &str) -> Result> { 87 | // Validate UUID 88 | if uuid::Uuid::parse_str(&id).is_err() { 89 | return Err(TssError::InvalidUuid(id.to_string()).into()); 90 | } 91 | let filter = doc! { "request_id": id }; 92 | if let Some(doc) = self.requests.find_one(filter, None).await? { 93 | Ok(Some(doc)) 94 | } else { 95 | Ok(None) 96 | } 97 | } 98 | 99 | pub async fn update_signing_result(&self, result: &SignerResult) -> Result<()> { 100 | // Validate UUID 101 | if uuid::Uuid::parse_str(&result.request_id).is_err() { 102 | return Err(TssError::InvalidUuid(result.request_id.clone()).into()); 103 | } 104 | let filter = doc! { 105 | "request_id": &result.request_id, 106 | "status": Bson::from(MessageStatus::Pending), 107 | }; 108 | 109 | let update = doc! { 110 | "$set": { 111 | "signature": to_document(&result.signature)?, 112 | "status": Bson::from(MessageStatus::Completed), 113 | } 114 | }; 115 | 116 | let options = UpdateOptions::builder().upsert(false).build(); 117 | 118 | //Perform atomic update 119 | self.requests.update_one(filter, update, options).await?; 120 | // If the status is not pending, simply return Ok() 121 | Ok(()) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/manager_service_tests.rs: -------------------------------------------------------------------------------- 1 | use reqwest::Client; 2 | use rocket::http::hyper::body; 3 | use rocket::response; 4 | use serde_json::{json, Value}; 5 | use std::process::{Command, Stdio}; 6 | use std::time::Duration; 7 | use tokio::time::{sleep, timeout}; 8 | use tss_network::common::{KeysToStore, MessageStatus, MessageToSignStored}; 9 | use tss_network::manager::api::{KeyGenResponseDTO, SigningResponseDTO}; 10 | 11 | fn build_project() { 12 | let status = Command::new("cargo") 13 | .args(["build", "--release"]) 14 | .status() 15 | .expect("Failed to build project"); 16 | 17 | assert!(status.success(), "Build failed"); 18 | } 19 | 20 | #[tokio::test] 21 | async fn test_signing_flow() { 22 | // Build the project 23 | build_project(); 24 | 25 | // Start the manager process 26 | let mut manager = Command::new("./target/release/manager") 27 | .stdout(Stdio::piped()) 28 | .stderr(Stdio::piped()) 29 | .spawn() 30 | .expect("Failed to start manager"); 31 | 32 | // Start the signer process 33 | let mut signer = Command::new("./target/release/signer") 34 | .stdout(Stdio::piped()) 35 | .stderr(Stdio::piped()) 36 | .spawn() 37 | .expect("Failed to start signer"); 38 | 39 | // Give some time for the processes to start up 40 | sleep(Duration::from_secs(5)).await; 41 | 42 | let client = Client::new(); 43 | let body = json!({ 44 | "message": "test_message" 45 | }); 46 | 47 | let response = client 48 | .post("http://127.0.0.1:8080/sign") 49 | .json(&body) 50 | .send() 51 | .await 52 | .expect("Failed to send request"); 53 | 54 | // Check if the request was successful 55 | assert!( 56 | response.status().is_success(), 57 | "Request failed with status: {}", 58 | response.status() 59 | ); 60 | 61 | let response_body: Value = response 62 | .json() 63 | .await 64 | .expect("Failed to parse response body"); 65 | let signing_res_dto: SigningResponseDTO = 66 | serde_json::from_value(response_body).expect("Failed to deserialize SigningResponseDTO"); 67 | 68 | // Add assertions to check the response 69 | assert!( 70 | !signing_res_dto.request_id.is_empty(), 71 | "request_id should not be empty" 72 | ); 73 | assert_eq!( 74 | signing_res_dto.status, "Pending", 75 | "status should be 'pending'" 76 | ); 77 | 78 | let timeout_duration = Duration::from_secs(60); // Adjust as needed 79 | let result = timeout( 80 | timeout_duration, 81 | poll_signing_result(&client, &signing_res_dto.request_id), 82 | ) 83 | .await 84 | .unwrap(); 85 | 86 | // For demonstration, we'll just check if the processes are still running 87 | assert!( 88 | manager.try_wait().expect("manager wait failed").is_none(), 89 | "Manager process exited prematurely" 90 | ); 91 | assert!( 92 | signer.try_wait().expect("signer wait failed").is_none(), 93 | "Signer process exited prematurely" 94 | ); 95 | // Clean up: kill the processes 96 | manager.kill().expect("Failed to kill manager"); 97 | signer.kill().expect("Failed to kill signer"); 98 | 99 | println!("Test completed successfully"); 100 | } 101 | 102 | #[tokio::test] 103 | async fn test_keygen_flow() { 104 | // Build the project 105 | build_project(); 106 | 107 | // Start the manager process 108 | let mut manager = Command::new("./target/release/manager") 109 | .stdout(Stdio::piped()) 110 | .stderr(Stdio::piped()) 111 | .spawn() 112 | .expect("Failed to start manager"); 113 | 114 | // Give some time for the processes to start up 115 | sleep(Duration::from_secs(5)).await; 116 | 117 | let client = Client::new(); 118 | 119 | let body = json!({ 120 | "threshold": 2, 121 | "total_parties": 3, 122 | "manager_url": "http://127.0.0.1:8080", 123 | }); 124 | 125 | let response = client 126 | .post("http://127.0.0.1:8080/key_gen_request") 127 | .json(&body) 128 | .send() 129 | .await 130 | .expect("Failed to send request"); 131 | 132 | // Check if the request was successful 133 | assert!( 134 | response.status().is_success(), 135 | "Request failed with status: {}", 136 | response.status() 137 | ); 138 | 139 | let response_body: Value = response 140 | .json() 141 | .await 142 | .expect("Failed to parse response body"); 143 | let keygen_res_dto: KeyGenResponseDTO = 144 | serde_json::from_value(response_body).expect("Failed to deserialize KeyGenResponseDTO"); 145 | 146 | assert!( 147 | !keygen_res_dto.request_id.is_empty(), 148 | "request_id should not be empty" 149 | ); 150 | assert_eq!( 151 | keygen_res_dto.keys.len(), 152 | 3, 153 | "keys should contain 3 elements" 154 | ); 155 | 156 | let stored_response = client 157 | .get(&format!( 158 | "http://127.0.0.1:8080/key_gen_result/{}", 159 | keygen_res_dto.request_id 160 | )) 161 | .send() 162 | .await 163 | .expect("Failed to send request"); 164 | 165 | let result: Option = stored_response 166 | .json() 167 | .await 168 | .expect("Failed to parse response"); 169 | 170 | assert!(result.clone().is_some(), "Result should not be None"); 171 | assert!( 172 | result.clone().unwrap().keys.unwrap().len() == 3, 173 | "Keys should contain 3 elements" 174 | ); 175 | let keys = result.unwrap().keys.unwrap(); 176 | for key in keys { 177 | assert!(!key.is_empty(), "Key should not be empty"); 178 | println!("Key: {}\n", key); 179 | } 180 | 181 | // Clean up: kill the processes 182 | manager.kill().expect("Failed to kill manager"); 183 | println!("key gen Test completed successfully"); 184 | } 185 | // Function to poll for signing result 186 | async fn poll_signing_result(client: &Client, request_id: &str) -> MessageToSignStored { 187 | let url = format!("http://127.0.0.1:8080/signing_result/{}", request_id); 188 | loop { 189 | let response = client 190 | .get(&url) 191 | .send() 192 | .await 193 | .expect("Failed to send request"); 194 | let result: Option = 195 | response.json().await.expect("Failed to parse response"); 196 | 197 | if let Some(stored_message) = result { 198 | match stored_message.status { 199 | MessageStatus::Completed => return stored_message, 200 | MessageStatus::Pending => { 201 | println!("Status still pending, waiting..."); 202 | sleep(Duration::from_secs(1)).await; 203 | } 204 | MessageStatus::InProgress => { 205 | println!("Status in progress, waiting..."); 206 | sleep(Duration::from_secs(1)).await; 207 | } 208 | } 209 | } else { 210 | panic!("Unexpected empty response"); 211 | } 212 | } 213 | } 214 | --------------------------------------------------------------------------------