From 1cf33e5ffd17f0d11e167e3fdc8ca585cefac4b1 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Mon, 18 Nov 2019 16:34:06 +0300 Subject: [PATCH] initial --- .gitattributes | 1 + .gitignore | 3 + LICENSE.md | 675 ++++++++++++++++++++++++++++++++ Makefile | 12 + README.md | 99 +++++ accounting/fixtures/cheque.sh | 8 + accounting/fixtures/cheque_data | Bin 0 -> 650 bytes accounting/service.go | 49 +++ accounting/service.pb.go | Bin 0 -> 18543 bytes accounting/service.proto | 23 ++ accounting/types.go | 353 +++++++++++++++++ accounting/types.pb.go | Bin 0 -> 93344 bytes accounting/types.proto | 106 +++++ accounting/types_test.go | 84 ++++ accounting/withdraw.go | 53 +++ accounting/withdraw.pb.go | Bin 0 -> 65250 bytes accounting/withdraw.proto | 61 +++ bootstrap/service.go | 11 + bootstrap/service.pb.go | Bin 0 -> 12895 bytes bootstrap/service.proto | 20 + bootstrap/types.go | 100 +++++ bootstrap/types.pb.go | Bin 0 -> 17190 bytes bootstrap/types.proto | 22 ++ chain/address.go | 185 +++++++++ chain/address_test.go | 292 ++++++++++++++ container/service.go | 68 ++++ container/service.pb.go | Bin 0 -> 53662 bytes container/service.proto | 68 ++++ container/types.go | 94 +++++ container/types.pb.go | Bin 0 -> 11819 bytes container/types.proto | 16 + container/types_test.go | 57 +++ decimal/decimal.go | 110 ++++++ decimal/decimal.pb.go | Bin 0 -> 8464 bytes decimal/decimal.proto | 14 + decimal/decimal_test.go | 445 +++++++++++++++++++++ go.mod | 22 ++ go.sum | 165 ++++++++ hash/hash.go | 98 +++++ hash/hash_test.go | 166 ++++++++ hash/hashesslice.go | 20 + hash/salt.go | 17 + internal/error.go | 7 + internal/proto.go | 16 + object/doc.go | 143 +++++++ object/extensions.go | 84 ++++ object/service.go | 215 ++++++++++ object/service.pb.go | Bin 0 -> 109487 bytes object/service.proto | 119 ++++++ object/sg.go | 66 ++++ object/sg_test.go | 87 ++++ object/types.go | 219 +++++++++++ object/types.pb.go | Bin 0 -> 91599 bytes object/types.proto | 107 +++++ object/utils.go | 107 +++++ object/verification.go | 132 +++++++ object/verification_test.go | 105 +++++ proto.go | 7 + query/types.go | 43 ++ query/types.pb.go | Bin 0 -> 15126 bytes query/types.proto | 25 ++ refs/address.go | 68 ++++ refs/cid.go | 96 +++++ refs/owner.go | 65 +++ refs/sgid.go | 14 + refs/types.go | 106 +++++ refs/types.pb.go | Bin 0 -> 9127 bytes refs/types.proto | 15 + refs/types_test.go | 112 ++++++ refs/uuid.go | 76 ++++ service/epoch.go | 7 + service/role.go | 24 ++ service/role_test.go | 22 ++ service/sign.go | 47 +++ service/ttl.go | 45 +++ service/ttl_test.go | 72 ++++ session/service.go | 57 +++ session/service.pb.go | Bin 0 -> 22812 bytes session/service.proto | 27 ++ session/store.go | 81 ++++ session/store_test.go | 84 ++++ session/types.go | 159 ++++++++ session/types.pb.go | Bin 0 -> 21017 bytes session/types.proto | 22 ++ state/service.go | 48 +++ state/service.pb.go | Bin 0 -> 29226 bytes state/service.proto | 37 ++ 87 files changed, 6283 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100755 accounting/fixtures/cheque.sh create mode 100644 accounting/fixtures/cheque_data create mode 100644 accounting/service.go create mode 100644 accounting/service.pb.go create mode 100644 accounting/service.proto create mode 100644 accounting/types.go create mode 100644 accounting/types.pb.go create mode 100644 accounting/types.proto create mode 100644 accounting/types_test.go create mode 100644 accounting/withdraw.go create mode 100644 accounting/withdraw.pb.go create mode 100644 accounting/withdraw.proto create mode 100644 bootstrap/service.go create mode 100644 bootstrap/service.pb.go create mode 100644 bootstrap/service.proto create mode 100644 bootstrap/types.go create mode 100644 bootstrap/types.pb.go create mode 100644 bootstrap/types.proto create mode 100644 chain/address.go create mode 100644 chain/address_test.go create mode 100644 container/service.go create mode 100644 container/service.pb.go create mode 100644 container/service.proto create mode 100644 container/types.go create mode 100644 container/types.pb.go create mode 100644 container/types.proto create mode 100644 container/types_test.go create mode 100644 decimal/decimal.go create mode 100644 decimal/decimal.pb.go create mode 100644 decimal/decimal.proto create mode 100644 decimal/decimal_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hash/hash.go create mode 100644 hash/hash_test.go create mode 100644 hash/hashesslice.go create mode 100644 hash/salt.go create mode 100644 internal/error.go create mode 100644 internal/proto.go create mode 100644 object/doc.go create mode 100644 object/extensions.go create mode 100644 object/service.go create mode 100644 object/service.pb.go create mode 100644 object/service.proto create mode 100644 object/sg.go create mode 100644 object/sg_test.go create mode 100644 object/types.go create mode 100644 object/types.pb.go create mode 100644 object/types.proto create mode 100644 object/utils.go create mode 100644 object/verification.go create mode 100644 object/verification_test.go create mode 100644 proto.go create mode 100644 query/types.go create mode 100644 query/types.pb.go create mode 100644 query/types.proto create mode 100644 refs/address.go create mode 100644 refs/cid.go create mode 100644 refs/owner.go create mode 100644 refs/sgid.go create mode 100644 refs/types.go create mode 100644 refs/types.pb.go create mode 100644 refs/types.proto create mode 100644 refs/types_test.go create mode 100644 refs/uuid.go create mode 100644 service/epoch.go create mode 100644 service/role.go create mode 100644 service/role_test.go create mode 100644 service/sign.go create mode 100644 service/ttl.go create mode 100644 service/ttl_test.go create mode 100644 session/service.go create mode 100644 session/service.pb.go create mode 100644 session/service.proto create mode 100644 session/store.go create mode 100644 session/store_test.go create mode 100644 session/types.go create mode 100644 session/types.pb.go create mode 100644 session/types.proto create mode 100644 state/service.go create mode 100644 state/service.pb.go create mode 100644 state/service.proto diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b002a5dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/**/*.pb.go -diff binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..22e0c656 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin +temp +/vendor/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..5c13ba2e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified +it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is +released under this License and any conditions added under +section 7. This requirement modifies the requirement in section 4 +to "keep intact all notices". +- c) You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either (1) a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or (2) access to copy the Corresponding +Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +- d) Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, +provided you inform other peers where the object code and +Corresponding Source of the work are being offered to the general +public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, +or requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors +or authors of the material; or +- e) Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions +of it) with contractual assumptions of liability to the recipient, +for any liability that these contractual assumptions directly +impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + +Copyright (C) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) +This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c17477a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +protoc: + @go mod tidy -v + @go mod vendor + # Install specific version for gogo-proto + @go list -f '{{.Path}}/...@{{.Version}}' -m github.com/gogo/protobuf | xargs go get -v + # Install specific version for protobuf lib + @go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go get -v + # Protoc generate + @find . -type f -name '*.proto' -not -path './vendor/*' \ + -exec protoc \ + --proto_path=.:./vendor \ + --gofast_out=plugins=grpc,paths=source_relative:. '{}' \; diff --git a/README.md b/README.md new file mode 100644 index 00000000..9247a10f --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# NeoFS-proto + +NeoFS-proto repository contains implementation of core NeoFS structures that +can be used for integration with NeoFS. + +## Description + +Repository contains 13 packages that implement NeoFS core structures. These +packages mostly contain protobuf files with service and structure definitions +or NeoFS core types with complemented functions. + +### Accounting + +Accounting package defines services and structures for accounting operations: +balance request and `cheque` operations for withdraw. `Cheque` is a structure +with inner ring signatures, which approve that user can withdraw requested +amount of assets. NeoFS smart contract takes binary formatted `cheque` as a +parameter in withdraw call. + +### Bootstrap + +Bootstrap package defines bootstrap service which is used by storage nodes to +connect to the storage network. + +### Chain + +Chain package contains util functions for operations with NEO Blockchain types: +wallet addresses, script-hashes. + +### Container + +Container package defines service and structures for operations with containers. +Objects in NeoFS are stored in containers. Container defines storage +policy for the objects. + +### Decimal + +Decimal defines custom decimal implementation which is used in accounting +operations. + +### Hash + +Hash package defines homomorphic hash type. + +### Internal + +Internal package defines constant error type and proto interface for custom +protobuf structures. + +### Object + +Object package defines service and structures for object operations. Object is +a core storage structure in NeoFS. Package contains detailed information +about object internal structure. + +### Query + +Query package defines structure for object search requests. + +### Refs + +Refs package defines core identity types: Object ID, Container ID, etc. + +### Service + +Service package defines util structure and functions for all NeoFS services +operations: TTL and request signature management, node roles, epoch retriever. + +### Session + +Session package defines service and structures for session obtain. Object +operations require an established session with pair of session keys signed by +owner of the object. + +### State + +State package defines service and structures for metrics gathering. + +## How to use + +NeoFS-proto packages contain godoc documentation. Examples of using most of +these packages can be found in NeoFS-CLI repository. CLI implements and +demonstrates all basic interactions with NeoFS: container, object, storage +group, and accounting operations. + +Protobuf files are recompiled with the command: + +``` +$ make protoc +``` + +## Contributing + +At this moment, we do not accept contributions. + +## License + +This project is licensed under the GPLv3 License - +see the [LICENSE.md](LICENSE.md) file for details diff --git a/accounting/fixtures/cheque.sh b/accounting/fixtures/cheque.sh new file mode 100755 index 00000000..93368138 --- /dev/null +++ b/accounting/fixtures/cheque.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +CHEQUE=d6520dabb6cb9b981792608c73670eff14775e9a65bbc189271723ba2703c53263e8d6e522dc32203339dcd8eee9c6b7439a0000000053724e000000000000001e61000603012d47e76210aec73be39ab3d186e0a40fe8d86bfa3d4fabfda57ba13b88f96abe1de4c7ecd46cb32081c0ff199e0b32708d2ce709dd146ce096484073a9b15a259ca799f8d848eb5bea16f6d0842a0181ccd47384af2cdb0fd0af0819e8a08802f7528ce97c9a93558efe7d4f62577aabdf771c931f54a71be6ad21e7d9cc1777686ad19b5dc4b80d7b8decf90054c5aad66c0e6fe63d8473b751cd77c1bd0557516e0f3e7d0ccb485809023b0c08a89f33ae38b2f99ce3f1ebc7905dddf0ed0f023e00f03a16e8707ce045eb42ee80d392451541ee510dc18e1c8befbac54d7426087d37d32d836537d317deafbbd193002a36f80fbdfbf3a730cf011bc6c75c7e6d5724f3adee7015fcb3068d321e2ae555e79107be0c46070efdae2f724dbc9f0340750b92789821683283bcb98e32b7e032b94f267b6964613fc31a7ce5813fddeea47a1db525634237e924178b5c8ea745549ae60aa3570ce6cf52e370e6ab87652bdf8a179176f1acaf48896bef9ab300818a53f410d86241d506a550f4915403fef27f744e829131d0ec980829fafa51db1714c2761d9f78762c008c323e9d6612e4f9efdc609f191fd9ca5431dd9dc037130150107ab8769780d728e9ffdf314019b57c8d2b940b9ec078afa951ed8b06c1bf352edd2037e29b8f24cca3ec700368a6f5829fb2a34fa03d0308ae6b05f433f2904d9a852fed1f5d2eb598ca79475b74ef6394e712d275cd798062c6d8e41fad822ac5a4fcb167f0a2e196f61f9f65a0adef9650f49150e7eb7bb08dd1739fa6e86b341f1b2cf5657fcd200637e8 +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +echo $CHEQUE | xxd -p -r > $DIR/cheque_data + +exit 0 diff --git a/accounting/fixtures/cheque_data b/accounting/fixtures/cheque_data new file mode 100644 index 0000000000000000000000000000000000000000..cd7b286642c3d1fc8d0080985c4d070013761b63 GIT binary patch literal 650 zcmV;50(Je?QVpxN%bS=Nl3BhH1ng9R*08?^K000000000UVE_gL0WC-8Vi2y!JL8(O(T3op59ruy`aMsp{iS=M zJBayez8&Pp?9^3o`#RgV6BPhwYktKWAVlOI&48|JMc=h@5`cW7$S zn_a}X4SS93`2bYKs@7}{Z{|IObGK2=cfq{{S5a;cK79q72;(~?CMLGDov!Hyh@@4CfJbS4OWH`6VHWjE6o z-mkmSlK?6<_z%7N^QSP+0UO50Tz+j=B=fEAa25Qs28}WvD&(&r;)X=BtNgE8mJ2k#_N{uSkh&@0zm!fr?Y~5ZGct)drs++d#>AKA)OG2NZOHxmI+5PGHps`KpC}sjVqK3p1^posZs5V2Eo5IF5MtE;+u~o%%kjZ18Ao8f}gUZPoO;m z2(D`d^fU61O`3%-?H^q(wV29zM_Y97W0dC-(sj*wfMUkjR literal 0 HcmV?d00001 diff --git a/accounting/service.go b/accounting/service.go new file mode 100644 index 00000000..df74e588 --- /dev/null +++ b/accounting/service.go @@ -0,0 +1,49 @@ +package accounting + +import ( + "github.com/nspcc-dev/neofs-proto/decimal" + "github.com/nspcc-dev/neofs-proto/internal" + "github.com/nspcc-dev/neofs-proto/refs" +) + +type ( + // OwnerID type alias. + OwnerID = refs.OwnerID + + // Decimal type alias. + Decimal = decimal.Decimal + + // Filter is used to filter accounts by criteria. + Filter func(acc *Account) bool +) + +const ( + // ErrEmptyAddress is raised when passed Address is empty. + ErrEmptyAddress = internal.Error("empty address") + + // ErrEmptyLockTarget is raised when passed LockTarget is empty. + ErrEmptyLockTarget = internal.Error("empty lock target") + + // ErrEmptyContainerID is raised when passed CID is empty. + ErrEmptyContainerID = internal.Error("empty container ID") + + // ErrEmptyParentAddress is raised when passed ParentAddress is empty. + ErrEmptyParentAddress = internal.Error("empty parent address") +) + +// SetTTL sets ttl to BalanceRequest to satisfy TTLRequest interface. +func (m BalanceRequest) SetTTL(v uint32) { m.TTL = v } + +// SumFunds goes through all accounts and sums up active funds. +func SumFunds(accounts []*Account) (res *decimal.Decimal) { + res = decimal.Zero.Copy() + + for i := range accounts { + if accounts[i] == nil { + continue + } + + res = res.Add(accounts[i].ActiveFunds) + } + return +} diff --git a/accounting/service.pb.go b/accounting/service.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..8708db374c6f37037fb8c01da66378b96ebd223f GIT binary patch literal 18543 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 literal 0 HcmV?d00001 diff --git a/accounting/service.proto b/accounting/service.proto new file mode 100644 index 00000000..b75bf9c9 --- /dev/null +++ b/accounting/service.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +package accounting; +option go_package = "github.com/nspcc-dev/neofs-proto/accounting"; + +import "decimal/decimal.proto"; +import "accounting/types.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option (gogoproto.stable_marshaler_all) = true; + +service Accounting { + rpc Balance(BalanceRequest) returns (BalanceResponse); +} + +message BalanceRequest { + bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + uint32 TTL = 2; +} + +message BalanceResponse { + decimal.Decimal Balance = 1; + repeated Account LockAccounts = 2; +} diff --git a/accounting/types.go b/accounting/types.go new file mode 100644 index 00000000..9c76fc75 --- /dev/null +++ b/accounting/types.go @@ -0,0 +1,353 @@ +package accounting + +import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/binary" + "reflect" + + "github.com/mr-tron/base58" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-proto/chain" + "github.com/nspcc-dev/neofs-proto/decimal" + "github.com/nspcc-dev/neofs-proto/internal" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/pkg/errors" +) + +type ( + // Cheque structure that describes a user request for withdrawal of funds. + Cheque struct { + ID ChequeID + Owner refs.OwnerID + Amount *decimal.Decimal + Height uint64 + Signatures []ChequeSignature + } + + // BalanceReceiver interface that is used to retrieve user balance by address. + BalanceReceiver interface { + Balance(accountAddress string) (*Account, error) + } + + // ChequeID is identifier of user request for withdrawal of funds. + ChequeID string + + // CID type alias. + CID = refs.CID + + // SGID type alias. + SGID = refs.SGID + + // ChequeSignature contains public key and hash, and is used to verify signatures. + ChequeSignature struct { + Key *ecdsa.PublicKey + Hash []byte + } +) + +const ( + // ErrWrongSignature is raised when wrong signature is passed. + ErrWrongSignature = internal.Error("wrong signature") + + // ErrWrongPublicKey is raised when wrong public key is passed. + ErrWrongPublicKey = internal.Error("wrong public key") + + // ErrWrongChequeData is raised when passed bytes cannot not be parsed as valid Cheque. + ErrWrongChequeData = internal.Error("wrong cheque data") + + // ErrInvalidLength is raised when passed bytes cannot not be parsed as valid ChequeID. + ErrInvalidLength = internal.Error("invalid length") + + u16size = 2 + u64size = 8 + + signaturesOffset = chain.AddressLength + refs.OwnerIDSize + u64size + u64size +) + +// NewChequeID generates valid random ChequeID using crypto/rand.Reader. +func NewChequeID() (ChequeID, error) { + d := make([]byte, chain.AddressLength) + if _, err := rand.Read(d); err != nil { + return "", err + } + + id := base58.Encode(d) + + return ChequeID(id), nil +} + +// String returns string representation of ChequeID. +func (b ChequeID) String() string { return string(b) } + +// Empty returns true, if ChequeID is empty. +func (b ChequeID) Empty() bool { return len(b) == 0 } + +// Valid validates ChequeID. +func (b ChequeID) Valid() bool { + d, err := base58.Decode(string(b)) + return err == nil && len(d) == chain.AddressLength +} + +// Bytes returns bytes representation of ChequeID. +func (b ChequeID) Bytes() []byte { + d, err := base58.Decode(string(b)) + if err != nil { + return make([]byte, chain.AddressLength) + } + return d +} + +// Equal checks that current ChequeID is equal to passed ChequeID. +func (b ChequeID) Equal(b2 ChequeID) bool { + return b.Valid() && b2.Valid() && string(b) == string(b2) +} + +// Unmarshal tries to parse []byte into valid ChequeID. +func (b *ChequeID) Unmarshal(data []byte) error { + *b = ChequeID(base58.Encode(data)) + if !b.Valid() { + return ErrInvalidLength + } + return nil +} + +// Size returns size (chain.AddressLength). +func (b ChequeID) Size() int { + return chain.AddressLength +} + +// MarshalTo tries to marshal ChequeID into passed bytes and returns +// count of copied bytes or error, if bytes len is not enough to contain ChequeID. +func (b ChequeID) MarshalTo(data []byte) (int, error) { + if len(data) < chain.AddressLength { + return 0, ErrInvalidLength + } + return copy(data, b.Bytes()), nil +} + +// Equals checks that m and tx are valid and equal Tx values. +func (m Tx) Equals(tx Tx) bool { + return m.From == tx.From && + m.To == tx.To && + m.Type == tx.Type && + m.Amount == tx.Amount +} + +// Verify validates current Cheque and Signatures that are generated for current Cheque. +func (b Cheque) Verify() error { + data := b.marshalBody() + for i, sign := range b.Signatures { + if err := crypto.VerifyRFC6979(sign.Key, data, sign.Hash); err != nil { + return errors.Wrapf(ErrWrongSignature, "item #%d: %s", i, err.Error()) + } + } + + return nil +} + +// Sign is used to sign current Cheque and stores result inside b.Signatures. +func (b *Cheque) Sign(key *ecdsa.PrivateKey) error { + hash, err := crypto.SignRFC6979(key, b.marshalBody()) + if err != nil { + return err + } + + b.Signatures = append(b.Signatures, ChequeSignature{ + Key: &key.PublicKey, + Hash: hash, + }) + + return nil +} + +func (b *Cheque) marshalBody() []byte { + buf := make([]byte, signaturesOffset) + + var offset int + + offset += copy(buf, b.ID.Bytes()) + offset += copy(buf[offset:], b.Owner.Bytes()) + + binary.BigEndian.PutUint64(buf[offset:], uint64(b.Amount.Value)) + offset += u64size + + binary.BigEndian.PutUint64(buf[offset:], b.Height) + + return buf +} + +func (b *Cheque) unmarshalBody(buf []byte) error { + var offset int + + if len(buf) < signaturesOffset { + return ErrWrongChequeData + } + + { // unmarshal UUID + if err := b.ID.Unmarshal(buf[offset : offset+chain.AddressLength]); err != nil { + return err + } + offset += chain.AddressLength + } + + { // unmarshal OwnerID + if err := b.Owner.Unmarshal(buf[offset : offset+refs.OwnerIDSize]); err != nil { + return err + } + offset += refs.OwnerIDSize + } + + { // unmarshal amount + amount := int64(binary.BigEndian.Uint64(buf[offset:])) + b.Amount = decimal.New(amount) + offset += u64size + } + + { // unmarshal height + b.Height = binary.BigEndian.Uint64(buf[offset:]) + offset += u64size + } + + return nil +} + +// MarshalBinary is used to marshal Cheque into bytes. +func (b Cheque) MarshalBinary() ([]byte, error) { + var ( + count = len(b.Signatures) + buf = make([]byte, b.Size()) + offset = copy(buf, b.marshalBody()) + ) + + binary.BigEndian.PutUint16(buf[offset:], uint16(count)) + offset += u16size + + for _, sign := range b.Signatures { + key := crypto.MarshalPublicKey(sign.Key) + offset += copy(buf[offset:], key) + offset += copy(buf[offset:], sign.Hash) + } + + return buf, nil +} + +// Size returns size of Cheque (count of bytes needs to store it). +func (b Cheque) Size() int { + return signaturesOffset + u16size + + len(b.Signatures)*(crypto.PublicKeyCompressedSize+crypto.RFC6979SignatureSize) +} + +// UnmarshalBinary tries to parse []byte into valid Cheque. +func (b *Cheque) UnmarshalBinary(buf []byte) error { + if err := b.unmarshalBody(buf); err != nil { + return err + } + + body := buf[:signaturesOffset] + + count := int64(binary.BigEndian.Uint16(buf[signaturesOffset:])) + offset := signaturesOffset + u16size + + if ln := count * int64(crypto.PublicKeyCompressedSize+crypto.RFC6979SignatureSize); ln > int64(len(buf[offset:])) { + return ErrWrongChequeData + } + + for i := int64(0); i < count; i++ { + sign := ChequeSignature{ + Key: crypto.UnmarshalPublicKey(buf[offset : offset+crypto.PublicKeyCompressedSize]), + Hash: make([]byte, crypto.RFC6979SignatureSize), + } + + offset += crypto.PublicKeyCompressedSize + if sign.Key == nil { + return errors.Wrapf(ErrWrongPublicKey, "item #%d", i) + } + + offset += copy(sign.Hash, buf[offset:offset+crypto.RFC6979SignatureSize]) + if err := crypto.VerifyRFC6979(sign.Key, body, sign.Hash); err != nil { + return errors.Wrapf(ErrWrongSignature, "item #%d: %s (offset=%d, len=%d)", i, err.Error(), offset, len(sign.Hash)) + } + + b.Signatures = append(b.Signatures, sign) + } + + return nil +} + +// ErrNotEnoughFunds generates error using address and amounts. +func ErrNotEnoughFunds(addr string, needed, residue *decimal.Decimal) error { + return errors.Errorf("not enough funds (requested=%s, residue=%s, addr=%s", needed, residue, addr) +} + +func (m *Account) hasLockAcc(addr string) bool { + for i := range m.LockAccounts { + if m.LockAccounts[i].Address == addr { + return true + } + } + return false +} + +// ValidateLock checks that account can be locked. +func (m *Account) ValidateLock() error { + switch { + case m.Address == "": + return ErrEmptyAddress + case m.ParentAddress == "": + return ErrEmptyParentAddress + case m.LockTarget == nil: + return ErrEmptyLockTarget + } + + switch v := m.LockTarget.Target.(type) { + case *LockTarget_WithdrawTarget: + if v.WithdrawTarget.Cheque != m.Address { + return errors.Errorf("wrong cheque ID: expected %s, has %s", m.Address, v.WithdrawTarget.Cheque) + } + case *LockTarget_ContainerCreateTarget: + switch { + case v.ContainerCreateTarget.CID.Empty(): + return ErrEmptyContainerID + } + } + return nil +} + +// CanLock checks possibility to lock funds. +func (m *Account) CanLock(lockAcc *Account) error { + switch { + case m.ActiveFunds.LT(lockAcc.ActiveFunds): + return ErrNotEnoughFunds(lockAcc.ParentAddress, lockAcc.ActiveFunds, m.ActiveFunds) + case m.hasLockAcc(lockAcc.Address): + return errors.Errorf("could not lock account(%s) funds: duplicating lock(%s)", m.Address, lockAcc.Address) + default: + return nil + } +} + +// LockForWithdraw checks that account contains locked funds by passed ChequeID. +func (m *Account) LockForWithdraw(chequeID string) bool { + switch v := m.LockTarget.Target.(type) { + case *LockTarget_WithdrawTarget: + return v.WithdrawTarget.Cheque == chequeID + } + return false +} + +// LockForContainerCreate checks that account contains locked funds for container creation. +func (m *Account) LockForContainerCreate(cid refs.CID) bool { + switch v := m.LockTarget.Target.(type) { + case *LockTarget_ContainerCreateTarget: + return v.ContainerCreateTarget.CID.Equal(cid) + } + return false +} + +// Equal checks that current Settlement is equal to passed Settlement. +func (m *Settlement) Equal(s *Settlement) bool { + if s == nil || m.Epoch != s.Epoch || len(m.Transactions) != len(s.Transactions) { + return false + } + return len(m.Transactions) == 0 || reflect.DeepEqual(m.Transactions, s.Transactions) +} diff --git a/accounting/types.pb.go b/accounting/types.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..cb4b0c8a1d41acb209fa31c9c7c6886fcbb1a07f GIT binary patch literal 93344 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̀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 literal 0 HcmV?d00001 diff --git a/accounting/types.proto b/accounting/types.proto new file mode 100644 index 00000000..1b4e7838 --- /dev/null +++ b/accounting/types.proto @@ -0,0 +1,106 @@ +syntax = "proto3"; +package accounting; +option go_package = "github.com/nspcc-dev/neofs-proto/accounting"; + +import "decimal/decimal.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option (gogoproto.stable_marshaler_all) = true; + +// Snapshot accounting messages +message Account { + bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + string Address = 2; + string ParentAddress = 3; + decimal.Decimal ActiveFunds = 4; + Lifetime Lifetime = 5 [(gogoproto.nullable) = false]; + LockTarget LockTarget = 6; + repeated Account LockAccounts = 7; +} + +message LockTarget { + oneof Target { + WithdrawTarget WithdrawTarget = 1; + ContainerCreateTarget ContainerCreateTarget = 2; + } +} + +// Snapshot balance messages +message Balances { + repeated Account Accounts = 1 [(gogoproto.nullable) = false]; +} + +// PayIn / PayOut messages +message PayIO { + uint64 BlockID = 1; + repeated Tx Transactions = 2 [(gogoproto.nullable) = false]; +} + +// Clearing messages +message Clearing { + repeated Tx Transactions = 1 [(gogoproto.nullable) = false]; +} + +// Clearing messages +message Withdraw { + string ID = 1; + uint64 Epoch = 2; + Tx Transaction = 3; +} + +// Lifetime of locks +message Lifetime { + enum Unit { + Unlimited = 0; + NeoFSEpoch = 1; + NeoBlock = 2; + } + + Unit unit = 1 [(gogoproto.customname) = "Unit"]; + int64 Value = 2; +} + +// Transaction messages +message Tx { + enum Type { + Unknown = 0; + Withdraw = 1; + PayIO = 2; + Inner = 3; + } + + Type type = 1 [(gogoproto.customname) = "Type"]; + string From = 2; + string To = 3; + decimal.Decimal Amount = 4; + bytes PublicKeys = 5; // of sender +} + +message Settlement { + message Receiver { + string To = 1; + decimal.Decimal Amount = 2; + } + + message Container { + bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; + repeated bytes SGIDs = 2 [(gogoproto.customtype) = "SGID", (gogoproto.nullable) = false]; + } + + message Tx { + string From = 1; + Container Container = 2 [(gogoproto.nullable) = false]; + repeated Receiver Receivers = 3 [(gogoproto.nullable) = false]; + } + + uint64 Epoch = 1; + repeated Tx Transactions = 2; +} + +message ContainerCreateTarget { + bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; +} + +message WithdrawTarget { + string Cheque = 1; +} diff --git a/accounting/types_test.go b/accounting/types_test.go new file mode 100644 index 00000000..c78e5b40 --- /dev/null +++ b/accounting/types_test.go @@ -0,0 +1,84 @@ +package accounting + +import ( + "io/ioutil" + "testing" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/nspcc-dev/neofs-proto/chain" + "github.com/nspcc-dev/neofs-proto/decimal" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/stretchr/testify/require" +) + +func TestCheque(t *testing.T) { + t.Run("new/valid", func(t *testing.T) { + id, err := NewChequeID() + require.NoError(t, err) + require.True(t, id.Valid()) + + d := make([]byte, chain.AddressLength+1) + + // expected size + 1 byte + str := base58.Encode(d) + require.False(t, ChequeID(str).Valid()) + + // expected size - 1 byte + str = base58.Encode(d[:len(d)-2]) + require.False(t, ChequeID(str).Valid()) + + // wrong encoding + d = d[:len(d)-1] // normal size + require.False(t, ChequeID(string(d)).Valid()) + }) + + t.Run("marshal/unmarshal", func(t *testing.T) { + var b2 = new(Cheque) + + key1 := test.DecodeKey(0) + key2 := test.DecodeKey(1) + + id, err := NewChequeID() + require.NoError(t, err) + + owner, err := refs.NewOwnerID(&key1.PublicKey) + require.NoError(t, err) + + b1 := &Cheque{ + ID: id, + Owner: owner, + Height: 100, + Amount: decimal.NewGAS(100), + } + + require.NoError(t, b1.Sign(key1)) + require.NoError(t, b1.Sign(key2)) + + data, err := b1.MarshalBinary() + require.NoError(t, err) + + require.Len(t, data, b1.Size()) + require.NoError(t, b2.UnmarshalBinary(data)) + require.Equal(t, b1, b2) + + require.NoError(t, b1.Verify()) + require.NoError(t, b2.Verify()) + }) + + t.Run("example from SC", func(t *testing.T) { + var pathToCheque = "fixtures/cheque_data" + expect, err := ioutil.ReadFile(pathToCheque) + require.NoError(t, err) + + var cheque Cheque + require.NoError(t, cheque.UnmarshalBinary(expect)) + + actual, err := cheque.MarshalBinary() + require.NoError(t, err) + + require.Equal(t, expect, actual) + + require.NoError(t, cheque.Verify()) + }) +} diff --git a/accounting/withdraw.go b/accounting/withdraw.go new file mode 100644 index 00000000..3cd27666 --- /dev/null +++ b/accounting/withdraw.go @@ -0,0 +1,53 @@ +package accounting + +import ( + "encoding/binary" + + "github.com/nspcc-dev/neofs-proto/refs" +) + +type ( + // MessageID type alias. + MessageID = refs.MessageID +) + +// SetTTL sets ttl to GetRequest to satisfy TTLRequest interface. +func (m *GetRequest) SetTTL(v uint32) { m.TTL = v } + +// SetTTL sets ttl to PutRequest to satisfy TTLRequest interface. +func (m *PutRequest) SetTTL(v uint32) { m.TTL = v } + +// SetTTL sets ttl to ListRequest to satisfy TTLRequest interface. +func (m *ListRequest) SetTTL(v uint32) { m.TTL = v } + +// SetTTL sets ttl to DeleteRequest to satisfy TTLRequest interface. +func (m *DeleteRequest) SetTTL(v uint32) { m.TTL = v } + +// SetSignature sets signature to PutRequest to satisfy SignedRequest interface. +func (m *PutRequest) SetSignature(v []byte) { m.Signature = v } + +// SetSignature sets signature to DeleteRequest to satisfy SignedRequest interface. +func (m *DeleteRequest) SetSignature(v []byte) { m.Signature = v } + +// PrepareData prepares bytes representation of PutRequest to satisfy SignedRequest interface. +func (m *PutRequest) PrepareData() ([]byte, error) { + var offset int + // MessageID-len + OwnerID-len + Amount + Height + buf := make([]byte, refs.UUIDSize+refs.OwnerIDSize+binary.MaxVarintLen64+binary.MaxVarintLen64) + offset += copy(buf[offset:], m.MessageID.Bytes()) + offset += copy(buf[offset:], m.OwnerID.Bytes()) + offset += binary.PutVarint(buf[offset:], m.Amount.Value) + binary.PutUvarint(buf[offset:], m.Height) + return buf, nil +} + +// PrepareData prepares bytes representation of DeleteRequest to satisfy SignedRequest interface. +func (m *DeleteRequest) PrepareData() ([]byte, error) { + var offset int + // ID-len + OwnerID-len + MessageID-len + buf := make([]byte, refs.UUIDSize+refs.OwnerIDSize+refs.UUIDSize) + offset += copy(buf[offset:], m.ID.Bytes()) + offset += copy(buf[offset:], m.OwnerID.Bytes()) + copy(buf[offset:], m.MessageID.Bytes()) + return buf, nil +} diff --git a/accounting/withdraw.pb.go b/accounting/withdraw.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..967a2ce0d59502e1b74628f3739bbbdfe282b345 GIT binary patch literal 65250 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 literal 0 HcmV?d00001 diff --git a/accounting/withdraw.proto b/accounting/withdraw.proto new file mode 100644 index 00000000..c099ef7c --- /dev/null +++ b/accounting/withdraw.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; +package accounting; +option go_package = "github.com/nspcc-dev/neofs-proto/accounting"; + +import "decimal/decimal.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option (gogoproto.stable_marshaler_all) = true; + +service Withdraw { + rpc Get(GetRequest) returns (GetResponse); + rpc Put(PutRequest) returns (PutResponse); + rpc List(ListRequest) returns (ListResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); +} + +message Item { + bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false]; + bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + decimal.Decimal Amount = 3; + uint64 Height = 4; + bytes Payload = 5; +} + +message GetRequest { + bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false]; + bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + uint32 TTL = 3; +} +message GetResponse { + Item Withdraw = 1; +} + +message PutRequest { + bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + decimal.Decimal Amount = 2; + uint64 Height = 3; + bytes MessageID = 4 [(gogoproto.customtype) = "MessageID", (gogoproto.nullable) = false]; + bytes Signature = 5; + uint32 TTL = 6; +} +message PutResponse { + bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false]; +} + +message ListRequest { + bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + uint32 TTL = 2; +} +message ListResponse { + repeated Item Items = 1; +} + +message DeleteRequest { + bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false]; + bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + bytes MessageID = 3 [(gogoproto.customtype) = "MessageID", (gogoproto.nullable) = false]; + bytes Signature = 4; + uint32 TTL = 5; +} +message DeleteResponse {} diff --git a/bootstrap/service.go b/bootstrap/service.go new file mode 100644 index 00000000..6d0d3ca9 --- /dev/null +++ b/bootstrap/service.go @@ -0,0 +1,11 @@ +package bootstrap + +import ( + "github.com/nspcc-dev/neofs-proto/service" +) + +// NodeType type alias. +type NodeType = service.NodeRole + +// SetTTL sets ttl to Request to satisfy TTLRequest interface. +func (m *Request) SetTTL(v uint32) { m.TTL = v } diff --git a/bootstrap/service.pb.go b/bootstrap/service.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..2fcdb4d20ebddedda631cd0d52a709618b312f29 GIT binary patch literal 12895 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 0 +} + +// SetFull changes state of node to indicate if node has enough space for storing users objects. +// If value is true - there's not enough space. +func (n *NodeStatus) SetFull(value bool) { + switch value { + case true: + *n |= NodeStatus(storageFullMask) + case false: + *n &= NodeStatus(^uint64(storageFullMask)) + } +} + +// Price returns price in 1e-8*GAS/Megabyte per month. +// User set price in GAS/Terabyte per month. +func (m NodeInfo) Price() uint64 { + for i := range m.Options { + if strings.HasPrefix(m.Options[i], optionPrice) { + n, err := strconv.ParseFloat(m.Options[i][len(optionPrice):], 64) + if err != nil { + return 0 + } + return uint64(n*1e8) / uint64(object.UnitsMB) // UnitsMB == megabytes in 1 terabyte + } + } + return 0 +} + +// Capacity returns node's capacity as reported by user. +func (m NodeInfo) Capacity() uint64 { + for i := range m.Options { + if strings.HasPrefix(m.Options[i], optionCapacity) { + n, err := strconv.ParseUint(m.Options[i][len(optionCapacity):], 10, 64) + if err != nil { + return 0 + } + return n + } + } + return 0 +} + +// String returns string representation of NodeInfo. +func (m NodeInfo) String() string { + return "(NodeInfo)<" + + "Address:" + m.Address + + ", " + + "PublicKey:" + hex.EncodeToString(m.PubKey) + + ", " + + "Options: [" + strings.Join(m.Options, ",") + "]>" +} + +// String returns string representation of SpreadMap. +func (m SpreadMap) String() string { + result := make([]string, 0, len(m.NetMap)) + for i := range m.NetMap { + result = append(result, m.NetMap[i].String()) + } + return "(SpreadMap)<" + + "Epoch: " + strconv.FormatUint(m.Epoch, 10) + + ", " + + "Netmap: [" + strings.Join(result, ",") + "]>" +} diff --git a/bootstrap/types.pb.go b/bootstrap/types.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..a90de05bba6ca107cde45da1d9883a9a27a89ac1 GIT binary patch literal 17190 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 OTqV+veeC^Vr}-Z!ETCHe literal 0 HcmV?d00001 diff --git a/bootstrap/types.proto b/bootstrap/types.proto new file mode 100644 index 00000000..4d6e458b --- /dev/null +++ b/bootstrap/types.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package bootstrap; +option go_package = "github.com/nspcc-dev/neofs-proto/bootstrap"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option (gogoproto.stable_marshaler_all) = true;; + +option (gogoproto.stringer_all) = false; +option (gogoproto.goproto_stringer_all) = false; + +message SpreadMap { + uint64 Epoch = 1; + repeated NodeInfo NetMap = 2 [(gogoproto.nullable) = false]; +} + +message NodeInfo { + string Address = 1 [(gogoproto.jsontag) = "address"]; + bytes PubKey = 2 [(gogoproto.jsontag) = "pubkey,omitempty"]; + repeated string Options = 3 [(gogoproto.jsontag) = "options,omitempty"]; + uint64 Status = 4 [(gogoproto.jsontag) = "status", (gogoproto.nullable) = false, (gogoproto.customtype) = "NodeStatus"]; +} diff --git a/chain/address.go b/chain/address.go new file mode 100644 index 00000000..e760d698 --- /dev/null +++ b/chain/address.go @@ -0,0 +1,185 @@ +package chain + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "encoding/hex" + + "github.com/mr-tron/base58" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-proto/internal" + "github.com/pkg/errors" + "golang.org/x/crypto/ripemd160" +) + +// WalletAddress implements NEO address. +type WalletAddress [AddressLength]byte + +const ( + // AddressLength contains size of address, + // 0x17 byte (address version) + 20 bytes of ScriptHash + 4 bytes of checksum. + AddressLength = 25 + + // ScriptHashLength contains size of ScriptHash. + ScriptHashLength = 20 + + // ErrEmptyAddress is raised when empty Address is passed. + ErrEmptyAddress = internal.Error("empty address") + + // ErrAddressLength is raised when passed address has wrong size. + ErrAddressLength = internal.Error("wrong address length") +) + +func checksum(sign []byte) []byte { + hash := sha256.Sum256(sign) + hash = sha256.Sum256(hash[:]) + return hash[:4] +} + +// FetchPublicKeys tries to parse public keys from verification script. +func FetchPublicKeys(vs []byte) []*ecdsa.PublicKey { + var ( + count int + offset int + ln = len(vs) + result []*ecdsa.PublicKey + ) + + switch { + case ln < 1: // wrong data size + return nil + case vs[ln-1] == 0xac: // last byte is CHECKSIG + count = 1 + case vs[ln-1] == 0xae: // last byte is CHECKMULTISIG + // 2nd byte from the end indicates about PK's count + count = int(vs[ln-2] - 0x50) + // ignores CHECKMULTISIG + offset = 1 + default: // unknown type + return nil + } + + result = make([]*ecdsa.PublicKey, 0, count) + for i := 0; i < count; i++ { + // ignores PUSHBYTE33 and tries to parse + from, to := offset+1, offset+1+crypto.PublicKeyCompressedSize + + // when passed VerificationScript has wrong size + if len(vs) < to { + return nil + } + + key := crypto.UnmarshalPublicKey(vs[from:to]) + // when wrong public key is passed + if key == nil { + return nil + } + result = append(result, key) + + offset += 1 + crypto.PublicKeyCompressedSize + } + return result +} + +// VerificationScript returns VerificationScript composed from public keys. +func VerificationScript(pubs ...*ecdsa.PublicKey) []byte { + var ( + pre []byte + suf []byte + body []byte + offset int + lnPK = len(pubs) + ln = crypto.PublicKeyCompressedSize*lnPK + lnPK // 33 * count + count * 1 (PUSHBYTES33) + ) + + if len(pubs) > 1 { + pre = []byte{0x51} // one address + suf = []byte{byte(0x50 + lnPK), 0xae} // count of PK's + CHECKMULTISIG + } else { + suf = []byte{0xac} // CHECKSIG + } + + ln += len(pre) + len(suf) + + body = make([]byte, ln) + offset += copy(body, pre) + + for i := range pubs { + body[offset] = 0x21 + offset++ + offset += copy(body[offset:], crypto.MarshalPublicKey(pubs[i])) + } + + copy(body[offset:], suf) + + return body +} + +// KeysToAddress return NEO address composed from public keys. +func KeysToAddress(pubs ...*ecdsa.PublicKey) string { + if len(pubs) == 0 { + return "" + } + return Address(VerificationScript(pubs...)) +} + +// Address returns NEO address based on passed VerificationScript. +func Address(verificationScript []byte) string { + sign := [AddressLength]byte{0x17} + hash := sha256.Sum256(verificationScript) + ripe := ripemd160.New() + ripe.Write(hash[:]) + copy(sign[1:], ripe.Sum(nil)) + copy(sign[21:], checksum(sign[:21])) + return base58.Encode(sign[:]) +} + +// ReversedScriptHashToAddress parses script hash and returns valid NEO address. +func ReversedScriptHashToAddress(sc string) (addr string, err error) { + var data []byte + if data, err = DecodeScriptHash(sc); err != nil { + return + } + sign := [AddressLength]byte{0x17} + copy(sign[1:], data) + copy(sign[1+ScriptHashLength:], checksum(sign[:1+ScriptHashLength])) + return base58.Encode(sign[:]), nil +} + +// IsAddress checks that passed NEO Address is valid. +func IsAddress(s string) error { + if s == "" { + return ErrEmptyAddress + } else if addr, err := base58.Decode(s); err != nil { + return errors.Wrap(err, "base58 decode") + } else if ln := len(addr); ln != AddressLength { + return errors.Wrapf(ErrAddressLength, "length %d != %d", AddressLength, ln) + } else if sum := checksum(addr[:21]); !bytes.Equal(addr[21:], sum) { + return errors.Errorf("wrong checksum %0x != %0x", + addr[21:], sum) + } + + return nil +} + +// ReverseBytes returns reversed []byte of given. +func ReverseBytes(data []byte) []byte { + for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { + data[i], data[j] = data[j], data[i] + } + return data +} + +// DecodeScriptHash parses script hash into slice of bytes. +func DecodeScriptHash(s string) ([]byte, error) { + if s == "" { + return nil, ErrEmptyAddress + } else if addr, err := hex.DecodeString(s); err != nil { + return nil, errors.Wrap(err, "hex decode") + } else if ln := len(addr); ln != ScriptHashLength { + return nil, errors.Wrapf(ErrAddressLength, "length %d != %d", ScriptHashLength, ln) + } else { + return addr, nil + } +} diff --git a/chain/address_test.go b/chain/address_test.go new file mode 100644 index 00000000..f83b1ddf --- /dev/null +++ b/chain/address_test.go @@ -0,0 +1,292 @@ +package chain + +import ( + "crypto/ecdsa" + "encoding/hex" + "testing" + + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +func TestAddress(t *testing.T) { + var ( + multiSigVerificationScript = "512103c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c57172103fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df952ae" + multiSigAddress = "ANbvKqa2SfgTUkq43NRUhCiyxPrpUPn7S3" + + normalVerificationScript = "2102a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61ac" + normalAddress = "AcraNnCuPKnUYtPYyrACRCVJhLpvskbfhu" + ) + + t.Run("check multi-sig address", func(t *testing.T) { + data, err := hex.DecodeString(multiSigVerificationScript) + require.NoError(t, err) + require.Equal(t, multiSigAddress, Address(data)) + }) + + t.Run("check normal address", func(t *testing.T) { + data, err := hex.DecodeString(normalVerificationScript) + require.NoError(t, err) + require.Equal(t, normalAddress, Address(data)) + }) +} + +func TestVerificationScript(t *testing.T) { + t.Run("check normal", func(t *testing.T) { + pkString := "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61" + + pkBytes, err := hex.DecodeString(pkString) + require.NoError(t, err) + + pk := crypto.UnmarshalPublicKey(pkBytes) + + expect, err := hex.DecodeString( + "21" + pkString + // PUSHBYTES33 + "ac", // CHECKSIG + ) + + require.Equal(t, expect, VerificationScript(pk)) + }) + + t.Run("check multisig", func(t *testing.T) { + pk1String := "03c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c5717" + pk2String := "03fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df9" + + pk1Bytes, err := hex.DecodeString(pk1String) + require.NoError(t, err) + + pk1 := crypto.UnmarshalPublicKey(pk1Bytes) + + pk2Bytes, err := hex.DecodeString(pk2String) + require.NoError(t, err) + + pk2 := crypto.UnmarshalPublicKey(pk2Bytes) + + expect, err := hex.DecodeString( + "51" + // one address + "21" + pk1String + // PUSHBYTES33 + "21" + pk2String + // PUSHBYTES33 + "52" + // 2 PublicKeys + "ae", // CHECKMULTISIG + ) + + require.Equal(t, expect, VerificationScript(pk1, pk2)) + }) +} + +func TestKeysToAddress(t *testing.T) { + t.Run("check normal", func(t *testing.T) { + pkString := "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61" + + pkBytes, err := hex.DecodeString(pkString) + require.NoError(t, err) + + pk := crypto.UnmarshalPublicKey(pkBytes) + + expect := "AcraNnCuPKnUYtPYyrACRCVJhLpvskbfhu" + + actual := KeysToAddress(pk) + require.Equal(t, expect, actual) + require.NoError(t, IsAddress(actual)) + }) + + t.Run("check multisig", func(t *testing.T) { + pk1String := "03c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c5717" + pk2String := "03fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df9" + + pk1Bytes, err := hex.DecodeString(pk1String) + require.NoError(t, err) + + pk1 := crypto.UnmarshalPublicKey(pk1Bytes) + + pk2Bytes, err := hex.DecodeString(pk2String) + require.NoError(t, err) + + pk2 := crypto.UnmarshalPublicKey(pk2Bytes) + + expect := "ANbvKqa2SfgTUkq43NRUhCiyxPrpUPn7S3" + actual := KeysToAddress(pk1, pk2) + require.Equal(t, expect, actual) + require.NoError(t, IsAddress(actual)) + }) +} + +func TestFetchPublicKeys(t *testing.T) { + var ( + multiSigVerificationScript = "512103c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c57172103fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df952ae" + normalVerificationScript = "2102a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61ac" + + pk1String = "03c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c5717" + pk2String = "03fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df9" + pk3String = "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61" + ) + + t.Run("shouls not fail", func(t *testing.T) { + wrongVS, err := hex.DecodeString(multiSigVerificationScript) + require.NoError(t, err) + + wrongVS[len(wrongVS)-1] = 0x1 + + wrongPK, err := hex.DecodeString(multiSigVerificationScript) + require.NoError(t, err) + wrongPK[2] = 0x1 + + var testCases = []struct { + name string + value []byte + }{ + {name: "empty VerificationScript"}, + { + name: "wrong size VerificationScript", + value: []byte{0x1}, + }, + { + name: "wrong VerificationScript type", + value: wrongVS, + }, + { + name: "wrong public key in VerificationScript", + value: wrongPK, + }, + } + + for i := range testCases { + tt := testCases[i] + t.Run(tt.name, func(t *testing.T) { + var keys []*ecdsa.PublicKey + require.NotPanics(t, func() { + keys = FetchPublicKeys(tt.value) + }) + require.Nil(t, keys) + }) + } + }) + + t.Run("check multi-sig address", func(t *testing.T) { + data, err := hex.DecodeString(multiSigVerificationScript) + require.NoError(t, err) + + pk1Bytes, err := hex.DecodeString(pk1String) + require.NoError(t, err) + + pk2Bytes, err := hex.DecodeString(pk2String) + require.NoError(t, err) + + pk1 := crypto.UnmarshalPublicKey(pk1Bytes) + pk2 := crypto.UnmarshalPublicKey(pk2Bytes) + + keys := FetchPublicKeys(data) + require.Len(t, keys, 2) + require.Equal(t, keys[0], pk1) + require.Equal(t, keys[1], pk2) + }) + + t.Run("check normal address", func(t *testing.T) { + data, err := hex.DecodeString(normalVerificationScript) + require.NoError(t, err) + + pkBytes, err := hex.DecodeString(pk3String) + require.NoError(t, err) + + pk := crypto.UnmarshalPublicKey(pkBytes) + + keys := FetchPublicKeys(data) + require.Len(t, keys, 1) + require.Equal(t, keys[0], pk) + }) + + t.Run("generate 10 keys VerificationScript and try parse it", func(t *testing.T) { + var ( + count = 10 + expect = make([]*ecdsa.PublicKey, 0, count) + ) + + for i := 0; i < count; i++ { + key := test.DecodeKey(i) + expect = append(expect, &key.PublicKey) + } + + vs := VerificationScript(expect...) + + actual := FetchPublicKeys(vs) + require.Equal(t, expect, actual) + }) +} + +func TestReversedScriptHashToAddress(t *testing.T) { + var testCases = []struct { + name string + value string + expect string + }{ + { + name: "first", + expect: "APfiG5imQgn8dzTTfaDfqHnxo3QDUkF69A", + value: "5696acd07f0927fd5f01946828638c9e2c90c5dc", + }, + + { + name: "second", + expect: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", + value: "23ba2703c53263e8d6e522dc32203339dcd8eee9", + }, + } + + for i := range testCases { + tt := testCases[i] + t.Run(tt.name, func(t *testing.T) { + actual, err := ReversedScriptHashToAddress(tt.value) + require.NoError(t, err) + require.Equal(t, tt.expect, actual) + require.NoError(t, IsAddress(actual)) + }) + } +} + +func TestReverseBytes(t *testing.T) { + var testCases = []struct { + name string + value []byte + expect []byte + }{ + {name: "empty"}, + { + name: "single byte", + expect: []byte{0x1}, + value: []byte{0x1}, + }, + + { + name: "two bytes", + expect: []byte{0x2, 0x1}, + value: []byte{0x1, 0x2}, + }, + + { + name: "three bytes", + expect: []byte{0x3, 0x2, 0x1}, + value: []byte{0x1, 0x2, 0x3}, + }, + + { + name: "five bytes", + expect: []byte{0x5, 0x4, 0x3, 0x2, 0x1}, + value: []byte{0x1, 0x2, 0x3, 0x4, 0x5}, + }, + + { + name: "eight bytes", + expect: []byte{0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1}, + value: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + }, + } + + for i := range testCases { + tt := testCases[i] + t.Run(tt.name, func(t *testing.T) { + actual := ReverseBytes(tt.value) + require.Equal(t, tt.expect, actual) + }) + } +} diff --git a/container/service.go b/container/service.go new file mode 100644 index 00000000..527377a7 --- /dev/null +++ b/container/service.go @@ -0,0 +1,68 @@ +package container + +import ( + "bytes" + "encoding/binary" + + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/pkg/errors" +) + +type ( + // CID type alias. + CID = refs.CID + // UUID type alias. + UUID = refs.UUID + // OwnerID type alias. + OwnerID = refs.OwnerID + // OwnerID type alias. + MessageID = refs.MessageID +) + +// SetTTL sets ttl to GetRequest to satisfy TTLRequest interface. +func (m *GetRequest) SetTTL(v uint32) { m.TTL = v } + +// SetTTL sets ttl to PutRequest to satisfy TTLRequest interface. +func (m *PutRequest) SetTTL(v uint32) { m.TTL = v } + +// SetTTL sets ttl to ListRequest to satisfy TTLRequest interface. +func (m *ListRequest) SetTTL(v uint32) { m.TTL = v } + +// SetTTL sets ttl to DeleteRequest to satisfy TTLRequest interface. +func (m *DeleteRequest) SetTTL(v uint32) { m.TTL = v } + +// SetSignature sets signature to PutRequest to satisfy SignedRequest interface. +func (m *PutRequest) SetSignature(v []byte) { m.Signature = v } + +// SetSignature sets signature to DeleteRequest to satisfy SignedRequest interface. +func (m *DeleteRequest) SetSignature(v []byte) { m.Signature = v } + +// PrepareData prepares bytes representation of PutRequest to satisfy SignedRequest interface. +func (m *PutRequest) PrepareData() ([]byte, error) { + var ( + err error + buf = new(bytes.Buffer) + capBytes = make([]byte, 8) + ) + + binary.BigEndian.PutUint64(capBytes, m.Capacity) + + if _, err = buf.Write(m.MessageID.Bytes()); err != nil { + return nil, errors.Wrap(err, "could not write message id") + } else if _, err = buf.Write(capBytes); err != nil { + return nil, errors.Wrap(err, "could not write capacity") + } else if _, err = buf.Write(m.OwnerID.Bytes()); err != nil { + return nil, errors.Wrap(err, "could not write pub") + } else if data, err := m.Rules.Marshal(); err != nil { + return nil, errors.Wrap(err, "could not marshal placement") + } else if _, err = buf.Write(data); err != nil { + return nil, errors.Wrap(err, "could not write placement") + } + + return buf.Bytes(), nil +} + +// PrepareData prepares bytes representation of DeleteRequest to satisfy SignedRequest interface. +func (m *DeleteRequest) PrepareData() ([]byte, error) { + return m.CID.Bytes(), nil +} diff --git a/container/service.pb.go b/container/service.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..bc2e8315415a646333365542818ea4fe09646b2b GIT binary patch literal 53662 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 LiDQqs2HpPv6vt{l literal 0 HcmV?d00001 diff --git a/container/types.proto b/container/types.proto new file mode 100644 index 00000000..b05001fb --- /dev/null +++ b/container/types.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package container; +option go_package = "github.com/nspcc-dev/neofs-proto/container"; + +import "github.com/nspcc-dev/netmap/selector.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option (gogoproto.stable_marshaler_all) = true; + +// The Container service definition. +message Container { + bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + bytes Salt = 2 [(gogoproto.customtype) = "UUID", (gogoproto.nullable) = false]; + uint64 Capacity = 3; + netmap.PlacementRule Rules = 4 [(gogoproto.nullable) = false]; +} diff --git a/container/types_test.go b/container/types_test.go new file mode 100644 index 00000000..c7dbbf8f --- /dev/null +++ b/container/types_test.go @@ -0,0 +1,57 @@ +package container + +import ( + "testing" + + "github.com/gogo/protobuf/proto" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/nspcc-dev/netmap" + "github.com/stretchr/testify/require" +) + +func TestCID(t *testing.T) { + t.Run("check that marshal/unmarshal works like expected", func(t *testing.T) { + var ( + c2 Container + cid2 CID + key = test.DecodeKey(0) + ) + + rules := netmap.PlacementRule{ + ReplFactor: 2, + SFGroups: []netmap.SFGroup{ + { + Selectors: []netmap.Select{ + {Key: "Country", Count: 1}, + {Key: netmap.NodesBucket, Count: 2}, + }, + Filters: []netmap.Filter{ + {Key: "Country", F: netmap.FilterIn("USA")}, + }, + }, + }, + } + + owner, err := refs.NewOwnerID(&key.PublicKey) + require.NoError(t, err) + + c1, err := New(10, owner, rules) + require.NoError(t, err) + + data, err := proto.Marshal(c1) + require.NoError(t, err) + + require.NoError(t, c2.Unmarshal(data)) + require.Equal(t, c1, &c2) + + cid1, err := c1.ID() + require.NoError(t, err) + + data, err = proto.Marshal(&cid1) + require.NoError(t, err) + require.NoError(t, cid2.Unmarshal(data)) + + require.Equal(t, cid1, cid2) + }) +} diff --git a/decimal/decimal.go b/decimal/decimal.go new file mode 100644 index 00000000..2ee00884 --- /dev/null +++ b/decimal/decimal.go @@ -0,0 +1,110 @@ +package decimal + +import ( + "math" + "strconv" + "strings" +) + +// GASPrecision contains precision for NEO Gas token. +const GASPrecision = 8 + +// Zero is empty Decimal value. +var Zero = &Decimal{} + +// New returns new Decimal (in satoshi). +func New(v int64) *Decimal { + return NewWithPrecision(v, GASPrecision) +} + +// NewGAS returns new Decimal * 1e8 (in GAS). +func NewGAS(v int64) *Decimal { + v *= int64(math.Pow10(GASPrecision)) + return NewWithPrecision(v, GASPrecision) +} + +// NewWithPrecision returns new Decimal with custom precision. +func NewWithPrecision(v int64, p uint32) *Decimal { + return &Decimal{Value: v, Precision: p} +} + +// ParseFloat return new Decimal parsed from float64 * 1e8 (in GAS). +func ParseFloat(v float64) *Decimal { + return new(Decimal).Parse(v, GASPrecision) +} + +// ParseFloatWithPrecision returns new Decimal parsed from float64 * 1^p. +func ParseFloatWithPrecision(v float64, p int) *Decimal { + return new(Decimal).Parse(v, p) +} + +// Copy returns copy of current Decimal. +func (m *Decimal) Copy() *Decimal { return &Decimal{Value: m.Value, Precision: m.Precision} } + +// Parse returns parsed Decimal from float64 * 1^p. +func (m *Decimal) Parse(v float64, p int) *Decimal { + m.Value = int64(v * math.Pow10(p)) + m.Precision = uint32(p) + return m +} + +// String returns string representation of Decimal. +func (m Decimal) String() string { + buf := new(strings.Builder) + val := m.Value + dec := int64(math.Pow10(int(m.Precision))) + if val < 0 { + buf.WriteRune('-') + val = -val + } + str := strconv.FormatInt(val/dec, 10) + buf.WriteString(str) + val %= dec + if val > 0 { + buf.WriteRune('.') + str = strconv.FormatInt(val, 10) + for i := len(str); i < int(m.Precision); i++ { + buf.WriteRune('0') + } + buf.WriteString(strings.TrimRight(str, "0")) + } + return buf.String() +} + +// Add returns d + m. +func (m Decimal) Add(d *Decimal) *Decimal { + precision := m.Precision + if precision < d.Precision { + precision = d.Precision + } + return &Decimal{ + Value: m.Value + d.Value, + Precision: precision, + } +} + +// Zero checks that Decimal is empty. +func (m Decimal) Zero() bool { return m.Value == 0 } + +// Equal checks that current Decimal is equal to passed Decimal. +func (m Decimal) Equal(v *Decimal) bool { return m.Value == v.Value && m.Precision == v.Precision } + +// GT checks that m > v. +func (m Decimal) GT(v *Decimal) bool { return m.Value > v.Value } + +// GTE checks that m >= v. +func (m Decimal) GTE(v *Decimal) bool { return m.Value >= v.Value } + +// LT checks that m < v. +func (m Decimal) LT(v *Decimal) bool { return m.Value < v.Value } + +// LTE checks that m <= v. +func (m Decimal) LTE(v *Decimal) bool { return m.Value <= v.Value } + +// Neg returns negative representation of current Decimal (m * -1). +func (m Decimal) Neg() *Decimal { + return &Decimal{ + Value: m.Value * -1, + Precision: m.Precision, + } +} diff --git a/decimal/decimal.pb.go b/decimal/decimal.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..e125af418fc17747c33a36b99d2c1002d42b8d3a GIT binary patch literal 8464 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 2 GAS", + expect: true, + values: [2]*Decimal{ + {Value: 5e8, Precision: GASPrecision}, + {Value: 2e8, Precision: GASPrecision}, + }, + }, + { + name: "100 GAS !> 100 GAS", + expect: false, + values: [2]*Decimal{ + {Value: 1e10, Precision: GASPrecision}, + {Value: 1e10, Precision: GASPrecision}, + }, + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + require.NotPanicsf(t, func() { + require.Truef(t, tt.expect == (tt.values[0].GT(tt.values[1])), tt.name) + }, tt.name) + }) + } +} + +func TestDecimal_GTE(t *testing.T) { + tests := []struct { + name string + expect bool + values [2]*Decimal + }{ + {name: "two zeros", expect: true, values: [2]*Decimal{Zero, Zero}}, + { + name: "5 GAS >= 2 GAS", + expect: true, + values: [2]*Decimal{ + {Value: 5e8, Precision: GASPrecision}, + {Value: 2e8, Precision: GASPrecision}, + }, + }, + { + name: "1 GAS !>= 100 GAS", + expect: false, + values: [2]*Decimal{ + {Value: 1e8, Precision: GASPrecision}, + {Value: 1e10, Precision: GASPrecision}, + }, + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + require.NotPanicsf(t, func() { + require.Truef(t, tt.expect == (tt.values[0].GTE(tt.values[1])), tt.name) + }, tt.name) + }) + } +} + +func TestDecimal_LT(t *testing.T) { + tests := []struct { + name string + expect bool + values [2]*Decimal + }{ + {name: "two zeros", expect: false, values: [2]*Decimal{Zero, Zero}}, + { + name: "5 GAS !< 2 GAS", + expect: false, + values: [2]*Decimal{ + {Value: 5e8, Precision: GASPrecision}, + {Value: 2e8, Precision: GASPrecision}, + }, + }, + { + name: "1 GAS < 100 GAS", + expect: true, + values: [2]*Decimal{ + {Value: 1e8, Precision: GASPrecision}, + {Value: 1e10, Precision: GASPrecision}, + }, + }, + { + name: "100 GAS !< 100 GAS", + expect: false, + values: [2]*Decimal{ + {Value: 1e10, Precision: GASPrecision}, + {Value: 1e10, Precision: GASPrecision}, + }, + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + require.NotPanicsf(t, func() { + require.Truef(t, tt.expect == (tt.values[0].LT(tt.values[1])), tt.name) + }, tt.name) + }) + } +} + +func TestDecimal_LTE(t *testing.T) { + tests := []struct { + name string + expect bool + values [2]*Decimal + }{ + {name: "two zeros", expect: true, values: [2]*Decimal{Zero, Zero}}, + { + name: "5 GAS <= 2 GAS", + expect: false, + values: [2]*Decimal{ + {Value: 5e8, Precision: GASPrecision}, + {Value: 2e8, Precision: GASPrecision}, + }, + }, + { + name: "1 GAS <= 100 GAS", + expect: true, + values: [2]*Decimal{ + {Value: 1e8, Precision: GASPrecision}, + {Value: 1e10, Precision: GASPrecision}, + }, + }, + { + name: "100 GAS !<= 1 GAS", + expect: false, + values: [2]*Decimal{ + {Value: 1e10, Precision: GASPrecision}, + {Value: 1e8, Precision: GASPrecision}, + }, + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + require.NotPanicsf(t, func() { + require.Truef(t, tt.expect == (tt.values[0].LTE(tt.values[1])), tt.name) + }, tt.name) + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..46490042 --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module github.com/nspcc-dev/neofs-proto + +go 1.13 + +require ( + code.cloudfoundry.org/bytefmt v0.0.0-20190819182555-854d396b647c + github.com/gogo/protobuf v1.3.1 + github.com/golang/protobuf v1.3.2 + github.com/google/uuid v1.1.1 + github.com/mr-tron/base58 v1.1.2 + github.com/nspcc-dev/neofs-crypto v0.2.1 + github.com/nspcc-dev/netmap v1.6.1 + github.com/nspcc-dev/tzhash v1.3.0 + github.com/onsi/ginkgo v1.10.2 // indirect + github.com/onsi/gomega v1.7.0 // indirect + github.com/pkg/errors v0.8.1 + github.com/prometheus/client_golang v1.2.1 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 + github.com/stretchr/testify v1.4.0 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + google.golang.org/grpc v1.24.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..6b48f3f5 --- /dev/null +++ b/go.sum @@ -0,0 +1,165 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +code.cloudfoundry.org/bytefmt v0.0.0-20190819182555-854d396b647c h1:2RuXx1+tSNWRjxhY0Bx52kjV2odJQ0a6MTbfTPhGAkg= +code.cloudfoundry.org/bytefmt v0.0.0-20190819182555-854d396b647c/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= +github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310 h1:t+qxRrRtwNiUYA+Xh2jSXhoG2grnMCMKX4Fg6lx9X1U= +github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nspcc-dev/hrw v1.0.8 h1:vwRuJXZXgkMvf473vFzeWGCfY1WBVeSHAEHvR4u3/Cg= +github.com/nspcc-dev/hrw v1.0.8/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkPG06MU= +github.com/nspcc-dev/neofs-crypto v0.2.1 h1:NxKexcW88vlHO/u7EYjx5Q1UaOQ7XhYrCsLSVgOcCxw= +github.com/nspcc-dev/neofs-crypto v0.2.1/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= +github.com/nspcc-dev/netmap v1.6.1 h1:Pigqpqi6QSdRiusbq5XlO20A18k6Eyu7j9MzOfAE3CM= +github.com/nspcc-dev/netmap v1.6.1/go.mod h1:mhV3UOg9ljQmu0teQShD6+JYX09XY5gu2I4hIByCH9M= +github.com/nspcc-dev/rfc6979 v0.1.0 h1:Lwg7esRRoyK1Up/IN1vAef1EmvrBeMHeeEkek2fAJ6c= +github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= +github.com/nspcc-dev/tzhash v1.3.0 h1:n6FTHsfPYbMi5Jmo6SwGVVRQD8i2w1P2ScCaW6rz69Q= +github.com/nspcc-dev/tzhash v1.3.0/go.mod h1:Lc4DersKS8MNIrunTmsAzANO56qnG+LZ4GOE/WYGVzU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +gopkg.in/abiosoft/ishell.v2 v2.0.0/go.mod h1:sFp+cGtH6o4s1FtpVPTMcHq2yue+c4DGOVohJCPUzwY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hash/hash.go b/hash/hash.go new file mode 100644 index 00000000..36884431 --- /dev/null +++ b/hash/hash.go @@ -0,0 +1,98 @@ +package hash + +import ( + "bytes" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neofs-proto/internal" + "github.com/nspcc-dev/tzhash/tz" + "github.com/pkg/errors" +) + +// HomomorphicHashSize contains size of HH. +const HomomorphicHashSize = 64 + +// Hash is implementation of HomomorphicHash. +type Hash [HomomorphicHashSize]byte + +// ErrWrongDataSize raised when wrong size of bytes is passed to unmarshal HH. +const ErrWrongDataSize = internal.Error("wrong data size") + +var ( + _ internal.Custom = (*Hash)(nil) + + emptyHH [HomomorphicHashSize]byte +) + +// Size returns size of Hash (HomomorphicHashSize). +func (h Hash) Size() int { return HomomorphicHashSize } + +// Empty checks that Hash is empty. +func (h Hash) Empty() bool { return bytes.Equal(h.Bytes(), emptyHH[:]) } + +// Reset sets current Hash to empty value. +func (h *Hash) Reset() { *h = Hash{} } + +// ProtoMessage method to satisfy proto.Message interface. +func (h Hash) ProtoMessage() {} + +// Bytes represents Hash as bytes. +func (h Hash) Bytes() []byte { + buf := make([]byte, HomomorphicHashSize) + copy(buf, h[:]) + return h[:] +} + +// Marshal returns bytes representation of Hash. +func (h Hash) Marshal() ([]byte, error) { return h.Bytes(), nil } + +// MarshalTo tries to marshal Hash into passed bytes and returns count of copied bytes. +func (h *Hash) MarshalTo(data []byte) (int, error) { return copy(data, h.Bytes()), nil } + +// Unmarshal tries to parse bytes into valid Hash. +func (h *Hash) Unmarshal(data []byte) error { + if ln := len(data); ln != HomomorphicHashSize { + return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", HomomorphicHashSize, ln) + } + + copy((*h)[:], data) + return nil +} + +// String returns string representation of Hash. +func (h Hash) String() string { return base58.Encode(h[:]) } + +// Equal checks that current Hash is equal to passed Hash. +func (h Hash) Equal(hash Hash) bool { return h == hash } + +// Verify validates if current hash generated from passed data. +func (h Hash) Verify(data []byte) bool { return h.Equal(Sum(data)) } + +// Validate checks if combined hashes are equal to current Hash. +func (h Hash) Validate(hashes []Hash) bool { + var hashBytes = make([][]byte, 0, len(hashes)) + for i := range hashes { + hashBytes = append(hashBytes, hashes[i].Bytes()) + } + ok, err := tz.Validate(h.Bytes(), hashBytes) + return err == nil && ok +} + +// Sum returns Tillich-Zémor checksum of data. +func Sum(data []byte) Hash { return tz.Sum(data) } + +// Concat combines hashes based on homomorphic property. +func Concat(hashes []Hash) (Hash, error) { + var ( + hash Hash + h = make([][]byte, 0, len(hashes)) + ) + for i := range hashes { + h = append(h, hashes[i].Bytes()) + } + cat, err := tz.Concat(h) + if err != nil { + return hash, err + } + return hash, hash.Unmarshal(cat) +} diff --git a/hash/hash_test.go b/hash/hash_test.go new file mode 100644 index 00000000..b3ef7035 --- /dev/null +++ b/hash/hash_test.go @@ -0,0 +1,166 @@ +package hash + +import ( + "bytes" + "crypto/rand" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func Test_Sum(t *testing.T) { + var ( + data = []byte("Hello world") + sum = Sum(data) + hash = []byte{0, 0, 0, 0, 1, 79, 16, 173, 134, 90, 176, 77, 114, 165, 253, 114, 0, 0, 0, 0, 0, 148, + 172, 222, 98, 248, 15, 99, 205, 129, 66, 91, 0, 0, 0, 0, 0, 138, 173, 39, 228, 231, 239, 123, + 170, 96, 186, 61, 0, 0, 0, 0, 0, 90, 69, 237, 131, 90, 161, 73, 38, 164, 185, 55} + ) + + require.Equal(t, hash, sum.Bytes()) +} + +func Test_Validate(t *testing.T) { + var ( + data = []byte("Hello world") + hash = Sum(data) + pieces = splitData(data, 2) + ln = len(pieces) + hashes = make([]Hash, 0, ln) + ) + + for i := 0; i < ln; i++ { + hashes = append(hashes, Sum(pieces[i])) + } + + require.True(t, hash.Validate(hashes)) +} + +func Test_Concat(t *testing.T) { + var ( + data = []byte("Hello world") + hash = Sum(data) + pieces = splitData(data, 2) + ln = len(pieces) + hashes = make([]Hash, 0, ln) + ) + + for i := 0; i < ln; i++ { + hashes = append(hashes, Sum(pieces[i])) + } + + res, err := Concat(hashes) + require.NoError(t, err) + require.Equal(t, hash, res) +} + +func Test_HashChunks(t *testing.T) { + var ( + chars = []byte("+") + size = 1400 + data = bytes.Repeat(chars, size) + hash = Sum(data) + count = 150 + ) + + hashes, err := dataHashes(data, count) + require.NoError(t, err) + require.Len(t, hashes, count) + + require.True(t, hash.Validate(hashes)) + + // 100 / 150 = 0 + hashes, err = dataHashes(data[:100], count) + require.Error(t, err) + require.Nil(t, hashes) +} + +func TestXOR(t *testing.T) { + var ( + dl = 10 + data = make([]byte, dl) + ) + + _, err := rand.Read(data) + require.NoError(t, err) + + t.Run("XOR with salt", func(t *testing.T) { + res := SaltXOR(data, nil) + require.Equal(t, res, data) + }) + + t.Run("XOR with empty salt", func(t *testing.T) { + xorWithSalt(t, data, 0) + }) + + t.Run("XOR with salt same data size", func(t *testing.T) { + xorWithSalt(t, data, dl) + }) + + t.Run("XOR with salt shorter than data aliquot", func(t *testing.T) { + xorWithSalt(t, data, dl/2) + }) + + t.Run("XOR with salt shorter than data aliquant", func(t *testing.T) { + xorWithSalt(t, data, dl/3/+1) + }) + + t.Run("XOR with salt longer than data aliquot", func(t *testing.T) { + xorWithSalt(t, data, dl*2) + }) + + t.Run("XOR with salt longer than data aliquant", func(t *testing.T) { + xorWithSalt(t, data, dl*2-1) + }) +} + +func xorWithSalt(t *testing.T, data []byte, saltSize int) { + var ( + direct, reverse []byte + salt = make([]byte, saltSize) + ) + + _, err := rand.Read(salt) + require.NoError(t, err) + + direct = SaltXOR(data, salt) + require.Len(t, direct, len(data)) + + reverse = SaltXOR(direct, salt) + require.Len(t, reverse, len(data)) + + require.Equal(t, reverse, data) +} + +func splitData(buf []byte, lim int) [][]byte { + var piece []byte + pieces := make([][]byte, 0, len(buf)/lim+1) + for len(buf) >= lim { + piece, buf = buf[:lim], buf[lim:] + pieces = append(pieces, piece) + } + if len(buf) > 0 { + pieces = append(pieces, buf) + } + return pieces +} + +func dataHashes(data []byte, count int) ([]Hash, error) { + var ( + ln = len(data) + mis = ln / count + off = (count - 1) * mis + hashes = make([]Hash, 0, count) + ) + if mis == 0 { + return nil, errors.Errorf("could not split %d bytes to %d pieces", ln, count) + } + + pieces := splitData(data[:off], mis) + pieces = append(pieces, data[off:]) + for i := 0; i < count; i++ { + hashes = append(hashes, Sum(pieces[i])) + } + return hashes, nil +} diff --git a/hash/hashesslice.go b/hash/hashesslice.go new file mode 100644 index 00000000..83bf4c5b --- /dev/null +++ b/hash/hashesslice.go @@ -0,0 +1,20 @@ +package hash + +import ( + "bytes" +) + +// HashesSlice is a collection that satisfies sort.Interface and can be +// sorted by the routines in sort package. +type HashesSlice []Hash + +// -- HashesSlice -- an inner type to sort Objects +// Len is the number of elements in the collection. +func (hs HashesSlice) Len() int { return len(hs) } + +// Less reports whether the element with +// index i should be sorted before the element with index j. +func (hs HashesSlice) Less(i, j int) bool { return bytes.Compare(hs[i].Bytes(), hs[j].Bytes()) == -1 } + +// Swap swaps the elements with indexes i and j. +func (hs HashesSlice) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] } diff --git a/hash/salt.go b/hash/salt.go new file mode 100644 index 00000000..5b6eeb0b --- /dev/null +++ b/hash/salt.go @@ -0,0 +1,17 @@ +package hash + +// SaltXOR xors bits of data with salt +// repeating salt if necessary. +func SaltXOR(data, salt []byte) (result []byte) { + result = make([]byte, len(data)) + ls := len(salt) + if ls == 0 { + copy(result, data) + return + } + + for i := range result { + result[i] = data[i] ^ salt[i%ls] + } + return +} diff --git a/internal/error.go b/internal/error.go new file mode 100644 index 00000000..7df16033 --- /dev/null +++ b/internal/error.go @@ -0,0 +1,7 @@ +package internal + +// Error is a custom error. +type Error string + +// Error is an implementation of error interface. +func (e Error) Error() string { return string(e) } diff --git a/internal/proto.go b/internal/proto.go new file mode 100644 index 00000000..951168b8 --- /dev/null +++ b/internal/proto.go @@ -0,0 +1,16 @@ +package internal + +import "github.com/gogo/protobuf/proto" + +// Custom contains methods to satisfy proto.Message +// including custom methods to satisfy protobuf for +// non-proto defined types. +type Custom interface { + Size() int + Empty() bool + Bytes() []byte + Marshal() ([]byte, error) + MarshalTo(data []byte) (int, error) + Unmarshal(data []byte) error + proto.Message +} diff --git a/object/doc.go b/object/doc.go new file mode 100644 index 00000000..d81495b0 --- /dev/null +++ b/object/doc.go @@ -0,0 +1,143 @@ +/* +Package object manages main storage structure in the system. All storage +operations are performed with the objects. During lifetime object might be +transformed into another object by cutting its payload or adding meta +information. All transformation may be reversed, therefore source object +will be able to restore. + +Object structure + +Object consists of Payload and Header. Payload is unlimited but storage nodes +may have a policy to store objects with a limited payload. In this case object +with large payload will be transformed into the chain of objects with small +payload. + +Headers are simple key-value fields that divided into two groups: system +headers and extended headers. System headers contain information about +protocol version, object id, payload length in bytes, owner id, container id +and object creation timestamp (both in epochs and unix time). All these fields +must be set up in the correct object. + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + | System Headers | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + | Version : 1 | + | Payload Length : 21673465 | + | Object ID : 465208e2-ba4f-4f99-ad47-82a59f4192d4 | + | Owner ID : AShvoCbSZ7VfRiPkVb1tEcBLiJrcbts1tt | + | Container ID : FGobtRZA6sBZv2i9k4L7TiTtnuP6E788qa278xfj3Fxj | + | Created At : Epoch#10, 1573033162 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + | Extended Headers | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + | User Header : , | + | Verification Header : , | + | Homomorphic Hash : 0x23d35a56ae... | + | Payload Checksum : 0x1bd34abs75... | + | Integrity Header :
, | + | Transformation : Payload Split | + | Link-parent : cae08935-b4ba-499a-bf6c-98276c1e6c0b | + | Link-next : c3b40fbf-3798-4b61-a189-2992b5fb5070 | + | Payload Checksum : 0x1f387a5c36... | + | Integrity Header :
, | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + | Payload | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + | 0xd1581963a342d231... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + +There are different kinds of extended headers. A correct object must contain +verification header, homomorphic hash header, payload checksum and +integrity header. The order of headers is matter. Let's look through all +these headers. + +Link header points to the connected objects. During object transformation, large +object might be transformed into the chain of smaller objects. One of these +objects drops payload and has several "Child" links. We call this object as +zero-object. Others will have "Parent" link to the zero-object, "Previous" +and "Next" links in the payload chain. + + [ Object ID:1 ] = > transformed + `- [ Zero-Object ID:1 ] + `- Link-child ID:2 + `- Link-child ID:3 + `- Link-child ID:4 + `- Payload [null] + `- [ Object ID:2 ] + `- Link-parent ID:1 + `- Link-next ID:3 + `- Payload [ 0x13ba... ] + `- [ Object ID:3 ] + `- Link-parent ID:1 + `- Link-previous ID:2 + `- Link-next ID:4 + `- Payload [ 0xcd34... ] + `- [ Object ID:4 ] + `- Link-parent ID:1 + `- Link-previous ID:3 + `- Payload [ 0xef86... ] + +Storage groups are also objects. They have "Storage Group" links to all +objects in the group. Links are set by nodes during transformations and, +in general, they should not be set by user manually. + +Redirect headers are not used yet, they will be implemented and described +later. + +User header is a key-value pair of string that can be defined by user. User +can use these headers as search attribute. You can store any meta information +about object there, e.g. object's nicename. + +Transformation header notifies that object was transformed by some pre-defined +way. This header sets up before object is transformed and all headers after +transformation must be located after transformation header. During reverse +transformation, all headers under transformation header will be cut out. + + +-+-+-+-+-+-+-+-+-+- +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+ + | Payload checksum | | Payload checksum | | Payload checksum | + | Integrity header | => | Integrity header | + | Integrity header | + +-+-+-+-+-+-+-+-+-+- | Transformation | | Transformation | + | Large payload | | New Checksum | | New Checksum | + +-+-+-+-+-+-+-+-+-+- | New Integrity | | New Integrity | + +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+ + | Small payload | | Small payload | + +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+ + +For now, we use only one type of transformation: payload split transformation. +This header set up by node automatically. + +Tombstone header notifies that this object was deleted by user. Objects with +tombstone header do not have payload, but they still contain meta information +in the headers. This way we implement two-phase commit for object removal. +Storage nodes will eventually delete all tombstone objects. If you want to +delete object, you must create new object with the same object id, with +tombstone header, correct signatures and without payload. + +Verification header contains session information. To put the object in +the system user must create session. It is required because objects might +be transformed and therefore must be re-signed. To do that node creates +a pair of session public and private keys. Object owner delegates permission to +re-sign objects by signing session public key. This header contains session +public key and owner's signature of this key. You must specify this header +manually. + +Homomorphic hash header contains homomorphic hash of the source object. +Transformations do not affect this header. This header used by data audit and +set by node automatically. + +Payload checksum contains checksum of the actual object payload. All payload +transformation must set new payload checksum headers. This header set by node +automatically. + +Integrity header contains checksum of the header and signature of the +session key. This header must be last in the list of extended headers. +Checksum is calculated by marshaling all above headers, including system +headers. This header set by node automatically. + +Storage group header is presented in storage group objects. It contains +information for data audit: size of validated data, homomorphic has of this +data, storage group expiration time in epochs or unix time. + + +*/ +package object diff --git a/object/extensions.go b/object/extensions.go new file mode 100644 index 00000000..1d896833 --- /dev/null +++ b/object/extensions.go @@ -0,0 +1,84 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-proto/hash" +) + +// IsLinking checks if object has children links to another objects. +// We have to check payload size because zero-object must have zero +// payload and non-zero payload length field in system header. +func (m Object) IsLinking() bool { + for i := range m.Headers { + switch v := m.Headers[i].Value.(type) { + case *Header_Link: + if v.Link.GetType() == Link_Child { + return m.SystemHeader.PayloadLength > 0 && len(m.Payload) == 0 + } + } + } + return false +} + +// VerificationHeader returns verification header if it is presented in extended headers. +func (m Object) VerificationHeader() (*VerificationHeader, error) { + _, vh := m.LastHeader(HeaderType(VerifyHdr)) + if vh == nil { + return nil, ErrHeaderNotFound + } + return vh.Value.(*Header_Verify).Verify, nil +} + +// SetVerificationHeader sets verification header in the object. +// It will replace existing verification header or add a new one. +func (m *Object) SetVerificationHeader(header *VerificationHeader) { + m.SetHeader(&Header{Value: &Header_Verify{Verify: header}}) +} + +// Links returns slice of ids of specified link type +func (m *Object) Links(t Link_Type) []ID { + var res []ID + for i := range m.Headers { + switch v := m.Headers[i].Value.(type) { + case *Header_Link: + if v.Link.GetType() == t { + res = append(res, v.Link.ID) + } + } + } + return res +} + +// Tombstone returns tombstone header if it is presented in extended headers. +func (m Object) Tombstone() *Tombstone { + _, h := m.LastHeader(HeaderType(TombstoneHdr)) + if h != nil { + return h.Value.(*Header_Tombstone).Tombstone + } + return nil +} + +// IsTombstone checks if object has tombstone header. +func (m Object) IsTombstone() bool { + n, _ := m.LastHeader(HeaderType(TombstoneHdr)) + return n != -1 +} + +// StorageGroup returns storage group structure if it is presented in extended headers. +func (m Object) StorageGroup() (*StorageGroup, error) { + _, sgHdr := m.LastHeader(HeaderType(StorageGroupHdr)) + if sgHdr == nil { + return nil, ErrHeaderNotFound + } + return sgHdr.Value.(*Header_StorageGroup).StorageGroup, nil +} + +// SetStorageGroup sets storage group header in the object. +// It will replace existing storage group header or add a new one. +func (m *Object) SetStorageGroup(sg *StorageGroup) { + m.SetHeader(&Header{Value: &Header_StorageGroup{StorageGroup: sg}}) +} + +// Empty checks if storage group has some data for validation. +func (m StorageGroup) Empty() bool { + return m.ValidationDataSize == 0 && m.ValidationHash.Equal(hash.Hash{}) +} diff --git a/object/service.go b/object/service.go new file mode 100644 index 00000000..098f9c31 --- /dev/null +++ b/object/service.go @@ -0,0 +1,215 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-proto/hash" + "github.com/nspcc-dev/neofs-proto/internal" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/nspcc-dev/neofs-proto/service" + "github.com/nspcc-dev/neofs-proto/session" +) + +type ( + // ID is a type alias of object id. + ID = refs.ObjectID + + // CID is a type alias of container id. + CID = refs.CID + + // SGID is a type alias of storage group id. + SGID = refs.SGID + + // OwnerID is a type alias of owner id. + OwnerID = refs.OwnerID + + // Hash is a type alias of Homomorphic hash. + Hash = hash.Hash + + // Token is a type alias of session token. + Token = session.Token + + // Request defines object rpc requests. + // All object operations must have TTL, Epoch, Container ID and + // permission of usage previous network map. + Request interface { + service.TTLRequest + service.EpochRequest + + CID() CID + AllowPreviousNetMap() bool + } +) + +const ( + // UnitsB starts enum for amount of bytes. + UnitsB int64 = 1 << (10 * iota) + + // UnitsKB defines amount of bytes in one kilobyte. + UnitsKB + + // UnitsMB defines amount of bytes in one megabyte. + UnitsMB + + // UnitsGB defines amount of bytes in one gigabyte. + UnitsGB + + // UnitsTB defines amount of bytes in one terabyte. + UnitsTB +) + +const ( + // ErrNotFound is raised when object is not found in the system. + ErrNotFound = internal.Error("could not find object") + + // ErrHeaderExpected is raised when first message in protobuf stream does not contain user header. + ErrHeaderExpected = internal.Error("expected header as a first message in stream") + + // KeyStorageGroup is a key for a search object by storage group id. + KeyStorageGroup = "STORAGE_GROUP" + + // KeyNoChildren is a key for searching object that have no children links. + KeyNoChildren = "LEAF" + + // KeyParent is a key for searching object by id of parent object. + KeyParent = "PARENT" + + // KeyHasParent is a key for searching object that have parent link. + KeyHasParent = "HAS_PAR" + + // KeyTombstone is a key for searching object that have tombstone header. + KeyTombstone = "TOMBSTONE" + + // KeyChild is a key for searching object by id of child link. + KeyChild = "CHILD" + + // KeyPrev is a key for searching object by id of previous link. + KeyPrev = "PREV" + + // KeyNext is a key for searching object by id of next link. + KeyNext = "NEXT" + + // KeyID is a key for searching object by object id. + KeyID = "ID" + + // KeyCID is a key for searching object by container id. + KeyCID = "CID" + + // KeyOwnerID is a key for searching object by owner id. + KeyOwnerID = "OWNERID" + + // KeyRootObject is a key for searching object that are zero-object or do + // not have any children. + KeyRootObject = "ROOT_OBJECT" +) + +func checkIsNotFull(v interface{}) bool { + var obj *Object + + switch t := v.(type) { + case *GetResponse: + obj = t.GetObject() + case *PutRequest: + if h := t.GetHeader(); h != nil { + obj = h.Object + } + default: + panic("unknown type") + } + + return obj == nil || obj.SystemHeader.PayloadLength != uint64(len(obj.Payload)) && !obj.IsLinking() +} + +// NotFull checks if protobuf stream provided whole object for get operation. +func (m *GetResponse) NotFull() bool { return checkIsNotFull(m) } + +// NotFull checks if protobuf stream provided whole object for put operation. +func (m *PutRequest) NotFull() bool { return checkIsNotFull(m) } + +// GetTTL returns TTL value from object put request. +func (m *PutRequest) GetTTL() uint32 { return m.GetHeader().TTL } + +// GetEpoch returns epoch value from object put request. +func (m *PutRequest) GetEpoch() uint64 { return m.GetHeader().GetEpoch() } + +// SetTTL sets TTL value into object put request. +func (m *PutRequest) SetTTL(ttl uint32) { m.GetHeader().TTL = ttl } + +// SetTTL sets TTL value into object get request. +func (m *GetRequest) SetTTL(ttl uint32) { m.TTL = ttl } + +// SetTTL sets TTL value into object head request. +func (m *HeadRequest) SetTTL(ttl uint32) { m.TTL = ttl } + +// SetTTL sets TTL value into object search request. +func (m *SearchRequest) SetTTL(ttl uint32) { m.TTL = ttl } + +// SetTTL sets TTL value into object delete request. +func (m *DeleteRequest) SetTTL(ttl uint32) { m.TTL = ttl } + +// SetTTL sets TTL value into object get range request. +func (m *GetRangeRequest) SetTTL(ttl uint32) { m.TTL = ttl } + +// SetTTL sets TTL value into object get range hash request. +func (m *GetRangeHashRequest) SetTTL(ttl uint32) { m.TTL = ttl } + +// SetEpoch sets epoch value into object put request. +func (m *PutRequest) SetEpoch(v uint64) { m.GetHeader().Epoch = v } + +// SetEpoch sets epoch value into object get request. +func (m *GetRequest) SetEpoch(v uint64) { m.Epoch = v } + +// SetEpoch sets epoch value into object head request. +func (m *HeadRequest) SetEpoch(v uint64) { m.Epoch = v } + +// SetEpoch sets epoch value into object search request. +func (m *SearchRequest) SetEpoch(v uint64) { m.Epoch = v } + +// SetEpoch sets epoch value into object delete request. +func (m *DeleteRequest) SetEpoch(v uint64) { m.Epoch = v } + +// SetEpoch sets epoch value into object get range request. +func (m *GetRangeRequest) SetEpoch(v uint64) { m.Epoch = v } + +// SetEpoch sets epoch value into object get range hash request. +func (m *GetRangeHashRequest) SetEpoch(v uint64) { m.Epoch = v } + +// CID returns container id value from object put request. +func (m *PutRequest) CID() CID { return m.GetHeader().Object.SystemHeader.CID } + +// CID returns container id value from object get request. +func (m *GetRequest) CID() CID { return m.Address.CID } + +// CID returns container id value from object head request. +func (m *HeadRequest) CID() CID { return m.Address.CID } + +// CID returns container id value from object search request. +func (m *SearchRequest) CID() CID { return m.ContainerID } + +// CID returns container id value from object delete request. +func (m *DeleteRequest) CID() CID { return m.Address.CID } + +// CID returns container id value from object get range request. +func (m *GetRangeRequest) CID() CID { return m.Address.CID } + +// CID returns container id value from object get range hash request. +func (m *GetRangeHashRequest) CID() CID { return m.Address.CID } + +// AllowPreviousNetMap returns permission to use previous network map in object put request. +func (m *PutRequest) AllowPreviousNetMap() bool { return false } + +// AllowPreviousNetMap returns permission to use previous network map in object get request. +func (m *GetRequest) AllowPreviousNetMap() bool { return true } + +// AllowPreviousNetMap returns permission to use previous network map in object head request. +func (m *HeadRequest) AllowPreviousNetMap() bool { return true } + +// AllowPreviousNetMap returns permission to use previous network map in object search request. +func (m *SearchRequest) AllowPreviousNetMap() bool { return true } + +// AllowPreviousNetMap returns permission to use previous network map in object delete request. +func (m *DeleteRequest) AllowPreviousNetMap() bool { return false } + +// AllowPreviousNetMap returns permission to use previous network map in object get range request. +func (m *GetRangeRequest) AllowPreviousNetMap() bool { return false } + +// AllowPreviousNetMap returns permission to use previous network map in object get range hash request. +func (m *GetRangeHashRequest) AllowPreviousNetMap() bool { return false } diff --git a/object/service.pb.go b/object/service.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..aede5cdaf2cfbf36068eeacda35974581054fbe1 GIT binary patch literal 109487 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 0; i-- { + if f != nil && f(&m.Headers[i]) { + return i, &m.Headers[i] + } + } + return -1, nil +} + +// AddHeader adds passed header to the end of extended header list. +func (m *Object) AddHeader(h *Header) { + m.Headers = append(m.Headers, *h) +} + +// SetPayload sets payload field and payload length in the system header. +func (m *Object) SetPayload(payload []byte) { + m.Payload = payload + m.SystemHeader.PayloadLength = uint64(len(payload)) +} + +// SetHeader replaces existing extended header or adds new one to the end of +// extended header list. +func (m *Object) SetHeader(h *Header) { + // looking for the header of that type + for i := range m.Headers { + if m.Headers[i].typeOf(h.Value) { + // if we found one - set it with new value and return + m.Headers[i] = *h + return + } + } + // if we did not find one - add this header + m.AddHeader(h) +} + +func (m Header) typeOf(t isHeader_Value) (ok bool) { + switch t.(type) { + case *Header_Link: + _, ok = m.Value.(*Header_Link) + case *Header_Redirect: + _, ok = m.Value.(*Header_Redirect) + case *Header_UserHeader: + _, ok = m.Value.(*Header_UserHeader) + case *Header_Transform: + _, ok = m.Value.(*Header_Transform) + case *Header_Tombstone: + _, ok = m.Value.(*Header_Tombstone) + case *Header_Verify: + _, ok = m.Value.(*Header_Verify) + case *Header_HomoHash: + _, ok = m.Value.(*Header_HomoHash) + case *Header_PayloadChecksum: + _, ok = m.Value.(*Header_PayloadChecksum) + case *Header_Integrity: + _, ok = m.Value.(*Header_Integrity) + case *Header_StorageGroup: + _, ok = m.Value.(*Header_StorageGroup) + } + return +} + +// HeaderType returns predicate that check if extended header is a header +// of specified type. +func HeaderType(t headerType) Pred { + switch t { + case LinkHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_Link); return ok } + case RedirectHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_Redirect); return ok } + case UserHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_UserHeader); return ok } + case TransformHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_Transform); return ok } + case TombstoneHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_Tombstone); return ok } + case VerifyHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_Verify); return ok } + case HomoHashHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_HomoHash); return ok } + case PayloadChecksumHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_PayloadChecksum); return ok } + case IntegrityHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_Integrity); return ok } + case StorageGroupHdr: + return func(h *Header) bool { _, ok := h.Value.(*Header_StorageGroup); return ok } + default: + return nil + } +} + +// Copy creates full copy of the object. +func (m *Object) Copy() (obj *Object) { + obj = new(Object) + m.CopyTo(obj) + return +} + +// CopyTo creates fills passed object with the data from the current object. +// This function creates copies on every available data slice. +func (m *Object) CopyTo(o *Object) { + o.SystemHeader = m.SystemHeader + o.Headers = make([]Header, len(m.Headers)) + o.Payload = make([]byte, len(m.Payload)) + + for i := range m.Headers { + switch v := m.Headers[i].Value.(type) { + case *Header_Link: + link := *v.Link + o.Headers[i] = Header{ + Value: &Header_Link{ + Link: &link, + }, + } + case *Header_HomoHash: + o.Headers[i] = Header{ + Value: &Header_HomoHash{ + HomoHash: v.HomoHash, + }, + } + default: + o.Headers[i] = *proto.Clone(&m.Headers[i]).(*Header) + } + } + + copy(o.Payload, m.Payload) +} + +// Address returns object's address. +func (m Object) Address() *refs.Address { + return &refs.Address{ + ObjectID: m.SystemHeader.ID, + CID: m.SystemHeader.CID, + } +} diff --git a/object/types.pb.go b/object/types.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..99b6c90fadc29d7b37401dfd813966a1becaaf8b GIT binary patch literal 91599 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 u9I=_Y@`R$(NcALw*;O`3`pV9#T1xvdUdyeqwMj l { + last = l + } + result = append(result, data[i:last]) + } + return +} + +// SendPutRequest prepares object and sends it in chunks through protobuf stream. +func SendPutRequest(s Service_PutClient, obj *Object, epoch uint64, ttl uint32) (*PutResponse, error) { + // TODO split must take into account size of the marshalled Object + chunks := splitBytes(obj.Payload, maxGetPayloadSize) + obj.Payload = chunks[0] + if err := s.Send(MakePutRequestHeader(obj, epoch, ttl, nil)); err != nil { + return nil, err + } + for i := range chunks[1:] { + if err := s.Send(MakePutRequestChunk(chunks[i+1])); err != nil { + return nil, err + } + } + resp, err := s.CloseAndRecv() + if err != nil && err != io.EOF { + return nil, err + } + return resp, nil +} + +// MakePutRequestHeader combines object, epoch, ttl and session token value +// into header of object put request. +func MakePutRequestHeader(obj *Object, epoch uint64, ttl uint32, token *session.Token) *PutRequest { + return &PutRequest{ + R: &PutRequest_Header{ + Header: &PutRequest_PutHeader{ + Epoch: epoch, + Object: obj, + TTL: ttl, + Token: token, + }, + }, + } +} + +// MakePutRequestChunk splits data into chunks that will be transferred +// in the protobuf stream. +func MakePutRequestChunk(chunk []byte) *PutRequest { + return &PutRequest{R: &PutRequest_Chunk{Chunk: chunk}} +} + +func errMaxSizeExceeded(size uint64) error { + return errors.Errorf("object payload size exceed: %s", bytefmt.ByteSize(size)) +} + +// ReceiveGetResponse receives object by chunks from the protobuf stream +// and combine it into single get response structure. +func ReceiveGetResponse(c Service_GetClient, maxSize uint64) (*GetResponse, error) { + res, err := c.Recv() + if err == io.EOF { + return res, err + } else if err != nil { + return nil, err + } + + obj := res.GetObject() + if obj == nil { + return nil, ErrHeaderExpected + } + + if obj.SystemHeader.PayloadLength > maxSize { + return nil, errMaxSizeExceeded(maxSize) + } + + if res.NotFull() { + payload := make([]byte, obj.SystemHeader.PayloadLength) + offset := copy(payload, obj.Payload) + + var r *GetResponse + for r, err = c.Recv(); err == nil; r, err = c.Recv() { + offset += copy(payload[offset:], r.GetChunk()) + } + if err != io.EOF { + return nil, err + } + obj.Payload = payload + } + + return res, nil +} diff --git a/object/verification.go b/object/verification.go new file mode 100644 index 00000000..761c04e2 --- /dev/null +++ b/object/verification.go @@ -0,0 +1,132 @@ +package object + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/pkg/errors" +) + +func (m Object) headersData(check bool) ([]byte, error) { + var bytebuf = new(bytes.Buffer) + + // fixme: we must marshal fields one by one without protobuf marshaling + // protobuf marshaling does not guarantee the same result + + if sysheader, err := m.SystemHeader.Marshal(); err != nil { + return nil, err + } else if _, err := bytebuf.Write(sysheader); err != nil { + return nil, err + } + + n, _ := m.LastHeader(HeaderType(IntegrityHdr)) + for i := range m.Headers { + if check && i == n { + // ignore last integrity header in order to check headers data + continue + } + + if header, err := m.Headers[i].Marshal(); err != nil { + return nil, err + } else if _, err := bytebuf.Write(header); err != nil { + return nil, err + } + } + return bytebuf.Bytes(), nil +} + +func (m Object) headersChecksum(check bool) ([]byte, error) { + data, err := m.headersData(check) + if err != nil { + return nil, err + } + checksum := sha256.Sum256(data) + return checksum[:], nil +} + +// PayloadChecksum calculates sha256 checksum of object payload. +func (m Object) PayloadChecksum() []byte { + checksum := sha256.Sum256(m.Payload) + return checksum[:] +} + +func (m Object) verifySignature(key []byte, ih *IntegrityHeader) error { + pk := crypto.UnmarshalPublicKey(key) + if crypto.Verify(pk, ih.HeadersChecksum, ih.ChecksumSignature) == nil { + return nil + } + return ErrVerifySignature +} + +// Verify performs local integrity check by finding verification header and +// integrity header. If header integrity is passed, function verifies +// checksum of the object payload. +func (m Object) Verify() error { + var ( + err error + checksum []byte + ) + // Prepare structures + _, vh := m.LastHeader(HeaderType(VerifyHdr)) + if vh == nil { + return ErrHeaderNotFound + } + verify := vh.Value.(*Header_Verify).Verify + + _, ih := m.LastHeader(HeaderType(IntegrityHdr)) + if ih == nil { + return ErrHeaderNotFound + } + integrity := ih.Value.(*Header_Integrity).Integrity + + // Verify signature + err = m.verifySignature(verify.PublicKey, integrity) + if err != nil { + return errors.Wrapf(err, "public key: %x", verify.PublicKey) + } + + // Verify checksum of header + checksum, err = m.headersChecksum(true) + if err != nil { + return err + } + if !bytes.Equal(integrity.HeadersChecksum, checksum) { + return ErrVerifyHeader + } + + // Verify checksum of payload + if m.SystemHeader.PayloadLength > 0 && !m.IsLinking() { + checksum = m.PayloadChecksum() + + _, ph := m.LastHeader(HeaderType(PayloadChecksumHdr)) + if ph == nil { + return ErrHeaderNotFound + } + if !bytes.Equal(ph.Value.(*Header_PayloadChecksum).PayloadChecksum, checksum) { + return ErrVerifyPayload + } + } + return nil +} + +// Sign creates new integrity header and adds it to the end of the list of +// extended headers. +func (m *Object) Sign(key *ecdsa.PrivateKey) error { + headerChecksum, err := m.headersChecksum(false) + if err != nil { + return err + } + headerChecksumSignature, err := crypto.Sign(key, headerChecksum) + if err != nil { + return err + } + m.AddHeader(&Header{Value: &Header_Integrity{ + Integrity: &IntegrityHeader{ + HeadersChecksum: headerChecksum, + ChecksumSignature: headerChecksumSignature, + }, + }}) + return nil +} diff --git a/object/verification_test.go b/object/verification_test.go new file mode 100644 index 00000000..f91e0518 --- /dev/null +++ b/object/verification_test.go @@ -0,0 +1,105 @@ +package object + +import ( + "testing" + + "github.com/google/uuid" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/nspcc-dev/neofs-proto/container" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/nspcc-dev/neofs-proto/session" + "github.com/stretchr/testify/require" +) + +func TestObject_Verify(t *testing.T) { + key := test.DecodeKey(0) + sessionkey := test.DecodeKey(1) + + payload := make([]byte, 1024*1024) + + cnr, err := container.NewTestContainer() + require.NoError(t, err) + + cid, err := cnr.ID() + require.NoError(t, err) + + id, err := uuid.NewRandom() + uid := refs.UUID(id) + require.NoError(t, err) + + obj := &Object{ + SystemHeader: SystemHeader{ + ID: uid, + CID: cid, + OwnerID: refs.OwnerID([refs.OwnerIDSize]byte{}), + }, + Headers: []Header{ + { + Value: &Header_UserHeader{ + UserHeader: &UserHeader{ + Key: "Profession", + Value: "Developer", + }, + }, + }, + { + Value: &Header_UserHeader{ + UserHeader: &UserHeader{ + Key: "Language", + Value: "GO", + }, + }, + }, + }, + } + obj.SetPayload(payload) + obj.SetHeader(&Header{Value: &Header_PayloadChecksum{[]byte("incorrect checksum")}}) + + t.Run("error no integrity header", func(t *testing.T) { + err = obj.Verify() + require.EqualError(t, err, ErrHeaderNotFound.Error()) + }) + + badHeaderChecksum := []byte("incorrect checksum") + signature, err := crypto.Sign(sessionkey, badHeaderChecksum) + require.NoError(t, err) + ih := &IntegrityHeader{ + HeadersChecksum: badHeaderChecksum, + ChecksumSignature: signature, + } + obj.SetHeader(&Header{Value: &Header_Integrity{ih}}) + + t.Run("error no validation header", func(t *testing.T) { + err = obj.Verify() + require.EqualError(t, err, ErrHeaderNotFound.Error()) + }) + + dataPK := crypto.MarshalPublicKey(&sessionkey.PublicKey) + signature, err = crypto.Sign(key, dataPK) + vh := &session.VerificationHeader{ + PublicKey: dataPK, + KeySignature: signature, + } + obj.SetVerificationHeader(vh) + + t.Run("error invalid header checksum", func(t *testing.T) { + err = obj.Verify() + require.EqualError(t, err, ErrVerifyHeader.Error()) + }) + + require.NoError(t, obj.Sign(sessionkey)) + + t.Run("error invalid payload checksum", func(t *testing.T) { + err = obj.Verify() + require.EqualError(t, err, ErrVerifyPayload.Error()) + }) + + obj.SetHeader(&Header{Value: &Header_PayloadChecksum{obj.PayloadChecksum()}}) + require.NoError(t, obj.Sign(sessionkey)) + + t.Run("correct", func(t *testing.T) { + err = obj.Verify() + require.NoError(t, err) + }) +} diff --git a/proto.go b/proto.go new file mode 100644 index 00000000..b15f22a7 --- /dev/null +++ b/proto.go @@ -0,0 +1,7 @@ +package neofs_proto // import "github.com/nspcc-dev/neofs-proto" + +import ( + _ "github.com/gogo/protobuf/gogoproto" + _ "github.com/gogo/protobuf/proto" + _ "github.com/golang/protobuf/proto" +) diff --git a/query/types.go b/query/types.go new file mode 100644 index 00000000..15a54f65 --- /dev/null +++ b/query/types.go @@ -0,0 +1,43 @@ +package query + +import ( + "strings" + + "github.com/gogo/protobuf/proto" +) + +var ( + _ proto.Message = (*Query)(nil) + _ proto.Message = (*Filter)(nil) +) + +// String returns string representation of Filter. +func (m Filter) String() string { + b := new(strings.Builder) + b.WriteString("") + return b.String() +} + +// String returns string representation of Query. +func (m Query) String() string { + b := new(strings.Builder) + b.WriteString("~u1$q~^2+$_IHZ2MmK9`bKGA|OTl9VGi&40gV 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 literal 0 HcmV?d00001 diff --git a/query/types.proto b/query/types.proto new file mode 100644 index 00000000..5f460724 --- /dev/null +++ b/query/types.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package query; +option go_package = "github.com/nspcc-dev/neofs-proto/query"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option (gogoproto.stable_marshaler_all) = true; + +message Filter { + option (gogoproto.goproto_stringer) = false; + + enum Type { + Exact = 0; + Regex = 1; + } + Type type = 1 [(gogoproto.customname) = "Type"]; + string Name = 2; + string Value = 3; +} + +message Query { + option (gogoproto.goproto_stringer) = false; + + repeated Filter Filters = 1 [(gogoproto.nullable) = false]; +} diff --git a/refs/address.go b/refs/address.go new file mode 100644 index 00000000..be6b6a32 --- /dev/null +++ b/refs/address.go @@ -0,0 +1,68 @@ +package refs + +import ( + "crypto/sha256" + "strings" + + "github.com/nspcc-dev/neofs-proto/internal" +) + +const ( + joinSeparator = "/" + + // ErrWrongAddress is raised when wrong address is passed to Address.Parse ParseAddress. + ErrWrongAddress = internal.Error("wrong address") + + // ErrEmptyAddress is raised when empty address is passed to Address.Parse ParseAddress. + ErrEmptyAddress = internal.Error("empty address") +) + +// ParseAddress parses address from string representation into new Address. +func ParseAddress(str string) (*Address, error) { + var addr Address + return &addr, addr.Parse(str) +} + +// Parse parses address from string representation into current Address. +func (m *Address) Parse(addr string) error { + if m == nil { + return ErrEmptyAddress + } + + items := strings.Split(addr, joinSeparator) + if len(items) != 2 { + return ErrWrongAddress + } + + if err := m.CID.Parse(items[0]); err != nil { + return err + } else if err := m.ObjectID.Parse(items[1]); err != nil { + return err + } + + return nil +} + +// String returns string representation of Address. +func (m Address) String() string { + return strings.Join([]string{m.CID.String(), m.ObjectID.String()}, joinSeparator) +} + +// IsFull checks that ContainerID and ObjectID is not empty. +func (m Address) IsFull() bool { + return !m.CID.Empty() && !m.ObjectID.Empty() +} + +// Equal checks that current Address is equal to passed Address. +func (m Address) Equal(a2 *Address) bool { + return m.CID.Equal(a2.CID) && m.ObjectID.Equal(a2.ObjectID) +} + +// Hash returns []byte that used as a key for storage bucket. +func (m Address) Hash() ([]byte, error) { + if !m.IsFull() { + return nil, ErrEmptyAddress + } + h := sha256.Sum256(append(m.ObjectID.Bytes(), m.CID.Bytes()...)) + return h[:], nil +} diff --git a/refs/cid.go b/refs/cid.go new file mode 100644 index 00000000..4f0cf39d --- /dev/null +++ b/refs/cid.go @@ -0,0 +1,96 @@ +package refs + +import ( + "bytes" + "crypto/sha256" + + "github.com/mr-tron/base58" + "github.com/pkg/errors" +) + +// CIDForBytes creates CID for passed bytes. +func CIDForBytes(data []byte) CID { return sha256.Sum256(data) } + +// CIDFromBytes parses CID from passed bytes. +func CIDFromBytes(data []byte) (cid CID, err error) { + if ln := len(data); ln != CIDSize { + return CID{}, errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", CIDSize, ln) + } + + copy(cid[:], data) + return +} + +// CIDFromString parses CID from string representation of CID. +func CIDFromString(c string) (CID, error) { + var cid CID + decoded, err := base58.Decode(c) + if err != nil { + return cid, err + } + return CIDFromBytes(decoded) +} + +// Size returns size of CID (CIDSize). +func (c CID) Size() int { return CIDSize } + +// Parse tries to parse CID from string representation. +func (c *CID) Parse(cid string) error { + var err error + if *c, err = CIDFromString(cid); err != nil { + return err + } + return nil +} + +// Empty checks that current CID is empty. +func (c CID) Empty() bool { return bytes.Equal(c.Bytes(), emptyCID) } + +// Equal checks that current CID is equal to passed CID. +func (c CID) Equal(cid CID) bool { return bytes.Equal(c.Bytes(), cid.Bytes()) } + +// Marshal returns CID bytes representation. +func (c CID) Marshal() ([]byte, error) { return c.Bytes(), nil } + +// MarshalBinary returns CID bytes representation. +func (c CID) MarshalBinary() ([]byte, error) { return c.Bytes(), nil } + +// MarshalTo marshal CID to bytes representation into passed bytes. +func (c *CID) MarshalTo(data []byte) (int, error) { return copy(data, c.Bytes()), nil } + +// ProtoMessage method to satisfy proto.Message interface. +func (c CID) ProtoMessage() {} + +// String returns string representation of CID. +func (c CID) String() string { return base58.Encode(c[:]) } + +// Reset resets current CID to zero value. +func (c *CID) Reset() { *c = CID{} } + +// Bytes returns CID bytes representation. +func (c CID) Bytes() []byte { + buf := make([]byte, CIDSize) + copy(buf, c[:]) + return buf +} + +// UnmarshalBinary tries to parse bytes representation of CID. +func (c *CID) UnmarshalBinary(data []byte) error { return c.Unmarshal(data) } + +// Unmarshal tries to parse bytes representation of CID. +func (c *CID) Unmarshal(data []byte) error { + if ln := len(data); ln != CIDSize { + return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", CIDSize, ln) + } + + copy((*c)[:], data) + return nil +} + +// Verify validates that current CID is generated for passed bytes data. +func (c CID) Verify(data []byte) error { + if id := CIDForBytes(data); !bytes.Equal(c[:], id[:]) { + return errors.New("wrong hash for data") + } + return nil +} diff --git a/refs/owner.go b/refs/owner.go new file mode 100644 index 00000000..ff88446f --- /dev/null +++ b/refs/owner.go @@ -0,0 +1,65 @@ +package refs + +import ( + "bytes" + "crypto/ecdsa" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neofs-proto/chain" + "github.com/pkg/errors" +) + +// NewOwnerID returns generated OwnerID from passed public keys. +func NewOwnerID(keys ...*ecdsa.PublicKey) (owner OwnerID, err error) { + if len(keys) == 0 { + return + } + var d []byte + d, err = base58.Decode(chain.KeysToAddress(keys...)) + if err != nil { + return + } + copy(owner[:], d) + return owner, nil +} + +// Size returns OwnerID size in bytes (OwnerIDSize). +func (OwnerID) Size() int { return OwnerIDSize } + +// Empty checks that current OwnerID is empty value. +func (o OwnerID) Empty() bool { return bytes.Equal(o.Bytes(), emptyOwner) } + +// Equal checks that current OwnerID is equal to passed OwnerID. +func (o OwnerID) Equal(id OwnerID) bool { return bytes.Equal(o.Bytes(), id.Bytes()) } + +// Reset sets current OwnerID to empty value. +func (o *OwnerID) Reset() { *o = OwnerID{} } + +// ProtoMessage method to satisfy proto.Message interface. +func (OwnerID) ProtoMessage() {} + +// Marshal returns OwnerID bytes representation. +func (o OwnerID) Marshal() ([]byte, error) { return o.Bytes(), nil } + +// MarshalTo copies OwnerID bytes representation into passed slice of bytes. +func (o OwnerID) MarshalTo(data []byte) (int, error) { return copy(data, o.Bytes()), nil } + +// String returns string representation of OwnerID. +func (o OwnerID) String() string { return base58.Encode(o[:]) } + +// Bytes returns OwnerID bytes representation. +func (o OwnerID) Bytes() []byte { + buf := make([]byte, OwnerIDSize) + copy(buf, o[:]) + return buf +} + +// Unmarshal tries to parse OwnerID bytes representation into current OwnerID. +func (o *OwnerID) Unmarshal(data []byte) error { + if ln := len(data); ln != OwnerIDSize { + return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", OwnerIDSize, ln) + } + + copy((*o)[:], data) + return nil +} diff --git a/refs/sgid.go b/refs/sgid.go new file mode 100644 index 00000000..d6fcf2d7 --- /dev/null +++ b/refs/sgid.go @@ -0,0 +1,14 @@ +package refs + +import ( + "github.com/pkg/errors" +) + +// SGIDFromBytes parse bytes representation of SGID into new SGID value. +func SGIDFromBytes(data []byte) (sgid SGID, err error) { + if ln := len(data); ln != SGIDSize { + return SGID{}, errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", SGIDSize, ln) + } + copy(sgid[:], data) + return +} diff --git a/refs/types.go b/refs/types.go new file mode 100644 index 00000000..785155c6 --- /dev/null +++ b/refs/types.go @@ -0,0 +1,106 @@ +// This package contains basic structures implemented in Go, such as +// +// CID - container id +// OwnerID - owner id +// ObjectID - object id +// SGID - storage group id +// Address - contains object id and container id +// UUID - a 128 bit (16 byte) Universal Unique Identifier as defined in RFC 4122 + +package refs + +import ( + "crypto/sha256" + + "github.com/google/uuid" + "github.com/nspcc-dev/neofs-proto/chain" + "github.com/nspcc-dev/neofs-proto/internal" +) + +type ( + // CID is implementation of ContainerID. + CID [CIDSize]byte + + // UUID wrapper over github.com/google/uuid.UUID. + UUID uuid.UUID + + // SGID is type alias of UUID. + SGID = UUID + + // ObjectID is type alias of UUID. + ObjectID = UUID + + // MessageID is type alias of UUID. + MessageID = UUID + + // OwnerID is wrapper over neofs-proto/chain.WalletAddress. + OwnerID chain.WalletAddress +) + +const ( + // UUIDSize contains size of UUID. + UUIDSize = 16 + + // SGIDSize contains size of SGID. + SGIDSize = UUIDSize + + // CIDSize contains size of CID. + CIDSize = sha256.Size + + // OwnerIDSize contains size of OwnerID. + OwnerIDSize = chain.AddressLength + + // ErrWrongDataSize is raised when passed bytes into Unmarshal have wrong size. + ErrWrongDataSize = internal.Error("wrong data size") + + // ErrEmptyOwner is raised when empty OwnerID is passed into container.New. + ErrEmptyOwner = internal.Error("owner cant be empty") + + // ErrEmptyCapacity is raised when empty Capacity is passed container.New. + ErrEmptyCapacity = internal.Error("capacity cant be empty") + + // ErrEmptyContainer is raised when it CID method is called for an empty container. + ErrEmptyContainer = internal.Error("cannot return ID for empty container") +) + +var ( + emptyCID = (CID{}).Bytes() + emptyUUID = (UUID{}).Bytes() + emptyOwner = (OwnerID{}).Bytes() + + _ internal.Custom = (*CID)(nil) + _ internal.Custom = (*SGID)(nil) + _ internal.Custom = (*UUID)(nil) + _ internal.Custom = (*OwnerID)(nil) + _ internal.Custom = (*ObjectID)(nil) + _ internal.Custom = (*MessageID)(nil) + + // NewSGID method alias. + NewSGID = NewUUID + + // NewObjectID method alias. + NewObjectID = NewUUID + + // NewMessageID method alias. + NewMessageID = NewUUID +) + +// NewUUID returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewUUID() (UUID, error) { + id, err := uuid.NewRandom() + if err != nil { + return UUID{}, err + } + return UUID(id), nil +} diff --git a/refs/types.pb.go b/refs/types.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..33f7578a684f5afeae4c69681a6ab90e0173119d GIT binary patch literal 9127 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 p.LastEpoch || p.OwnerID.Empty() { + return nil + } + + t := &PToken{ + mtx: new(sync.Mutex), + Token: Token{ + ID: tid, + Header: VerificationHeader{PublicKey: crypto.MarshalPublicKey(&key.PublicKey)}, + FirstEpoch: p.FirstEpoch, + LastEpoch: p.LastEpoch, + ObjectID: p.ObjectID, + OwnerID: p.OwnerID, + }, + PrivateKey: key, + } + + s.Lock() + s.tokens[t.ID] = t + s.Unlock() + + return t +} + +// Fetch tries to fetch a token with specified id. +func (s *simpleStore) Fetch(id TokenID) *PToken { + s.RLock() + defer s.RUnlock() + + return s.tokens[id] +} + +// Remove removes token with id from store. +func (s *simpleStore) Remove(id TokenID) { + s.Lock() + delete(s.tokens, id) + s.Unlock() +} diff --git a/session/store_test.go b/session/store_test.go new file mode 100644 index 00000000..1a9e9771 --- /dev/null +++ b/session/store_test.go @@ -0,0 +1,84 @@ +package session + +import ( + "crypto/ecdsa" + "crypto/rand" + "testing" + + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/stretchr/testify/require" +) + +type testClient struct { + *ecdsa.PrivateKey + OwnerID OwnerID +} + +func (c *testClient) Sign(data []byte) ([]byte, error) { + return crypto.Sign(c.PrivateKey, data) +} + +func newTestClient(t *testing.T) *testClient { + key, err := ecdsa.GenerateKey(defaultCurve(), rand.Reader) + require.NoError(t, err) + + owner, err := refs.NewOwnerID(&key.PublicKey) + require.NoError(t, err) + + return &testClient{PrivateKey: key, OwnerID: owner} +} + +func signToken(t *testing.T, token *PToken, c *testClient) { + require.NotNil(t, token) + + signH, err := c.Sign(token.Header.PublicKey) + require.NoError(t, err) + require.NotNil(t, signH) + + // data is not yet signed + require.False(t, token.Verify(&c.PublicKey)) + + signT, err := c.Sign(token.verificationData()) + require.NoError(t, err) + require.NotNil(t, signT) + + token.AddSignatures(signH, signT) + require.True(t, token.Verify(&c.PublicKey)) +} + +func TestTokenStore(t *testing.T) { + s := NewSimpleStore() + + oid, err := refs.NewObjectID() + require.NoError(t, err) + + c := newTestClient(t) + require.NotNil(t, c) + + // create new token + token := s.New(TokenParams{ObjectID: []ObjectID{oid}, OwnerID: c.OwnerID}) + signToken(t, token, c) + + // check that it can be fetched + t1 := s.Fetch(token.ID) + require.NotNil(t, t1) + require.Equal(t, token, t1) + + // create and sign another token by the same client + t1 = s.New(TokenParams{ObjectID: []ObjectID{oid}, OwnerID: c.OwnerID}) + signToken(t, t1, c) + + data := []byte{1, 2, 3} + sign, err := t1.SignData(data) + require.NoError(t, err) + require.Error(t, token.Header.VerifyData(data, sign)) + + sign, err = token.SignData(data) + require.NoError(t, err) + require.NoError(t, token.Header.VerifyData(data, sign)) + + s.Remove(token.ID) + require.Nil(t, s.Fetch(token.ID)) + require.NotNil(t, s.Fetch(t1.ID)) +} diff --git a/session/types.go b/session/types.go new file mode 100644 index 00000000..ceb2944a --- /dev/null +++ b/session/types.go @@ -0,0 +1,159 @@ +package session + +import ( + "crypto/ecdsa" + "encoding/binary" + "sync" + + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-proto/internal" + "github.com/nspcc-dev/neofs-proto/refs" + "github.com/pkg/errors" +) + +type ( + // ObjectID type alias. + ObjectID = refs.ObjectID + // OwnerID type alias. + OwnerID = refs.OwnerID + // TokenID type alias. + TokenID = refs.UUID + + // PToken is a wrapper around Token that allows to sign data + // and to do thread-safe manipulations. + PToken struct { + Token + + mtx *sync.Mutex + PrivateKey *ecdsa.PrivateKey + } +) + +const ( + // ErrWrongFirstEpoch is raised when passed Token contains wrong first epoch. + // First epoch is an epoch since token is valid + ErrWrongFirstEpoch = internal.Error("wrong first epoch") + + // ErrWrongLastEpoch is raised when passed Token contains wrong last epoch. + // Last epoch is an epoch until token is valid + ErrWrongLastEpoch = internal.Error("wrong last epoch") + + // ErrWrongOwner is raised when passed Token contains wrong OwnerID. + ErrWrongOwner = internal.Error("wrong owner") + + // ErrEmptyPublicKey is raised when passed Token contains wrong public key. + ErrEmptyPublicKey = internal.Error("empty public key") + + // ErrWrongObjectsCount is raised when passed Token contains wrong objects count. + ErrWrongObjectsCount = internal.Error("wrong objects count") + + // ErrWrongObjects is raised when passed Token contains wrong object ids. + ErrWrongObjects = internal.Error("wrong objects") + + // ErrInvalidSignature is raised when wrong signature is passed to VerificationHeader.VerifyData(). + ErrInvalidSignature = internal.Error("invalid signature") +) + +// verificationData returns byte array to sign. +// Note: protobuf serialization is inconsistent as +// wire order is unspecified. +func (m *Token) verificationData() (data []byte) { + var size int + if l := len(m.ObjectID); l > 0 { + size = m.ObjectID[0].Size() + data = make([]byte, 16+l*size) + } else { + data = make([]byte, 16) + } + binary.BigEndian.PutUint64(data, m.FirstEpoch) + binary.BigEndian.PutUint64(data[8:], m.LastEpoch) + for i := range m.ObjectID { + copy(data[16+i*size:], m.ObjectID[i].Bytes()) + } + return +} + +// IsSame checks if the passed token is valid and equal to current token +func (m *Token) IsSame(t *Token) error { + switch { + case m.FirstEpoch != t.FirstEpoch: + return ErrWrongFirstEpoch + case m.LastEpoch != t.LastEpoch: + return ErrWrongLastEpoch + case !m.OwnerID.Equal(t.OwnerID): + return ErrWrongOwner + case m.Header.PublicKey == nil: + return ErrEmptyPublicKey + case len(m.ObjectID) != len(t.ObjectID): + return ErrWrongObjectsCount + default: + for i := range m.ObjectID { + if !m.ObjectID[i].Equal(t.ObjectID[i]) { + return errors.Wrapf(ErrWrongObjects, "expect %s, actual: %s", m.ObjectID[i], t.ObjectID[i]) + } + } + } + return nil +} + +// Sign tries to sign current Token data and stores signature inside it. +func (m *Token) Sign(key *ecdsa.PrivateKey) error { + if err := m.Header.Sign(key); err != nil { + return err + } + + s, err := crypto.Sign(key, m.verificationData()) + if err != nil { + return err + } + + m.Signature = s + return nil +} + +// Verify checks if token is correct and signed. +func (m *Token) Verify(keys ...*ecdsa.PublicKey) bool { + if m.FirstEpoch > m.LastEpoch { + return false + } + for i := range keys { + if m.Header.Verify(keys[i]) && crypto.Verify(keys[i], m.verificationData(), m.Signature) == nil { + return true + } + } + return false +} + +// Sign adds token signatures. +func (t *PToken) AddSignatures(signH, signT []byte) { + t.mtx.Lock() + + t.Header.KeySignature = signH + t.Signature = signT + + t.mtx.Unlock() +} + +// SignData signs data with session private key. +func (t *PToken) SignData(data []byte) ([]byte, error) { + return crypto.Sign(t.PrivateKey, data) +} + +// VerifyData checks if signature of data by token t +// is equal to sign. +func (m *VerificationHeader) VerifyData(data, sign []byte) error { + if crypto.Verify(crypto.UnmarshalPublicKey(m.PublicKey), data, sign) != nil { + return ErrInvalidSignature + } + return nil +} + +// Verify checks if verification header was issued by id. +func (m *VerificationHeader) Verify(keys ...*ecdsa.PublicKey) bool { + for i := range keys { + if crypto.Verify(keys[i], m.PublicKey, m.KeySignature) == nil { + return true + } + } + return false +} diff --git a/session/types.pb.go b/session/types.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..ce0e0a4e5909f294f4b32aff63db2e22b6e5833c GIT binary patch literal 21017 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