diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 3d02d05d..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Release -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - - "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" - -permissions: - contents: write - packages: write - -jobs: - release-binaries-github: - name: Release Binaries to Github - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Make release - run: | - make release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Clean release folder - run: | - sudo rm -rf dist - \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..1eacb7ad --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,143 @@ +name: Test Workflow + +on: + pull_request: + branches: + - development + - main + +jobs: + test-linux: + name: "Test - Linux Environment" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Golang v1.21.9 + uses: actions/setup-go@v5 + with: + go-version: '1.21.9' + + - name: Setup Python v3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install test dependency + run: | + pip3 install requests + + - name: Run tests + run: | + cd tests && ls && python3 -u run.py + + - name: Packing Quorum and Non-Quorum node logs + if: always() + run: | + cd tests && python3 -u pack_node_logs.py + + - name: Set Timestamp for Node Logs + if: always() + run: echo "TIMESTAMP=$(date -u +'%Y-%m-%dT%H-%M-%SZ' | sed 's/:/-/g')" >> $GITHUB_ENV + + - name: Uploading Quorum and Non-Quorum node logs as Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: node-logs-linux-${{ env.TIMESTAMP }} + path: tests/node_logs + + test-macos: + name: "Test - MacOS Environment" + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Golang v1.21.9 + uses: actions/setup-go@v5 + with: + go-version: '1.21.9' + + - name: Setup Python v3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install test dependency + run: | + pip3 install requests + + - name: MacOS install tmux + run: brew install tmux + + - name: Run tests + run: | + cd tests && ls && python3 -u run.py + + - name: Packing Quorum and Non-Quorum node logs + if: always() + run: | + cd tests && python3 -u pack_node_logs.py + + - name: Set Timestamp for Node Logs + if: always() + run: echo "TIMESTAMP=$(date -u +'%Y-%m-%dT%H-%M-%SZ' | sed 's/:/-/g')" >> $GITHUB_ENV + + - name: Uploading Quorum and Non-Quorum node logs as Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: node-logs-macos-${{ env.TIMESTAMP }} + path: tests/node_logs + + test-windows: + name: "Test - Windows Environment" + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Golang v1.21.9 + uses: actions/setup-go@v5 + with: + go-version: '1.21.9' + + - name: Setup Python v3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install test dependency + run: | + pip3 install requests + + - name: Run tests + run: | + cd tests && ls && python3 -u run.py + + - name: Packing Quorum and Non-Quorum node logs + if: always() + run: | + cd tests && python3 -u pack_node_logs.py + + - name: Set Timestamp for Node Logs + if: always() + shell: pwsh + run: | + $timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ssZ" + echo "TIMESTAMP=$timestamp" >> $env:GITHUB_ENV + + - name: Uploading Quorum and Non-Quorum node logs as Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: node-logs-windows-${{ env.TIMESTAMP }} + path: tests/node_logs diff --git a/.gitignore b/.gitignore index 58b5bf90..9f8b4b91 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ api_config.json *.json *.pdf .vscode +__pycache__ # Dependency directories (remove the comment below to include it) # vendor/ @@ -27,3 +28,6 @@ linux/ mac/ windows/ dist/ + +# used for testing purpose +!node_registry.json \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 0df6513e..00000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,104 +0,0 @@ -project_name: rubixgoplatform - -env: - - CGO_ENABLED=1 - -before: - hooks: - - go mod tidy -compat=1.20 - -builds: - - - id: ubuntu-amd64 - main: ./ - binary: rubixgoplatform - env: - - CC=x86_64-linux-gnu-gcc - goos: - - linux - goarch: - - amd64 - - - id: ubuntu-arm64 - main: ./ - binary: rubixgoplatform - env: - - CC=aarch64-linux-gnu-gcc - goos: - - linux - goarch: - - arm64 - - - id: darwin-amd64 - main: ./ - binary: rubixgoplatform - env: - - CC=o64-clang - - CGO_LDFLAGS=-L/lib - goos: - - darwin - goarch: - - amd64 - - - id: darwin-arm64 - main: ./ - binary: rubixgoplatform - env: - - CC=oa64-clang - - CGO_LDFLAGS=-L/lib - goos: - - darwin - goarch: - - arm64 - - - id: windows-amd64 - main: ./ - binary: rubixgoplatform - env: - - CC=x86_64-w64-mingw32-gcc - goos: - - windows - goarch: - - amd64 - -archives: - - id: linux-darwin-archive - builds: - - ubuntu-amd64 - - ubuntu-arm64 - - darwin-amd64 - - darwin-arm64 - format: tar.gz - wrap_in_directory: false - name_template: "{{ .Binary }}-v{{ .Version }}-{{ .Os }}-{{ .Arch }}" - - - id: windows-archive - builds: - - windows-amd64 - format: zip - wrap_in_directory: false - name_template: "{{ .Binary }}-v{{ .Version }}-{{ .Os }}-{{ .Arch }}" - -checksum: - name_template: 'checksums.txt' - algorithm: sha256 - -snapshot: - name_template: "{{ incpatch .Version }}-next" - -changelog: - sort: asc - filters: - exclude: - - '^docs:' - - '^test:' - -release: - github: - owner: rubixchain - name: rubixgoplatform - - draft: false - mode: append - header: | - # Release Notes \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702d..00000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/Makefile b/Makefile index 1972f840..87d463d6 100644 --- a/Makefile +++ b/Makefile @@ -19,39 +19,4 @@ compile-mac: clean: rm -f linux/rubixgoplatform windows/rubixgoplatform.exe mac/rubixgoplatform -all: compile-linux compile-windows compile-mac - -############################ Release ################################## - -GORELEASER_VERSION := 1.20 -GORELEASER_IMAGE := ghcr.io/goreleaser/goreleaser-cross:v$(GORELEASER_VERSION) - -# Publish binaries to Gitbub. It is used in `release` Github Action Workflow -ifdef GITHUB_TOKEN -release: - docker run \ - --rm \ - -e GITHUB_TOKEN=$(GITHUB_TOKEN) \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v `pwd`:/go/src/rubixgoplatform \ - -w /go/src/rubixgoplatform \ - $(GORELEASER_IMAGE) \ - release \ - --clean -else -release: - @echo "Error: GITHUB_TOKEN is not defined. Please define it before running 'make release'." -endif - -# Generate binaries in local -release-dry-run: - docker run \ - --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v `pwd`:/go/src/rubixgoplatform \ - -w /go/src/rubixgoplatform \ - $(GORELEASER_IMAGE) \ - release \ - --clean \ - --skip-publish \ - --skip-validate +all: compile-linux compile-windows compile-mac \ No newline at end of file diff --git a/README.md b/README.md index 7817a9d8..f457f546 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ This following options are used for this command -port string Server/Host port (default "20000") -didType int - DID type (0-Basic Mode, 1-Standard Mode, 2-Wallet Mode) (default 0) + DID type (0-Basic Mode, 1-Standard Mode, 2-Wallet Mode, 3-Child Mode, 4-Light Mode) (default 0) -didSecret string DID secret (default "My DID Secret") -privPWD string @@ -121,8 +121,14 @@ This following options are used for this command DID private key file name (default "pvtKey.pem") -pubKeyFile string DID public key file name (default "pubKey.pem") + -mnemonicKeyFile string + Mnemonic key file (default "mnemonic.txt") + -ChildPath int + BIP Child Path (default 0) -fp forcepassword This flag prompts to enter the password in terminal + + _Note: Use Light mode for PKI based authentication with backward compatiblity to PKI+NLSS based sign, and Basic mode for PKI+NLSS based authentication._ ``` Get All DID Command : To get all DID use this command. @@ -311,4 +317,35 @@ This following options are used for this command Server/Host port (default "20000") -fp Force password to be entered on the terminal +``` +To Add explorer url +: To add explorer url where to send the transaction data. +``` +./rubixgoplatform addexplorer + +This following options are used for this command + -links string + URLs, mutiple URLs will be seprated by comma + -port string + Server/Host port (default "20000") +``` +To remove explorer url +: To remove explorer url where not to send the transaction data. +``` +./rubixgoplatform removeexplorer + +This following options are used for this command + -links string + URLs, mutiple URLs will be seprated by comma + -port string + Server/Host port (default "20000") +``` +To get all explorer urls +: To get explorer urls where the transaction data is being sent. +``` +./rubixgoplatform getallexplorer + +This following options are used for this command + -port string + Server/Host port (default "20000") ``` \ No newline at end of file diff --git a/block/block.go b/block/block.go index cf3dcf4c..19227f7e 100644 --- a/block/block.go +++ b/block/block.go @@ -181,6 +181,7 @@ func (b *Block) blkDecode() error { return fmt.Errorf("invalid block, missing block content") } hb := util.CalculateHash(bc.([]byte), "SHA3-256") + var tcb map[string]interface{} err = cbor.Unmarshal(bc.([]byte), &tcb) if err != nil { @@ -194,7 +195,9 @@ func (b *Block) blkDecode() error { } tcb[TCSignatureKey] = ksb } + tcb[TCBlockHashKey] = util.HexToStr(hb) + b.bm = tcb return nil } @@ -214,7 +217,9 @@ func (b *Block) blkEncode() error { return err } hb := util.CalculateHash(bc, "SHA3-256") + b.bm[TCBlockHashKey] = util.HexToStr(hb) + m := make(map[string]interface{}) m[TCBlockContentKey] = bc if sok { @@ -328,6 +333,7 @@ func (b *Block) GetSigner() ([]string, error) { func (b *Block) GetHashSig(did string) (string, string, error) { h, ok := b.bm[TCBlockHashKey] + if !ok { return "", "", fmt.Errorf("invalid token chain block, missing block hash") } diff --git a/client/did.go b/client/did.go index 1522c7ab..ea51b372 100644 --- a/client/did.go +++ b/client/did.go @@ -48,10 +48,12 @@ func (c *Client) GetAllDIDs() (*model.GetAccountInfo, error) { } func (c *Client) CreateDID(cfg *did.DIDCreate) (string, bool) { - if cfg.Type < did.BasicDIDMode && cfg.Type > did.WalletDIDMode { + if cfg.Type < did.LiteDIDMode && cfg.Type > did.WalletDIDMode { return "Invalid DID mode", false } switch cfg.Type { + case did.LiteDIDMode: + cfg.PubKeyFile = "" case did.BasicDIDMode: if cfg.ImgFile == "" { c.log.Error("Image file requried") @@ -123,15 +125,19 @@ func (c *Client) CreateDID(cfg *did.DIDCreate) (string, bool) { fields := make(map[string]string) fields[setup.DIDConfigField] = string(jd) files := make(map[string]string) - if cfg.ImgFile != "" { - files["image"] = cfg.ImgFile - } - if cfg.DIDImgFileName != "" { - files["did_image"] = cfg.DIDImgFileName - } - if cfg.PubImgFile != "" { - files["pub_image"] = cfg.PubImgFile + + if cfg.Type != did.LiteDIDMode { + if cfg.ImgFile != "" { + files["image"] = cfg.ImgFile + } + if cfg.DIDImgFileName != "" { + files["did_image"] = cfg.DIDImgFileName + } + if cfg.PubImgFile != "" { + files["pub_image"] = cfg.PubImgFile + } } + if cfg.PubKeyFile != "" { files["pub_key"] = cfg.PubKeyFile } @@ -145,29 +151,39 @@ func (c *Client) CreateDID(cfg *did.DIDCreate) (string, bool) { c.log.Error("Failed to create DID", "message", dr.Message) return "Failed to create DID, " + dr.Message, false } - c.log.Info("DID Created successfully") + c.log.Info(fmt.Sprintf("DID %v Created successfully", dr.Result.DID)) return dr.Result.DID, true } func (c *Client) SetupDID(dc *did.DIDCreate) (string, bool) { - if dc.Type < did.BasicDIDMode && dc.Type > did.WalletDIDMode { + if dc.Type < did.BasicDIDMode && dc.Type > did.LiteDIDMode { return "Invalid DID mode", false } - if !strings.Contains(dc.PubImgFile, did.PubShareFileName) || - !strings.Contains(dc.DIDImgFileName, did.DIDImgFileName) || - !strings.Contains(dc.PubKeyFile, did.PubKeyFileName) || - !strings.Contains(dc.QuorumPubKeyFile, did.QuorumPubKeyFileName) || - !strings.Contains(dc.QuorumPrivKeyFile, did.QuorumPvtKeyFileName) { - return "Required files are missing", false - } + switch dc.Type { + case did.LiteDIDMode: + if !strings.Contains(dc.PubKeyFile, did.PubKeyFileName) || + !strings.Contains(dc.PrivKeyFile, did.PvtKeyFileName) || + !strings.Contains(dc.MnemonicFile, did.MnemonicFileName) { + return "Required files are missing", false + } case did.BasicDIDMode: if !strings.Contains(dc.PrivImgFile, did.PvtShareFileName) || + !strings.Contains(dc.PubImgFile, did.PubShareFileName) || + !strings.Contains(dc.DIDImgFileName, did.DIDImgFileName) || + !strings.Contains(dc.PubKeyFile, did.PubKeyFileName) || + !strings.Contains(dc.QuorumPubKeyFile, did.QuorumPubKeyFileName) || + !strings.Contains(dc.QuorumPrivKeyFile, did.QuorumPvtKeyFileName) || !strings.Contains(dc.PrivKeyFile, did.PvtKeyFileName) { return "Required files are missing", false } case did.StandardDIDMode: - if !strings.Contains(dc.PrivImgFile, did.PvtShareFileName) { + if !strings.Contains(dc.PubImgFile, did.PubShareFileName) || + !strings.Contains(dc.DIDImgFileName, did.DIDImgFileName) || + !strings.Contains(dc.PrivImgFile, did.PvtShareFileName) || + !strings.Contains(dc.PubKeyFile, did.PubKeyFileName) || + !strings.Contains(dc.QuorumPubKeyFile, did.QuorumPubKeyFileName) || + !strings.Contains(dc.QuorumPrivKeyFile, did.QuorumPvtKeyFileName) { return "Required files are missing", false } } @@ -179,27 +195,32 @@ func (c *Client) SetupDID(dc *did.DIDCreate) (string, bool) { fields := make(map[string]string) fields[setup.DIDConfigField] = string(jd) files := make(map[string]string) - if dc.PubImgFile != "" { - files["pub_image"] = dc.PubImgFile - } - if dc.DIDImgFileName != "" { - files["did_image"] = dc.DIDImgFileName - } - if dc.PrivImgFile != "" { - files["priv_image"] = dc.PrivImgFile + + if dc.Type != did.LiteDIDMode { + if dc.PubImgFile != "" { + files["pub_image"] = dc.PubImgFile + } + if dc.DIDImgFileName != "" { + files["did_image"] = dc.DIDImgFileName + } + if dc.PrivImgFile != "" { + files["priv_image"] = dc.PrivImgFile + } + if dc.QuorumPubKeyFile != "" { + files["quorum_pub_key"] = dc.QuorumPubKeyFile + } + if dc.QuorumPrivKeyFile != "" { + files["quorum_priv_key"] = dc.QuorumPrivKeyFile + } } + if dc.PubKeyFile != "" { files["pub_key"] = dc.PubKeyFile } if dc.PrivKeyFile != "" { files["priv_key"] = dc.PrivKeyFile } - if dc.QuorumPubKeyFile != "" { - files["quorum_pub_key"] = dc.QuorumPubKeyFile - } - if dc.QuorumPrivKeyFile != "" { - files["quorum_priv_key"] = dc.QuorumPrivKeyFile - } + var br model.BasicResponse err = c.sendMutiFormRequest("POST", setup.APISetupDID, nil, fields, files, &br) if err != nil { diff --git a/client/explorer.go b/client/explorer.go new file mode 100644 index 00000000..6bcd2b17 --- /dev/null +++ b/client/explorer.go @@ -0,0 +1,39 @@ +package client + +import ( + "github.com/rubixchain/rubixgoplatform/core/model" + "github.com/rubixchain/rubixgoplatform/setup" +) + +func (c *Client) AddExplorer(links []string) (string, bool) { + m := model.ExplorerLinks{ + Links: links, + } + var rm model.BasicResponse + err := c.sendJSONRequest("POST", setup.APIAddExplorer, nil, &m, &rm) + if err != nil { + return err.Error(), false + } + return rm.Message, rm.Status +} + +func (c *Client) RemoveExplorer(links []string) (string, bool) { + m := model.ExplorerLinks{ + Links: links, + } + var rm model.BasicResponse + err := c.sendJSONRequest("POST", setup.APIRemoveExplorer, nil, &m, &rm) + if err != nil { + return err.Error(), false + } + return rm.Message, rm.Status +} + +func (c *Client) GetAllExplorer() ([]string, string, bool) { + var rm model.ExplorerResponse + err := c.sendJSONRequest("GET", setup.APIGetAllExplorer, nil, nil, &rm) + if err != nil { + return nil, err.Error(), false + } + return rm.Result.Links, rm.Message, rm.Status +} diff --git a/client/node.go b/client/node.go index 02604409..0ef5334d 100644 --- a/client/node.go +++ b/client/node.go @@ -13,3 +13,12 @@ func (c *Client) Shutdown() (string, bool) { } return rm.Message, rm.Status } + +func (c *Client) PeerID() (string, bool) { + var rm model.BasicResponse + err := c.sendJSONRequest("GET", setup.APIPeerID, nil, nil, &rm) + if err != nil { + return "Failed to fetch peer ID of node, error: " + err.Error(), false + } + return rm.Message, rm.Status +} diff --git a/command/command.go b/command/command.go index 650d8d51..e2d9e850 100644 --- a/command/command.go +++ b/command/command.go @@ -33,7 +33,7 @@ const ( ) const ( - version string = "0.0.16" + version string = "0.0.17" ) const ( VersionCmd string = "-v" @@ -76,8 +76,12 @@ const ( DumpSmartContractTokenChainCmd string = "dumpsmartcontracttokenchain" GetTokenBlock string = "gettokenblock" GetSmartContractData string = "getsmartcontractdata" + GetPeerID string = "get-peer-id" ReleaseAllLockedTokensCmd string = "releaseAllLockedTokens" CheckQuorumStatusCmd string = "checkQuorumStatus" + AddExplorerCmd string = "addexplorer" + RemoveExplorerCmd string = "removeexplorer" + GetAllExplorerCmd string = "getallexplorer" ) var commands = []string{VersionCmd, @@ -122,6 +126,7 @@ var commands = []string{VersionCmd, DumpSmartContractTokenChainCmd, GetTokenBlock, GetSmartContractData, + GetPeerID, CheckQuorumStatusCmd, } var commandsHelp = []string{"To get tool version", @@ -165,7 +170,8 @@ var commandsHelp = []string{"To get tool version", "This command will subscribe to a smart contract token", "This command will dump the smartcontract token chain", "This command gets token block", - "This command gets the smartcontract data from latest block"} + "This command gets the smartcontract data from latest block", + "This command will fetch the peer ID of the node"} type Command struct { cfg config.Config @@ -240,6 +246,9 @@ type Command struct { executorAddr string latest bool quorumAddr string + links []string + mnemonicFile string + ChildPath int } func showVersion() { @@ -364,6 +373,7 @@ func Run(args []string) { cmd := &Command{} var peers string var timeout int + var links string flag.StringVar(&cmd.runDir, "p", "./", "Working directory path") flag.StringVar(&cmd.logFile, "logFile", "", "Log file name") @@ -380,6 +390,7 @@ func Run(args []string) { flag.StringVar(&peers, "peers", "", "Bootstrap peers, mutiple peers will be seprated by comma") flag.BoolVar(&cmd.didRoot, "didRoot", false, "Root DID") flag.IntVar(&cmd.didType, "didType", 0, "DID Creation type") + flag.IntVar(&cmd.ChildPath, "ChildPath", 0, "BIP child Path") flag.StringVar(&cmd.didSecret, "didSecret", "My DID Secret", "DID creation secret") flag.BoolVar(&cmd.forcePWD, "fp", false, "Force password entry") flag.StringVar(&cmd.privPWD, "privPWD", "mypassword", "Private key password") @@ -388,6 +399,7 @@ func Run(args []string) { flag.StringVar(&cmd.didImgFile, "didImgFile", did.DIDImgFileName, "DID image") flag.StringVar(&cmd.privImgFile, "privImgFile", did.PvtShareFileName, "DID public share image") flag.StringVar(&cmd.pubImgFile, "pubImgFile", did.PubShareFileName, "DID public share image") + flag.StringVar(&cmd.mnemonicFile, "mnemonicKeyFile", did.MnemonicFileName, "Mnemonic key file") flag.StringVar(&cmd.privKeyFile, "privKeyFile", did.PvtKeyFileName, "Private key file") flag.StringVar(&cmd.pubKeyFile, "pubKeyFile", did.PubKeyFileName, "Public key file") flag.StringVar(&cmd.quorumList, "quorumList", "quorumlist.json", "Quorum list") @@ -432,6 +444,7 @@ func Run(args []string) { flag.StringVar(&cmd.executorAddr, "executorAddr", "", "Smart contract Executor Address") flag.BoolVar(&cmd.latest, "latest", false, "flag to set latest") flag.StringVar(&cmd.quorumAddr, "quorumAddr", "", "Quorum Node Address to check the status of the Quorum") + flag.StringVar(&links, "links", "", "Explorer url") if len(os.Args) < 2 { fmt.Println("Invalid Command") @@ -450,6 +463,11 @@ func Run(args []string) { cmd.peers = strings.Split(peers, ",") } + if links != "" { + links = strings.ReplaceAll(links, " ", "") + cmd.links = strings.Split(links, ",") + } + cmd.timeout = time.Duration(timeout) * time.Minute if !cmd.validateOptions() { @@ -572,10 +590,18 @@ func Run(args []string) { cmd.getSmartContractData() case ExecuteSmartcontractCmd: cmd.executeSmartcontract() + case GetPeerID: + cmd.peerIDCmd() case ReleaseAllLockedTokensCmd: cmd.releaseAllLockedTokens() case CheckQuorumStatusCmd: cmd.checkQuorumStatus() + case AddExplorerCmd: + cmd.addExplorer() + case RemoveExplorerCmd: + cmd.removeExplorer() + case GetAllExplorerCmd: + cmd.getAllExplorer() default: cmd.log.Error("Invalid command") } diff --git a/command/did.go b/command/did.go index c6a84a05..d139543e 100644 --- a/command/did.go +++ b/command/did.go @@ -50,7 +50,12 @@ func (cmd *Command) CreateDID() { } cmd.quorumPWD = pwd } - if cmd.didType == did.WalletDIDMode { + if cmd.didType == did.LiteDIDMode { + if cmd.privKeyFile == "" || cmd.pubKeyFile == "" { + cmd.log.Error("private key & public key file names required") + return + } + } else if cmd.didType == did.WalletDIDMode { f, err := os.Open(cmd.imgFile) if err != nil { cmd.log.Error("failed to open image", "err", err) @@ -115,8 +120,7 @@ func (cmd *Command) CreateDID() { cmd.log.Error("failed to create image", "err", err) return } - } - if cmd.didType != did.BasicDIDMode { + } else if cmd.didType != did.BasicDIDMode { if cmd.privKeyFile == "" || cmd.pubKeyFile == "" { cmd.log.Error("private key & public key file names required") return @@ -147,13 +151,15 @@ func (cmd *Command) CreateDID() { DIDImgFileName: cmd.didImgFile, PubImgFile: cmd.pubImgFile, PubKeyFile: cmd.pubKeyFile, + MnemonicFile: cmd.mnemonicFile, + ChildPath: cmd.ChildPath, } msg, status := cmd.c.CreateDID(&cfg) if !status { cmd.log.Error("Failed to create DID", "message", msg) return } - cmd.log.Info("DID Created successfully") + cmd.log.Info(fmt.Sprintf("DID %v created successfully", msg)) } func (cmd *Command) GetAllDID() { @@ -248,6 +254,8 @@ func (cmd *Command) SignatureResponse(br *model.BasicResponse, timeout ...time.D Mode: sr.Mode, } switch sr.Mode { + case did.LiteDIDMode: + sresp.Password = password case did.BasicDIDMode: sresp.Password = password case did.StandardDIDMode: diff --git a/command/explorer.go b/command/explorer.go new file mode 100644 index 00000000..01b1552b --- /dev/null +++ b/command/explorer.go @@ -0,0 +1,43 @@ +package command + +import "fmt" + +func (cmd *Command) addExplorer() { + if len(cmd.links) == 0 { + cmd.log.Error("links required for Explorer") + return + } + msg, status := cmd.c.AddExplorer(cmd.links) + + if !status { + cmd.log.Error("Add Explorer command failed, " + msg) + } else { + cmd.log.Info("Add Explorer command finished, " + msg) + } +} + +func (cmd *Command) removeExplorer() { + if len(cmd.links) == 0 { + cmd.log.Error("links required for Explorer") + return + } + msg, status := cmd.c.RemoveExplorer(cmd.links) + if !status { + cmd.log.Error("Remove Explorer command failed, " + msg) + } else { + cmd.log.Info("Remove Explorer command finished, " + msg) + } +} + +func (cmd *Command) getAllExplorer() { + links, msg, status := cmd.c.GetAllExplorer() + if !status { + cmd.log.Error("Get all Explorer command failed, " + msg) + } else { + cmd.log.Info("Get all Explorer command finished, " + msg) + cmd.log.Info("Explorer links", "links", links) + for i, q := range links { + fmt.Printf("URL %d: %s\n", i, q) + } + } +} diff --git a/command/node.go b/command/node.go index 87975cac..e343eb34 100644 --- a/command/node.go +++ b/command/node.go @@ -1,5 +1,10 @@ package command +import ( + "fmt" + "os" +) + func (cmd *Command) ShutDownCmd() { msg, status := cmd.c.Shutdown() if !status { @@ -8,3 +13,15 @@ func (cmd *Command) ShutDownCmd() { } cmd.log.Info("Shutdown initiated successfully, " + msg) } + +func (cmd *Command) peerIDCmd() { + msg, status := cmd.c.PeerID() + if !status { + cmd.log.Error("Failed to fetch peer ID of the node", "msg", msg) + return + } + _, err := fmt.Fprint(os.Stdout, msg, "\n") + if err != nil { + cmd.log.Error(err.Error()) + } +} diff --git a/contract/contract.go b/contract/contract.go index 6da75c2e..d19334e3 100644 --- a/contract/contract.go +++ b/contract/contract.go @@ -106,6 +106,7 @@ func (c *Contract) blkDecode() error { if err != nil { return err } + tcb[SCBlockHashKey] = util.HexToStr(hb) if sok { var ksb map[string]interface{} @@ -149,6 +150,7 @@ func (c *Contract) blkEncode() error { return err } hb := util.CalculateHash(bc, "SHA3-256") + c.sm[SCBlockHashKey] = util.HexToStr(hb) m := make(map[string]interface{}) m[SCBlockContentKey] = bc @@ -232,7 +234,8 @@ func (c *Contract) GetHashSig(did string) (string, string, string, error) { ss := util.GetStringFromMap(ssi, did) ks := util.GetStringFromMap(ksi, did) - if ss == "" || ks == "" { + // ss == "" || + if ks == "" { return "", "", "", fmt.Errorf("invalid smart contract, share/key signature block is missing") } return hi.(string), ss, ks, nil @@ -387,6 +390,7 @@ func (c *Contract) UpdateSignature(dc did.DIDCrypto) error { if err != nil { return fmt.Errorf("Failed to get signature, " + err.Error()) } + if c.sm[SCShareSignatureKey] == nil { ksm := make(map[string]interface{}) ksm[did] = util.HexToStr(ssig) @@ -428,19 +432,39 @@ func (c *Contract) UpdateSignature(dc did.DIDCrypto) error { return c.blkEncode() } +// This function is used by the quorums to verify sender's signature func (c *Contract) VerifySignature(dc did.DIDCrypto) error { - did := dc.GetDID() - hs, ss, ps, err := c.GetHashSig(did) + //fetch sender's did + didstr := dc.GetDID() + + //fetch sender's signature + hs, ss, ps, err := c.GetHashSig(didstr) if err != nil { c.log.Error("err", err) return err } - ok, err := dc.Verify(hs, util.StrToHex(ss), util.StrToHex(ps)) - if err != nil { - return err - } - if !ok { - return fmt.Errorf("did signature verification failed") + + //If the ss i.e., share signature is empty, then its a Pki sign, so call PvtVerify + //Else it is NLSS based sign, so call NlssVerify + didType := dc.GetSignType() + if didType == did.BIPVersion { + ok, err := dc.PvtVerify([]byte(hs), util.StrToHex(ps)) + + if err != nil { + return err + } + if !ok { + return fmt.Errorf("did Pki signature verification failed") + } + } else { + ok, err := dc.NlssVerify(hs, util.StrToHex(ss), util.StrToHex(ps)) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("did Nlss signature verification failed") + } } + return nil } diff --git a/core/core.go b/core/core.go index 9a5ced93..f608c287 100644 --- a/core/core.go +++ b/core/core.go @@ -46,7 +46,9 @@ const ( APIGetTokenNumber string = "/api/get-token-number" APIGetMigratedTokenStatus string = "/api/get-Migrated-token-status" APISyncDIDArbitration string = "/api/sync-did-arbitration" + APIUnlockTokens string = "/api/unlock-tokens" APICheckQuorumStatusPath string = "/api/check-quorum-status" + APIGetPeerDIDTypePath string = "/api/get-peer-didType" ) const ( @@ -320,6 +322,7 @@ func (c *Core) SetupCore() error { c.w.SetupWallet(c.ipfs) c.PingSetup() c.CheckQuorumStatusSetup() + c.GetPeerdidTypeSetup() c.peerSetup() c.w.AddDIDLastChar() c.SetupToken() @@ -352,7 +355,6 @@ func (c *Core) Start() (bool, string) { return false, "Failed to start ping port" } //c.w.ReleaseAllLockedTokens() - // exp := model.ExploreModel{ // Cmd: ExpPeerStatusCmd, // PeerID: c.peerID, @@ -509,6 +511,8 @@ func (c *Core) SetupDID(reqID string, didStr string) (did.DIDCrypto, error) { return nil, fmt.Errorf("faield to get did channel") } switch dt.Type { + case did.LiteDIDMode: + return did.InitDIDLite(didStr, c.didDir, dc), nil case did.BasicDIDMode: return did.InitDIDBasic(didStr, c.didDir, dc), nil case did.StandardDIDMode: @@ -522,20 +526,70 @@ func (c *Core) SetupDID(reqID string, didStr string) (did.DIDCrypto, error) { } } +// Initializes the did in it's corresponding did mode (basic/ lite) func (c *Core) SetupForienDID(didStr string) (did.DIDCrypto, error) { err := c.FetchDID(didStr) if err != nil { + c.log.Error("couldn't fetch did") return nil, err } - return did.InitDIDBasic(didStr, c.didDir, nil), nil + + // Fetching peer's did type from PeerDIDTable using GetPeerDIDType function + // and own did type from DIDTable using GetDID function + didtype, err := c.w.GetPeerDIDType(didStr) + if err != nil { + dt, err1 := c.w.GetDID(didStr) + if err1 != nil { + return nil, fmt.Errorf("couldn't fetch did type") + } + didtype = dt.Type + } + return c.InitialiseDID(didStr, didtype) } +// Initializes the quorum in it's corresponding did mode (basic/ lite) func (c *Core) SetupForienDIDQuorum(didStr string) (did.DIDCrypto, error) { err := c.FetchDID(didStr) if err != nil { return nil, err } - return did.InitDIDQuorumc(didStr, c.didDir, ""), nil + + // Fetching peer's did type from PeerDIDTable using GetPeerDIDType function + // and own did type from DIDTable using GetDID function + didtype, err := c.w.GetPeerDIDType(didStr) + if err != nil || didtype == -1 { + dt, err1 := c.w.GetDID(didStr) + + if err1 != nil || dt.Type == -1 { + peerId := c.w.GetPeerID(didStr) + + if peerId == "" { + return nil, err + } + didtype_, msg, err2 := c.GetPeerdidType_fromPeer(peerId, didStr) + if err2 != nil { + c.log.Error(msg) + return nil, err2 + } + didtype = didtype_ + peerUpdateResult, err3 := c.w.UpdatePeerDIDType(didStr, didtype) + if !peerUpdateResult { + c.log.Error("couldn't update did type in peer did table", err3) + } + } else { + didtype = dt.Type + } + + } + + switch didtype { + case did.BasicDIDMode: + return did.InitDIDQuorumc(didStr, c.didDir, ""), nil + case did.LiteDIDMode: + return did.InitDIDQuorum_Lt(didStr, c.didDir, ""), nil + default: + return nil, fmt.Errorf("invalid did type") + } } func (c *Core) FetchDID(did string) error { @@ -565,3 +619,19 @@ func (c *Core) FetchDID(did string) error { func (c *Core) GetPeerID() string { return c.peerID } + +// Initializes the did in it's corresponding did mode (basic/ lite) +func (c *Core) InitialiseDID(didStr string, didType int) (did.DIDCrypto, error) { + err := c.FetchDID(didStr) + if err != nil { + return nil, err + } + switch didType { + case did.LiteDIDMode: + return did.InitDIDLite(didStr, c.didDir, nil), nil + case did.BasicDIDMode: + return did.InitDIDBasic(didStr, c.didDir, nil), nil + default: + return nil, fmt.Errorf("invalid did type, couldn't initialise") + } +} diff --git a/core/did.go b/core/did.go index 3adacdb3..5818135c 100644 --- a/core/did.go +++ b/core/did.go @@ -30,6 +30,23 @@ func (c *Core) GetDIDAccess(req *model.GetDIDAccess) *model.DIDAccessResponse { resp.Message = "Password does not match" return resp } + } else if dt.Type == did.LiteDIDMode { + _, ok := c.ValidateDIDToken(req.Token, setup.ChanllegeTokenType, req.DID) + if !ok { + resp.Message = "Invalid token" + return resp + } + dc := did.InitDIDLite(req.DID, c.didDir, nil) + ok, err := dc.PvtVerify([]byte(req.Token), req.Signature) + if err != nil { + c.log.Error("Failed to verify DID signature", "err", err) + resp.Message = "Failed to verify DID signature" + return resp + } + if !ok { + resp.Message = "Invalid signature" + return resp + } } else { _, ok := c.ValidateDIDToken(req.Token, setup.ChanllegeTokenType, req.DID) if !ok { @@ -197,11 +214,17 @@ func (c *Core) registerDID(reqID string, did string) error { if err != nil { return fmt.Errorf("register did, failed to do signature") } + + dt, err := c.w.GetDID(did) + if err != nil { + return fmt.Errorf("DID does not exist") + } pm := &PeerMap{ PeerID: c.peerID, DID: did, Signature: sig, Time: t, + DIDType: dt.Type, } err = c.publishPeerMap(pm) if err != nil { diff --git a/core/explorer.go b/core/explorer.go index 9ecf6bef..4f9a32a7 100644 --- a/core/explorer.go +++ b/core/explorer.go @@ -3,7 +3,9 @@ package core import ( "fmt" "net/http" + "strings" + "github.com/rubixchain/rubixgoplatform/core/storage" "github.com/rubixchain/rubixgoplatform/wrapper/config" "github.com/rubixchain/rubixgoplatform/wrapper/ensweb" "github.com/rubixchain/rubixgoplatform/wrapper/helper/jsonutil" @@ -16,11 +18,13 @@ const ( ExplorerTransactionAPI string = "CreateOrUpdateRubixTransaction" ExplorerCreateDataTransAPI string = "create-datatokens" ExplorerMapDIDAPI string = "map-did" + ExplorerURLTable string = "ExplorerURLTable" ) type ExplorerClient struct { ensweb.Client log logger.Logger + es storage.Storage } type ExplorerDID struct { @@ -67,11 +71,33 @@ type ExplorerResponse struct { Status bool `json:"Status"` } +type ExplorerURL struct { + URL string `gorm:"column:url;primaryKey" json:"ExplorerURL"` + Port int `gorm:"column:port" json:"Explorerport"` +} + func (c *Core) InitRubixExplorer() error { + + err := c.s.Init(ExplorerURLTable, &ExplorerURL{}, true) + if err != nil { + c.log.Error("Failed to initialise storage ExplorerURL ", "err", err) + return err + } + url := "deamon-explorer.azurewebsites.net" if c.testNet { url = "rubix-deamon-api.ensurity.com" } + + err = c.s.Read(ExplorerURLTable, &ExplorerURL{}, "url=?", url) + if err != nil { + err = c.s.Write(ExplorerURLTable, &ExplorerURL{URL: url, Port: 443}) + } + + if err != nil { + return err + } + cl, err := ensweb.NewClient(&config.Config{ServerAddress: url, ServerPort: "443", Production: "true"}, c.log) if err != nil { return err @@ -79,33 +105,44 @@ func (c *Core) InitRubixExplorer() error { c.ec = &ExplorerClient{ Client: cl, log: c.log.Named("explorerclient"), + es: c.s, } return nil } func (ec *ExplorerClient) SendExploerJSONRequest(method string, path string, input interface{}, output interface{}) error { - req, err := ec.JSONRequest(method, ExplorerBasePath+path, input) - if err != nil { - return err - } - resp, err := ec.Do(req) + + var urls []string + urls, err := ec.GetAllExplorer() if err != nil { - ec.log.Error("Failed r get response from explorer", "err", err) return err } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - str := fmt.Sprintf("Http Request failed with status %d", resp.StatusCode) - ec.log.Error(str) - return fmt.Errorf(str) - } - if output == nil { - return nil - } - err = jsonutil.DecodeJSONFromReader(resp.Body, output) - if err != nil { - ec.log.Error("Invalid response from the node", "err", err) - return err + + for _, url := range urls { + req, err := ec.JSONRequestForExplorer(method, ExplorerBasePath+path, input, url) + if err != nil { + ec.log.Error("Request could not be sent to : "+url, "err", err) + continue + } + resp, err := ec.Do(req) + if err != nil { + ec.log.Error("Failed to get response from explorer : "+url, "err", err) + continue + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + str := fmt.Sprintf("Http Request failed with status %d for "+url, resp.StatusCode) + ec.log.Error(str) + continue + } + if output == nil { + continue + } + err = jsonutil.DecodeJSONFromReader(resp.Body, output) + if err != nil { + ec.log.Error("Invalid response from the node", "err", err) + continue + } } return nil } @@ -170,3 +207,66 @@ func (ec *ExplorerClient) ExplorerDataTransaction(et *ExplorerDataTrans) error { } return nil } + +func (c *Core) AddExplorer(links []string) error { + + var eurl []ExplorerURL + + for _, url := range links { + if strings.HasPrefix(url, "https") { + url = strings.TrimPrefix(url, "https://") + } else if strings.HasPrefix(url, "http") { + url = strings.TrimPrefix(url, "http://") + } + eur := ExplorerURL{ + URL: url, + Port: 0, + } + eurl = append(eurl, eur) + } + + err := c.s.Write(ExplorerURLTable, eurl) + if err != nil { + return err + } + return nil +} + +func (c *Core) RemoveExplorer(links []string) error { + + for _, url := range links { + if strings.HasPrefix(url, "https") { + url = strings.TrimPrefix(url, "https://") + } else if strings.HasPrefix(url, "http") { + url = strings.TrimPrefix(url, "http://") + } + err := c.s.Delete(ExplorerURLTable, &ExplorerURL{}, "url=?", url) + + if err != nil { + return err + } + } + return nil +} + +func (c *Core) GetAllExplorer() ([]string, error) { + var urls []string + urls, err := c.ec.GetAllExplorer() + if err != nil { + return nil, err + } + return urls, nil +} + +func (ec *ExplorerClient) GetAllExplorer() ([]string, error) { + var eurl []ExplorerURL + var urls []string + err := ec.es.Read(ExplorerURLTable, &eurl, "url!=?", "") + if err != nil { + return nil, err + } + for _, url := range eurl { + urls = append(urls, fmt.Sprintf("https://%s", url.URL)) + } + return urls, nil +} diff --git a/core/ipfsport/peer.go b/core/ipfsport/peer.go index d5c68fb8..cee13a43 100644 --- a/core/ipfsport/peer.go +++ b/core/ipfsport/peer.go @@ -3,6 +3,7 @@ package ipfsport import ( "context" "fmt" + "net" "net/http" "path" "sync" @@ -65,13 +66,34 @@ func (pm *PeerManager) getPeerPort() uint16 { defer pm.lock.Unlock() for i, status := range pm.ps { if !status { - pm.ps[i] = true - return pm.startPort + uint16(i) + port := pm.startPort + uint16(i) + availability := isPortAvailable(port) + + if availability { + pm.ps[i] = true + return pm.startPort + uint16(i) + } } } return 0 } +func isPortAvailable(port uint16) bool { + // Convert uint16 port to int + portInt := int(port) + + // Attempt to listen on the port + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", portInt)) + if err != nil { + // Port is not available + return false + } + defer listener.Close() + + // Port is available + return true +} + func (pm *PeerManager) releasePeerPort(port uint16) bool { pm.lock.Lock() defer pm.lock.Unlock() @@ -159,7 +181,6 @@ func (pm *PeerManager) OpenPeerConn(peerID string, did string, appname string) ( defer resp.Close() if resp.Error != nil { pm.log.Error("error in forward request") - pm.releasePeerPort(portNum) return nil, resp.Error } p.Client, err = ensweb.NewClient(scfg, p.log) diff --git a/core/migrate.go b/core/migrate.go index 317ed005..cd55f0b0 100644 --- a/core/migrate.go +++ b/core/migrate.go @@ -158,25 +158,31 @@ func (c *Core) migrateNode(reqID string, m *MigrateRequest, didDir string) error } if !resume { didCreate := didm.DIDCreate{ - Dir: didDir, - Type: m.DIDType, - PrivPWD: m.PrivPWD, - QuorumPWD: m.QuorumPWD, - DIDImgFileName: rubixDir + "DATA/" + d[0].DID + "/DID.png", - PubImgFile: rubixDir + "DATA/" + d[0].DID + "/PublicShare.png", - PrivImgFile: rubixDir + "DATA/" + d[0].DID + "/PrivateShare.png", + Dir: didDir, + Type: m.DIDType, + PrivPWD: m.PrivPWD, + QuorumPWD: m.QuorumPWD, } - _, err = os.Stat(didCreate.DIDImgFileName) - if err != nil { - c.log.Error("Failed to migrate, missing DID.png file", "err", err) - return fmt.Errorf("failed to migrate, missing DID.png file") - } - _, err = os.Stat(didCreate.PubImgFile) - if err != nil { - c.log.Error("Failed to migrate, missing PublicShare.png file", "err", err) - return fmt.Errorf("failed to migrate, missing PublicShare.png file") + if didCreate.Type != didm.LiteDIDMode { + didCreate = didm.DIDCreate{ + DIDImgFileName: rubixDir + "DATA/" + d[0].DID + "/DID.png", + PubImgFile: rubixDir + "DATA/" + d[0].DID + "/PublicShare.png", + PrivImgFile: rubixDir + "DATA/" + d[0].DID + "/PrivateShare.png", + } + + _, err = os.Stat(didCreate.DIDImgFileName) + if err != nil { + c.log.Error("Failed to migrate, missing DID.png file", "err", err) + return fmt.Errorf("failed to migrate, missing DID.png file") + } + _, err = os.Stat(didCreate.PubImgFile) + if err != nil { + c.log.Error("Failed to migrate, missing PublicShare.png file", "err", err) + return fmt.Errorf("failed to migrate, missing PublicShare.png file") + } } + did, err = c.d.MigrateDID(&didCreate) if err != nil { c.log.Error("Failed to migrate, failed in creation of new DID address", "err", err, "msg", did) @@ -646,6 +652,7 @@ func (c *Core) migrateNode(reqID string, m *MigrateRequest, didDir string) error fp.Close() } } + // if len(migrateTokens) > 0 { // fp, err := os.Open("migratedtokens.txt") // if err == nil { @@ -655,6 +662,7 @@ func (c *Core) migrateNode(reqID string, m *MigrateRequest, didDir string) error // fp.Close() // } // } + creditFiles, err := util.GetAllFiles(rubixDir + "Wallet/WALLET_DATA/Credits/") if err != nil { c.log.Error("Failed to migrate, failed to read credit files", "err", err) diff --git a/core/model/basic.go b/core/model/basic.go index 65493651..eb70deaa 100644 --- a/core/model/basic.go +++ b/core/model/basic.go @@ -20,3 +20,9 @@ type MigratedTokenStatus struct { Message string `json:"message"` MigratedStatus []int `json:"migratedstatus"` } + +// GetDIDTypeResponse is the model for response of peer while fetching did type +type GetDIDTypeResponse struct { + DidType int + BasicResponse +} diff --git a/core/model/explorer.go b/core/model/explorer.go new file mode 100644 index 00000000..6956e7cc --- /dev/null +++ b/core/model/explorer.go @@ -0,0 +1,13 @@ +package model + +// ExplorerLinks +type ExplorerLinks struct { + Links []string `json:"links"` +} + +// ExplorerResponse used as model for the API responses +type ExplorerResponse struct { + Status bool `json:"status"` + Message string `json:"message"` + Result ExplorerLinks `json:"result"` +} diff --git a/core/peer.go b/core/peer.go index 52456864..553cd878 100644 --- a/core/peer.go +++ b/core/peer.go @@ -18,6 +18,7 @@ const ( type PeerMap struct { PeerID string `json:"peer_id"` DID string `json:"did"` + DIDType int `json:"did_type"` Signature []byte `json:"signature"` Time string `json:"time"` } @@ -48,7 +49,7 @@ func (c *Core) peerCallback(peerID string, topic string, data []byte) { return } h := util.CalculateHashString(m.PeerID+m.DID+m.Time, "SHA3-256") - dc, err := c.SetupForienDID(m.DID) + dc, err := c.InitialiseDID(m.DID, m.DIDType) if err != nil { return } @@ -56,7 +57,7 @@ func (c *Core) peerCallback(peerID string, topic string, data []byte) { if err != nil || !st { return } - c.w.AddDIDPeerMap(m.DID, m.PeerID) + c.w.AddDIDPeerMap(m.DID, m.PeerID, m.DIDType) } func (c *Core) peerStatus(req *ensweb.Request) *ensweb.Result { diff --git a/core/ping.go b/core/ping.go index 35b0e729..4d0731f0 100644 --- a/core/ping.go +++ b/core/ping.go @@ -29,6 +29,11 @@ func (c *Core) CheckQuorumStatusSetup() { c.l.AddRoute(APICheckQuorumStatusPath, "GET", c.CheckQuorumStatusResponse) } +// GetPeerdidTypeSetup will setup the ping route +func (c *Core) GetPeerdidTypeSetup() { + c.l.AddRoute(APIGetPeerDIDTypePath, "GET", c.GetPeerdidTypeResponse) +} + // PingRecevied is the handler for ping request func (c *Core) PingRecevied(req *ensweb.Request) *ensweb.Result { c.log.Info("Ping Received") @@ -98,3 +103,48 @@ func (c *Core) CheckQuorumStatus(peerID string, did string) (string, bool, error } return checkQuorumStatusResponse.Message, checkQuorumStatusResponse.Status, nil } + +// CheckQuorumStatusResponse is the handler for CheckQuorumStatus request +func (c *Core) GetPeerdidTypeResponse(req *ensweb.Request) *ensweb.Result { //PingRecevied + did := c.l.GetQuerry(req, "did") + c.log.Info("Fetching peer did type from peer") + resp := &model.GetDIDTypeResponse{ + BasicResponse: model.BasicResponse{ + Status: false, + }, + } + + dt, err := c.w.GetDID(did) + if err != nil { + c.log.Error("Couldn't fetch did type from DID Table", "error", err) + resp.Message = "Couldn't fetch did type for did: " + did + resp.Status = false + resp.DidType = -1 + return c.l.RenderJSON(req, &resp, http.StatusOK) + } else { + resp.DidType = dt.Type + resp.Status = true + resp.Message = "successfully fetched did type" + return c.l.RenderJSON(req, &resp, http.StatusOK) + } + +} + +// GetPeerdidType will ping the peer & get the did type +func (c *Core) GetPeerdidType_fromPeer(peerID string, did string) (int, string, error) { + q := make(map[string]string) + p, err := c.pm.OpenPeerConn(peerID, did, c.getCoreAppName(peerID)) + if err != nil { + return -1, "Quorum Connection Error", fmt.Errorf("quorum connection error") + } + + // Close the p2p before exit + defer p.Close() + q["did"] = did + var getPeerdidTypeResponse model.GetDIDTypeResponse + err = p.SendJSONRequest("GET", APIGetPeerDIDTypePath, q, nil, &getPeerdidTypeResponse, false, 2*time.Minute) + if err != nil { + return -1, "Send Json Request error ", err + } + return getPeerdidTypeResponse.DidType, getPeerdidTypeResponse.Message, nil +} diff --git a/core/quorum_initiator.go b/core/quorum_initiator.go index 1416bf78..237cc0b4 100644 --- a/core/quorum_initiator.go +++ b/core/quorum_initiator.go @@ -60,9 +60,10 @@ type ConensusReply struct { } type ConsensusResult struct { - RunningCount int - SuccessCount int - FailedCount int + RunningCount int + SuccessCount int + FailedCount int + PledgingDIDSignStatus bool } type ConsensusStatus struct { @@ -128,6 +129,7 @@ type CreditSignature struct { PrivSignature string `json:"priv_signature"` DID string `json:"did"` Hash string `json:"hash"` + SignVersion string `json:"sign_version"` //represents sign version (PkiSign == 0 or NlssSign==1) } type TokenArbitrationReq struct { @@ -141,6 +143,11 @@ type ArbitaryStatus struct { status bool } +type TokenList struct { + Tokens []string + DID string +} + // PingSetup will setup the ping route func (c *Core) QuroumSetup() { c.l.AddRoute(APICreditStatus, "GET", c.creditStatus) @@ -150,6 +157,7 @@ func (c *Core) QuroumSetup() { c.l.AddRoute(APIUpdatePledgeToken, "POST", c.updatePledgeToken) c.l.AddRoute(APISignatureRequest, "POST", c.signatureRequest) c.l.AddRoute(APISendReceiverToken, "POST", c.updateReceiverToken) + c.l.AddRoute(APIUnlockTokens, "POST", c.unlockTokens) if c.arbitaryMode { c.l.AddRoute(APIMapDIDArbitration, "POST", c.mapDIDArbitration) c.l.AddRoute(APICheckDIDArbitration, "GET", c.chekDIDArbitration) @@ -165,20 +173,52 @@ func (c *Core) SetupQuorum(didStr string, pwd string, pvtKeyPwd string) error { c.log.Error("DID does not exist", "did", didStr) return fmt.Errorf("DID does not exist") } - dc := did.InitDIDQuorumc(didStr, c.didDir, pwd) - if dc == nil { - c.log.Error("Failed to setup quorum") - return fmt.Errorf("failed to setup quorum") + + dt, err := c.w.GetDID(didStr) + if err != nil { + c.log.Error("DID could not fetch", "did", didStr) + return fmt.Errorf("DID does not exist") } - c.qc[didStr] = dc - if pvtKeyPwd != "" { - dc := did.InitDIDBasicWithPassword(didStr, c.didDir, pvtKeyPwd) + + //To support NLSS backward compatibility, + //If the Quorum's did is created in lite mode, + //it will initiate DIDQuorum_Lt, and if it is in basic mode, + //it will initiate DIDQuorumc + switch dt.Type { + case did.LiteDIDMode: + dc := did.InitDIDQuorum_Lt(didStr, c.didDir, pwd) if dc == nil { c.log.Error("Failed to setup quorum") return fmt.Errorf("failed to setup quorum") } - c.pqc[didStr] = dc + c.qc[didStr] = dc + if pvtKeyPwd != "" { + dc := did.InitDIDLiteWithPassword(didStr, c.didDir, pvtKeyPwd) + if dc == nil { + c.log.Error("Failed to setup quorum as dc is nil") + return fmt.Errorf("failed to setup quorum") + } + c.pqc[didStr] = dc + } + case did.BasicDIDMode: + dc := did.InitDIDQuorumc(didStr, c.didDir, pwd) + if dc == nil { + c.log.Error("Failed to setup quorum") + return fmt.Errorf("failed to setup quorum") + } + c.qc[didStr] = dc + if pvtKeyPwd != "" { + dc := did.InitDIDBasicWithPassword(didStr, c.didDir, pvtKeyPwd) + if dc == nil { + c.log.Error("Failed to setup quorum") + return fmt.Errorf("failed to setup quorum") + } + c.pqc[didStr] = dc + } + default: + return fmt.Errorf("DID Type is not supported") } + c.up.RunUnpledge() return nil } @@ -234,9 +274,10 @@ func (c *Core) initiateConsensus(cr *ConensusRequest, sc *contract.Contract, dc }, P: make(map[string]*ipfsport.Peer), Result: ConsensusResult{ - RunningCount: 0, - SuccessCount: 0, - FailedCount: 0, + RunningCount: 0, + SuccessCount: 0, + FailedCount: 0, + PledgingDIDSignStatus: false, }, } reqPledgeTokens := float64(0) @@ -337,6 +378,10 @@ func (c *Core) initiateConsensus(cr *ConensusRequest, sc *contract.Contract, dc } } if err != nil { + unlockErr := c.checkLokedTokens(cr, ql) + if unlockErr != nil { + c.log.Error(unlockErr.Error() + "Locked tokens could not be unlocked") + } return nil, nil, err } @@ -673,29 +718,65 @@ func (c *Core) finishConsensus(id string, qt int, p *ipfsport.Peer, status bool, defer c.qlock.Unlock() cs, ok := c.quorumRequest[id] if !ok { + fmt.Println("failed to get quorum consensus") if p != nil { p.Close() } return } + pd, ok := c.pd[id] //getting details of quorums who pledged + if !ok { + fmt.Println("failed to get pledged token details") + if p != nil { + p.Close() + } + return + } + pledgingQuorumDID := make([]string, 0, len(pd.PledgedTokens)) + for k := range pd.PledgedTokens { + pledgingQuorumDID = append(pledgingQuorumDID, k) + } + var pledgingDID string + if len(pledgingQuorumDID) > 0 { + pledgingDID = pledgingQuorumDID[0] + } + + var signVersion string + + //signVersion = 0 => Pki based sign in lite mode + //signVersion = 1 => Nlss based sign in basic mode + if util.HexToStr(ss) == "" { + signVersion = "0" + } else { + signVersion = "1" + } + switch qt { case 0: cs.Result.RunningCount-- if status { did := p.GetPeerDID() - if cs.Result.SuccessCount < MinConsensusRequired { - csig := CreditSignature{ - Signature: util.HexToStr(ss), - PrivSignature: util.HexToStr(ps), - DID: did, - Hash: hash, - } + csig := CreditSignature{ + Signature: util.HexToStr(ss), + PrivSignature: util.HexToStr(ps), + DID: did, + Hash: hash, + SignVersion: signVersion, + } + if cs.Result.SuccessCount < MinConsensusRequired-1 { cs.P[did] = p cs.Credit.Credit = append(cs.Credit.Credit, csig) + cs.Result.SuccessCount++ + if did == pledgingDID { + cs.Result.PledgingDIDSignStatus = true + } + } else if (did == pledgingDID || cs.Result.PledgingDIDSignStatus) && cs.Result.SuccessCount == MinConsensusRequired-1 { + cs.P[did] = p + cs.Credit.Credit = append(cs.Credit.Credit, csig) + cs.Result.SuccessCount++ } else { p.Close() } - cs.Result.SuccessCount++ } else { cs.Result.FailedCount++ if p != nil { @@ -1260,3 +1341,38 @@ func (c *Core) createCommitedTokensBlock(newBlock *block.Block, smartContractTok } return nil } + +func (c *Core) checkLokedTokens(cr *ConensusRequest, quorumList []string) error { + // var err error + pd := c.pd[cr.ReqID] + + pledgingQuorumDID := make([]string, 0, len(pd.PledgedTokens)) + for k := range pd.PledgedTokens { + pledgingQuorumDID = append(pledgingQuorumDID, k) + } + var pledgingDID string + if len(pledgingQuorumDID) > 0 { + pledgingDID = pledgingQuorumDID[0] + } + var br model.BasicResponse + for _, addr := range quorumList { + peerID, did, _ := util.ParseAddress(addr) + if did == pledgingDID { + p, err := c.pm.OpenPeerConn(peerID, did, c.getCoreAppName(peerID)) + if err != nil { + c.log.Error("Failed to get peer connection", "err", err) + return err + } + tokenList := TokenList{ + Tokens: pd.PledgedTokens[pledgingDID], + DID: pledgingDID, + } + err = p.SendJSONRequest("POST", APIUnlockTokens, nil, &tokenList, &br, true) + if err != nil { + c.log.Error("Invalid response for pledge request", "err", err) + return err + } + } + } + return nil +} diff --git a/core/quorum_recv.go b/core/quorum_recv.go index 284d09a6..141b296f 100644 --- a/core/quorum_recv.go +++ b/core/quorum_recv.go @@ -46,7 +46,7 @@ func (c *Core) verifyContract(cr *ConensusRequest) (bool, *contract.Contract) { } err = sc.VerifySignature(dc) if err != nil { - c.log.Error("Failed to verify sender signature", "err", err) + c.log.Error("Failed to verify sender signature in verifyContract", "err", err) return false, nil } return true, sc @@ -570,6 +570,14 @@ func (c *Core) reqPledgeToken(req *ensweb.Request) *ensweb.Result { crep.Message = "Failed to parse json request" return c.l.RenderJSON(req, &crep, http.StatusOK) } + + _, ok := c.qc[did] + if !ok { + c.log.Error("Quorum is not setup") + crep.Message = "Quorum is not setup" + return c.l.RenderJSON(req, &crep, http.StatusOK) + } + if (pr.TokensRequired) < MinTrnxAmt { c.log.Error("Pledge amount is less than ", MinTrnxAmt) crep.Message = "Pledge amount is less than minimum transcation amount" @@ -1316,3 +1324,26 @@ func (c *Core) getProofverificationDetails(tokenID string, senderAddr string) (s } return receiverDID, txnId, nil } + +func (c *Core) unlockTokens(req *ensweb.Request) *ensweb.Result { + var tokenList TokenList + err := c.l.ParseJSON(req, &tokenList) + crep := model.BasicResponse{ + Status: false, + } + if err != nil { + c.log.Error("Failed to parse json request", "err", err) + crep.Message = "Failed to parse json request" + return c.l.RenderJSON(req, &crep, http.StatusOK) + } + err = c.w.UnlockLockedTokens(tokenList.DID, tokenList.Tokens) + if err != nil { + c.log.Error("Failed to update token status", "err", err) + return c.l.RenderJSON(req, &crep, http.StatusOK) + } + crep.Status = true + crep.Message = "Tokens Unlocked Successfully." + c.log.Info("Tokens Unclocked") + return c.l.RenderJSON(req, &crep, http.StatusOK) + +} diff --git a/core/wallet/did.go b/core/wallet/did.go index 224971dd..798bd84a 100644 --- a/core/wallet/did.go +++ b/core/wallet/did.go @@ -10,6 +10,7 @@ type DIDType struct { type DIDPeerMap struct { DID string `gorm:"column:did;primaryKey"` + DIDType *int `gorm:"column:did_type"` PeerID string `gorm:"column:peer_id"` DIDLastChar string `gorm:"column:did_last_char"` } @@ -82,7 +83,7 @@ func (w *Wallet) IsDIDExist(did string) bool { return true } -func (w *Wallet) AddDIDPeerMap(did string, peerID string) error { +func (w *Wallet) AddDIDPeerMap(did string, peerID string, didType int) error { lastChar := string(did[len(did)-1]) var dm DIDPeerMap err := w.s.Read(DIDStorage, &dm, "did=?", did) @@ -94,12 +95,17 @@ func (w *Wallet) AddDIDPeerMap(did string, peerID string) error { dm.DID = did dm.PeerID = peerID dm.DIDLastChar = lastChar + dm.DIDType = &didType return w.s.Write(DIDPeerStorage, &dm) } if dm.PeerID != peerID { dm.PeerID = peerID return w.s.Update(DIDPeerStorage, &dm, "did=?", did) } + if dm.DIDType == nil { + dm.DIDType = &didType + return w.s.Update(DIDPeerStorage, &dm, "did=?", did) + } return nil } @@ -130,3 +136,36 @@ func (w *Wallet) GetPeerID(did string) string { } return dm.PeerID } + +// Fetches did type of the given did from PeerDIDTable +func (w *Wallet) GetPeerDIDType(did string) (int, error) { + var dm DIDPeerMap + err := w.s.Read(DIDPeerStorage, &dm, "did=?", did) + if err != nil { + w.log.Error("couldn't fetch did type from peer did table") + return -1, err + } + if dm.DIDType == nil { + return -1, nil + } + return *dm.DIDType, nil +} + +// Updates did type of the given did in PeerDIDTable +func (w *Wallet) UpdatePeerDIDType(did string, didtype int) (bool, error) { + var dm DIDPeerMap + err := w.s.Read(DIDPeerStorage, &dm, "did=?", did) + if err != nil { + w.log.Error("couldn't read from peer did table") + return false, err + } + + dm.DIDType = &didtype + + err1 := w.s.Update(DIDPeerStorage, &dm, "did=?", did) + if err1 != nil { + w.log.Error("couldn't update did type in peer did table") + return false, err1 + } + return true, nil +} diff --git a/core/wallet/token.go b/core/wallet/token.go index 09249119..7bfad11b 100644 --- a/core/wallet/token.go +++ b/core/wallet/token.go @@ -575,3 +575,21 @@ func (w *Wallet) ReleaseAllLockedTokens() error { } return nil } + +func (w *Wallet) UnlockLockedTokens(did string, tokenList []string) error { + for _, tid := range tokenList { + var t Token + err := w.s.Read(TokenStorage, &t, "did=? AND token_id=?", did, tid) + if err != nil { + w.log.Error("Failed to update token status", "err", err) + return err + } + t.TokenStatus = TokenIsFree + err = w.s.Update(TokenStorage, &t, "did=? AND token_id=?", did, tid) + if err != nil { + w.log.Error("Failed to update token status", "err", err) + return err + } + } + return nil +} diff --git a/core/wallet/token_test.go b/core/wallet/token_test.go index 237ff76b..1fd7b90c 100644 --- a/core/wallet/token_test.go +++ b/core/wallet/token_test.go @@ -49,7 +49,7 @@ func (d *DIDDummy) Sign(hash string) ([]byte, []byte, error) { } // Sign will verifyt he signature -func (d *DIDDummy) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +func (d *DIDDummy) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { // read senderDID _, pubKeyByte, err := crypto.DecodeKeyPair("", nil, d.pubKey) diff --git a/crypto/bip39.go b/crypto/bip39.go new file mode 100644 index 00000000..2b4af1b3 --- /dev/null +++ b/crypto/bip39.go @@ -0,0 +1,158 @@ +package crypto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "encoding/pem" + "fmt" + + "github.com/tyler-smith/go-bip32" + "github.com/tyler-smith/go-bip39" + + secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +const ( + BIP39 = iota +) + +// Generate child private and public keys for BIP39 masterkey and given path +// The child private key is regenerated on demand from master key hence never stored +// The child public key need to shared with other peers for verification +// Make sure the path of child is also stored along with public key +func BIPGenerateChild(masterKey string, childPath int, pwd string) ([]byte, []byte, error) { + masterKeybip32, err := bip32.B58Deserialize(masterKey) + if err != nil { + return nil, nil, err + } + privateKey, err := masterKeybip32.NewChildKey(uint32(childPath)) + if err != nil { + return nil, nil, err + } + + privKey := secp256k1.PrivKeyFromBytes(privateKey.Key) + privkeybyte := privKey.Serialize() + + pubkeybyte := privKey.PubKey().SerializeUncompressed() + var pemEncPriv []byte + if pwd != "" { + encBlock, err := Seal(pwd, privkeybyte) + if err != nil { + return nil, nil, err + } + _, err = UnSeal(pwd, encBlock) + if err != nil { + return nil, nil, err + } + pemEncPriv = pem.EncodeToMemory(&pem.Block{Type: "ENCRYPTED PRIVATE KEY", Bytes: encBlock}) + } else { + pemEncPriv = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privkeybyte}) + } + pemEncPub := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubkeybyte}) + + return pemEncPriv, pemEncPub, nil + +} + +// GenerateKeyPair will generate key pair based on the configuration +func DecodeBIPKeyPair(pwd string, privKey []byte, pubKey []byte) ([]byte, []byte, error) { + var cryptoPrivKey []byte + var cryptoPubKey []byte + if privKey != nil { + pemBlock, _ := pem.Decode(privKey) + if pemBlock == nil { + return nil, nil, fmt.Errorf("invalid private key") + } + if pemBlock.Type == "ENCRYPTED PRIVATE KEY" { + if pwd == "" { + return nil, nil, fmt.Errorf("key is encrypted need password to decrypt") + } + decData, err := UnSeal(pwd, pemBlock.Bytes) + if err != nil { + return nil, nil, fmt.Errorf("key is invalid or password is wrong") + } + cryptoPrivKey = decData + } else { + cryptoPrivKey = pemBlock.Bytes + } + } + if pubKey != nil { + pemBlock, _ := pem.Decode(pubKey) + if pemBlock == nil { + return nil, nil, fmt.Errorf("invalid public key") + } + cryptoPubKey = pemBlock.Bytes + } + + return cryptoPrivKey, cryptoPubKey, nil +} + +// Generate BIPMasterKey from Mnemonic and user provided password +// Useful in key recovery / device migration through mnemonics +func BIPGenerateMasterKeyFromMnemonic(mnemonic string, pwd string) ([]byte, error) { + var masterkeySeralise string + seed := bip39.NewSeed(mnemonic, pwd) + masterKey, _ := bip32.NewMasterKey(seed) + masterkeySeralise = masterKey.B58Serialize() + return []byte(masterkeySeralise), nil +} + +// Generate a random BIP mnemonic in rubix +func BIPGenerateMnemonic() string { + entropy, _ := bip39.NewEntropy(256) + mnemonic, _ := bip39.NewMnemonic(entropy) + return mnemonic +} + +// Generate a Bip32 HD wallet MasteKey for the mnemonic and a user provided randomness +// here we are reusing the password used for sealing masterkey as source of randomness also +func BIPGenerateMasterKey(cfg *CryptoConfig) ([]byte, error) { + var pemEncPriv []byte + var err error + if cfg.Alg == 0 { + mnemonic := BIPGenerateMnemonic() + pemEncPriv, err = BIPGenerateMasterKeyFromMnemonic(mnemonic, cfg.Pwd) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("unsupported algorithm") + } + return pemEncPriv, nil +} + +// Decode the master public key +func BIPDecodeMasterKey(pwd string, privKey []byte) ([]byte, error) { + var cryptoPrivKey []byte + var err error + if privKey != nil { + pemBlock, _ := pem.Decode(privKey) + if pemBlock == nil { + return nil, fmt.Errorf("invalid private key") + } + if pemBlock.Type == "ENCRYPTED PRIVATE KEY" { + if pwd == "" { + return nil, fmt.Errorf("key is encrypted need password to decrypt") + } + cryptoPrivKey, err = UnSeal(pwd, pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("key is invalid or password is wrong") + } + } + } + return cryptoPrivKey, nil +} + +func BIPSign(priv PrivateKey, data []byte) ([]byte, error) { + return priv.(crypto.Signer).Sign(rand.Reader, data, crypto.SHA256) +} + +func BIPVerify(pub PublicKey, data []byte, sig []byte) bool { + switch pub.(type) { + case *ecdsa.PublicKey: + return ecdsa.VerifyASN1(pub.(*ecdsa.PublicKey), data, sig) + default: + return false + } +} diff --git a/crypto/bip39_test.go b/crypto/bip39_test.go new file mode 100644 index 00000000..2f2456db --- /dev/null +++ b/crypto/bip39_test.go @@ -0,0 +1,49 @@ +package crypto + +import ( + "crypto/rand" + "testing" + + secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +func BIPtest(t *testing.T, mnemonic string, pwd string) { + + masterKey, err := BIPGenerateMasterKeyFromMnemonic(mnemonic, pwd) + if err != nil { + t.Fatal("failed to generate key pair", "err", err) + } + + priv, pub, err := BIPGenerateChild(string(masterKey), 0, pwd) + if err != nil { + t.Fatal("failed to generate child", "err", err) + } + + privkey, pubkey, err := DecodeBIPKeyPair(pwd, priv, pub) + if err != nil { + t.Fatal("failed to decode key pair", "err", err) + } + + data, err := GetRandBytes(rand.Reader, 20) + if err != nil { + t.Fatal("failed to generate random number", "err", err) + } + + privkeyback := secp256k1.PrivKeyFromBytes(privkey) + privKeySer := privkeyback.ToECDSA() + pubkeyback, _ := secp256k1.ParsePubKey(pubkey) + pubKeySer := pubkeyback.ToECDSA() + + sig, err := BIPSign(privKeySer, data) + if err != nil { + t.Fatal("failed to do signature", "err", err) + } + + if !BIPVerify(pubKeySer, data, sig) { + t.Fatal("failed to do verify signature", "err", err) + } +} +func TestBIPKeyGeneration(t *testing.T) { + BIPtest(t, "cup symbol flee find decline market tube border artist clever make plastic unfold chaos float artwork sustain suspect risk process fox decrease west seven", "test") + BIPtest(t, "cub symbol flee find decline market tube border artist clever make plastic unfold chaos float artwork sustain suspect risk process fox decrease west seven", "test") +} diff --git a/crypto/crypto.go b/crypto/crypto.go index dead956e..5bcb6e12 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -6,9 +6,13 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "crypto/sha512" "crypto/x509" "encoding/pem" "fmt" + + "filippo.io/keygen" + "golang.org/x/crypto/hkdf" ) // PublicKey represents a public key using an unspecified algorithm. @@ -23,6 +27,7 @@ type CryptoAlgType int const ( RSA2048 CryptoAlgType = iota ECDSAP256 + ECDSADET ) // CryptoConfig is configuration for the crypto @@ -43,6 +48,10 @@ func GenerateKeyPair(cfg *CryptoConfig) ([]byte, []byte, error) { case ECDSAP256: privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) pubKey = &privKey.(*ecdsa.PrivateKey).PublicKey + case ECDSADET: + r := hkdf.New(sha512.New, []byte(cfg.Pwd), nil, nil) + privKey, _ = keygen.ECDSALegacy(elliptic.P256(), r) + pubKey = &privKey.(*ecdsa.PrivateKey).PublicKey default: return nil, nil, fmt.Errorf("unsupported algorithm") } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 968e3e7e..2f3d3170 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -32,5 +32,6 @@ func testKeyGeneration(t *testing.T, alg CryptoAlgType, pwd string) { func TestKeyGeneration(t *testing.T) { testKeyGeneration(t, ECDSAP256, "") + testKeyGeneration(t, ECDSADET, "test") testKeyGeneration(t, ECDSAP256, "TestPassword") } diff --git a/did/basic.go b/did/basic.go index d3f83290..51aa080a 100644 --- a/did/basic.go +++ b/did/basic.go @@ -63,6 +63,10 @@ func (d *DIDBasic) GetDID() string { return d.did } +func (d *DIDBasic) GetSignType() int { + return NlssVersion +} + // Sign will return the singature of the DID func (d *DIDBasic) Sign(hash string) ([]byte, []byte, error) { byteImg, err := util.GetPNGImagePixels(d.dir + PvtShareFileName) @@ -107,7 +111,7 @@ func (d *DIDBasic) Sign(hash string) ([]byte, []byte, error) { } // Sign will verifyt he signature -func (d *DIDBasic) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +func (d *DIDBasic) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { // read senderDID didImg, err := util.GetPNGImagePixels(d.dir + DIDImgFileName) if err != nil { @@ -154,7 +158,7 @@ func (d *DIDBasic) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bo } hashPvtSign := util.HexToStr(util.CalculateHash([]byte(pSig), "SHA3-256")) if !crypto.Verify(pubKeyByte, []byte(hashPvtSign), pvtKeySIg) { - return false, fmt.Errorf("failed to verify private key singature") + return false, fmt.Errorf("failed to verify nlss private key singature") } return true, nil } diff --git a/did/child.go b/did/child.go index 229a8afd..e97da41d 100644 --- a/did/child.go +++ b/did/child.go @@ -64,6 +64,10 @@ func (d *DIDChild) GetDID() string { return d.did } +func (d *DIDChild) GetSignType() int { + return NlssVersion +} + // Sign will return the singature of the DID func (d *DIDChild) Sign(hash string) ([]byte, []byte, error) { @@ -113,7 +117,7 @@ func (d *DIDChild) Sign(hash string) ([]byte, []byte, error) { } // Sign will verifyt he signature -func (d *DIDChild) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +func (d *DIDChild) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { rb, err := ioutil.ReadFile(d.dir + MasterDIDFileName) if err != nil { return false, err diff --git a/did/did.go b/did/did.go index 432dd889..7edf5ba7 100644 --- a/did/did.go +++ b/did/did.go @@ -7,10 +7,12 @@ import ( "fmt" "image" "io" + "io/ioutil" "os" "path/filepath" "time" + secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" ipfsnode "github.com/ipfs/go-ipfs-api" files "github.com/ipfs/go-ipfs-files" "github.com/rubixchain/rubixgoplatform/crypto" @@ -38,8 +40,9 @@ type DID struct { type DIDCrypto interface { GetDID() string + GetSignType() int Sign(hash string) ([]byte, []byte, error) - Verify(hash string, didSig []byte, pvtSig []byte) (bool, error) + NlssVerify(hash string, didSig []byte, pvtSig []byte) (bool, error) PvtSign(hash []byte) ([]byte, error) PvtVerify(hash []byte, sign []byte) (bool, error) } @@ -69,6 +72,95 @@ func (d *DID) CreateDID(didCreate *DIDCreate) (string, error) { return "", err } + //In lite mode, did is simply the SHA-256 hash of the public key + if didCreate.Type == LiteDIDMode { + if didCreate.PrivPWD == "" { + d.log.Error("password required for creating", "err", err) + return "", err + } + + _mnemonic, err := os.ReadFile(didCreate.MnemonicFile) + if err != nil { + d.log.Debug("failed to read mnemonic file", "err", err) + } + var mnemonic string + if _mnemonic == nil { + mnemonic = crypto.BIPGenerateMnemonic() + } else { + mnemonic = string(_mnemonic) + } + + masterKey, err := crypto.BIPGenerateMasterKeyFromMnemonic(mnemonic, didCreate.PrivPWD) + if err != nil { + d.log.Error("failed to create keypair", "err", err) + } + + //generating private and public key pair + pvtKey, pubKey, err := crypto.BIPGenerateChild(string(masterKey), didCreate.ChildPath, didCreate.PrivPWD) + if err != nil { + d.log.Error("failed to create child", "err", err) + } + + err = util.FileWrite(dirName+"/private/"+MnemonicFileName, []byte(mnemonic)) + if err != nil { + d.log.Error("failed to write mnemonic file", "err", err) + return "", err + } + + err = util.FileWrite(dirName+"/private/"+PvtKeyFileName, pvtKey) + if err != nil { + return "", err + } + + err = util.FileWrite(dirName+"/public/"+PubKeyFileName, pubKey) + if err != nil { + return "", err + } + + privKeyTest, err := ioutil.ReadFile(dirName + "/private/" + PvtKeyFileName) + if err != nil { + return "", err + } + + Privkey, _, err := crypto.DecodeBIPKeyPair(didCreate.PrivPWD, privKeyTest, nil) + if err != nil { + return "", err + } + + privkeyback := secp256k1.PrivKeyFromBytes(Privkey) + privKeySer := privkeyback.ToECDSA() + pvtKeySign, err := crypto.BIPSign(privKeySer, []byte("test")) + if err != nil { + return "", err + } + pubKeyTest, err := ioutil.ReadFile(dirName + "/public/" + PubKeyFileName) + if err != nil { + return "", err + } + + _, pubKeyByte, err := crypto.DecodeBIPKeyPair("", nil, pubKeyTest) + if err != nil { + return "", err + } + + pubkeyback, _ := secp256k1.ParsePubKey(pubKeyByte) + pubKeySer := pubkeyback.ToECDSA() + if !crypto.BIPVerify(pubKeySer, []byte("test"), pvtKeySign) { + return "", fmt.Errorf("failed to verify private key singature") + } else { + fmt.Println(" BIP sign tested successfully") + } + + if didCreate.QuorumPWD == "" { + if didCreate.PrivPWD != "" { + didCreate.QuorumPWD = didCreate.PrivPWD + } else { + didCreate.QuorumPWD = DefaultPWD + } + } + + } + if didCreate.Type == BasicDIDMode || didCreate.Type == StandardDIDMode { f, err := os.Open(didCreate.ImgFile) if err != nil { @@ -157,20 +249,17 @@ func (d *DID) CreateDID(didCreate *DIDCreate) (string, error) { d.log.Error("failed to create keypair", "err", err) return "", err } + err = util.FileWrite(dirName+"/private/"+PvtKeyFileName, pvtKey) if err != nil { return "", err } + err = util.FileWrite(dirName+"/public/"+PubKeyFileName, pubKey) if err != nil { return "", err } - } else { - _, err := util.Filecopy(didCreate.PubKeyFile, dirName+"/public/"+PubKeyFileName) - if err != nil { - d.log.Error("failed to copy pub key", "err", err) - return "", err - } + } if didCreate.Type == ChildDIDMode { @@ -181,7 +270,7 @@ func (d *DID) CreateDID(didCreate *DIDCreate) (string, error) { if err != nil { return "", err } - } else { + } else if didCreate.Type != LiteDIDMode { if didCreate.QuorumPWD == "" { if didCreate.PrivPWD != "" { didCreate.QuorumPWD = didCreate.PrivPWD @@ -205,6 +294,7 @@ func (d *DID) CreateDID(didCreate *DIDCreate) (string, error) { } } + //passing the diroctory of public key file to add it to ipfs and exctract the hash did, err := d.getDirHash(dirName + "/public/") if err != nil { return "", err @@ -252,21 +342,24 @@ func (d *DID) MigrateDID(didCreate *DIDCreate) (string, error) { return "", err } - _, err = util.Filecopy(didCreate.DIDImgFileName, dirName+"/public/"+DIDImgFileName) - if err != nil { - d.log.Error("failed to copy did image", "err", err) - return "", err - } - _, err = util.Filecopy(didCreate.PubImgFile, dirName+"/public/"+PubShareFileName) - if err != nil { - d.log.Error("failed to copy public share", "err", err) - return "", err - } - _, err = util.Filecopy(didCreate.PrivImgFile, dirName+"/private/"+PvtShareFileName) - if err != nil { - d.log.Error("failed to copy private share key", "err", err) - return "", err + if didCreate.Type != LiteDIDMode { + _, err = util.Filecopy(didCreate.DIDImgFileName, dirName+"/public/"+DIDImgFileName) + if err != nil { + d.log.Error("failed to copy did image", "err", err) + return "", err + } + _, err = util.Filecopy(didCreate.PubImgFile, dirName+"/public/"+PubShareFileName) + if err != nil { + d.log.Error("failed to copy public share", "err", err) + return "", err + } + _, err = util.Filecopy(didCreate.PrivImgFile, dirName+"/private/"+PvtShareFileName) + if err != nil { + d.log.Error("failed to copy private share key", "err", err) + return "", err + } } + if didCreate.Type == BasicDIDMode { if didCreate.PrivKeyFile == "" || didCreate.PubKeyFile == "" { if didCreate.PrivPWD == "" { @@ -313,28 +406,31 @@ func (d *DID) MigrateDID(didCreate *DIDCreate) (string, error) { didCreate.QuorumPWD = DefaultPWD } } - if didCreate.QuorumPrivKeyFile == "" || didCreate.QuorumPubKeyFile == "" { - pvtKey, pubKey, err := crypto.GenerateKeyPair(&crypto.CryptoConfig{Alg: crypto.ECDSAP256, Pwd: didCreate.QuorumPWD}) - if err != nil { - d.log.Error("failed to create keypair", "err", err) - return "", err - } - err = util.FileWrite(dirName+"/private/"+QuorumPvtKeyFileName, pvtKey) - if err != nil { - return "", err - } - err = util.FileWrite(dirName+"/public/"+QuorumPubKeyFileName, pubKey) - if err != nil { - return "", err - } - } else { - _, err = util.Filecopy(didCreate.QuorumPrivKeyFile, dirName+"/private/"+QuorumPvtKeyFileName) - if err != nil { - return "", err - } - _, err = util.Filecopy(didCreate.QuorumPubKeyFile, dirName+"/public/"+QuorumPubKeyFileName) - if err != nil { - return "", err + + if didCreate.Type != LiteDIDMode { + if didCreate.QuorumPrivKeyFile == "" || didCreate.QuorumPubKeyFile == "" { + pvtKey, pubKey, err := crypto.GenerateKeyPair(&crypto.CryptoConfig{Alg: crypto.ECDSAP256, Pwd: didCreate.QuorumPWD}) + if err != nil { + d.log.Error("failed to create keypair", "err", err) + return "", err + } + err = util.FileWrite(dirName+"/private/"+QuorumPvtKeyFileName, pvtKey) + if err != nil { + return "", err + } + err = util.FileWrite(dirName+"/public/"+QuorumPubKeyFileName, pubKey) + if err != nil { + return "", err + } + } else { + _, err = util.Filecopy(didCreate.QuorumPrivKeyFile, dirName+"/private/"+QuorumPvtKeyFileName) + if err != nil { + return "", err + } + _, err = util.Filecopy(didCreate.QuorumPubKeyFile, dirName+"/public/"+QuorumPubKeyFileName) + if err != nil { + return "", err + } } } @@ -373,21 +469,29 @@ type object struct { Hash string } +// Calculate the hash of a directory using IPFS func (d *DID) getDirHash(dir string) (string, error) { + // Get information about the directory stat, err := os.Lstat(dir) if err != nil { return "", err } + // Create a new SerialFile using the directory information sf, err := files.NewSerialFile(dir, false, stat) if err != nil { return "", err } defer sf.Close() + + // Create a new SliceDirectory with the SerialFile slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry(filepath.Base(dir), sf)}) defer slf.Close() + + // Create a MultiFileReader with the SliceDirectory reader := files.NewMultiFileReader(slf, true) + // Send a request to IPFS to add the directory resp, err := d.ipfs.Request("add"). Option("recursive", true). Option("cid-version", 1). @@ -400,10 +504,13 @@ func (d *DID) getDirHash(dir string) (string, error) { defer resp.Close() + // Check for errors in the response if resp.Error != nil { return "", resp.Error } defer resp.Output.Close() + + // Decode the JSON response and extract the hash dec := json.NewDecoder(resp.Output) var final string for { @@ -418,6 +525,7 @@ func (d *DID) getDirHash(dir string) (string, error) { final = out.Hash } + // Check if the final hash is empty if final == "" { return "", errors.New("no results received") } diff --git a/did/dummy.go b/did/dummy.go index be1b1667..4b098ac0 100644 --- a/did/dummy.go +++ b/did/dummy.go @@ -40,7 +40,7 @@ func (d *DIDDummy) Sign(hash string) ([]byte, []byte, error) { } // Sign will verifyt he signature -func (d *DIDDummy) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +func (d *DIDDummy) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { // read senderDID _, pubKeyByte, err := crypto.DecodeKeyPair("", nil, d.pubKey) diff --git a/did/lite.go b/did/lite.go new file mode 100644 index 00000000..beccd88a --- /dev/null +++ b/did/lite.go @@ -0,0 +1,181 @@ +package did + +import ( + "bytes" + "fmt" + "io/ioutil" + "time" + + secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/rubixchain/rubixgoplatform/crypto" + "github.com/rubixchain/rubixgoplatform/nlss" + "github.com/rubixchain/rubixgoplatform/util" +) + +// DIDLite will handle Light DID +type DIDLite struct { + did string + dir string + ch *DIDChan + pwd string +} + +// InitDIDLite will return the Light did handle +func InitDIDLite(did string, baseDir string, ch *DIDChan) *DIDLite { + return &DIDLite{did: did, dir: util.SanitizeDirPath(baseDir) + did + "/", ch: ch} +} + +func InitDIDLiteWithPassword(did string, baseDir string, pwd string) *DIDLite { + return &DIDLite{did: did, dir: util.SanitizeDirPath(baseDir) + did + "/", pwd: pwd} +} + +func (d *DIDLite) getPassword() (string, error) { + if d.pwd != "" { + return d.pwd, nil + } + if d.ch == nil || d.ch.InChan == nil || d.ch.OutChan == nil { + return "", fmt.Errorf("Invalid configuration") + } + sr := &SignResponse{ + Status: true, + Message: "Password needed", + Result: SignReqData{ + ID: d.ch.ID, + Mode: LiteDIDMode, + }, + } + d.ch.OutChan <- sr + var ch interface{} + select { + case ch = <-d.ch.InChan: + case <-time.After(d.ch.Timeout): + return "", fmt.Errorf("Timeout, failed to get password") + } + + srd, ok := ch.(SignRespData) + if !ok { + return "", fmt.Errorf("Invalid data received on the channel") + } + d.pwd = srd.Password + return d.pwd, nil +} + +func (d *DIDLite) GetDID() string { + return d.did +} + +// When the did creation and signing is done in Light mode, +// this function returns the sign version as BIPVersion = 0 +func (d *DIDLite) GetSignType() int { + return BIPVersion +} + +// PKI based sign in lite mode +// In lite mode, the sign function returns only the private signature, unlike the basic mode +func (d *DIDLite) Sign(hash string) ([]byte, []byte, error) { + pvtKeySign, err := d.PvtSign([]byte(hash)) + bs := []byte{} + + return bs, pvtKeySign, err +} + +// verify nlss based signatures +func (d *DIDLite) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { + //read senderDID + didImg, err := util.GetPNGImagePixels(d.dir + DIDImgFileName) + if err != nil { + return false, err + } + pubImg, err := util.GetPNGImagePixels(d.dir + PubShareFileName) + + if err != nil { + return false, err + } + + pSig := util.BytesToBitstream(pvtShareSig) + + ps := util.StringToIntArray(pSig) + + didBin := util.ByteArraytoIntArray(didImg) + pubBin := util.ByteArraytoIntArray(pubImg) + pubPos := util.RandomPositions("verifier", hash, 32, ps) + pubPosInt := util.GetPrivatePositions(pubPos.PosForSign, pubBin) + pubStr := util.IntArraytoStr(pubPosInt) + orgPos := make([]int, len(pubPos.OriginalPos)) + for i := range pubPos.OriginalPos { + orgPos[i] = pubPos.OriginalPos[i] / 8 + } + didPosInt := util.GetPrivatePositions(orgPos, didBin) + didStr := util.IntArraytoStr(didPosInt) + cb := nlss.Combine2Shares(nlss.ConvertBitString(pSig), nlss.ConvertBitString(pubStr)) + + db := nlss.ConvertBitString(didStr) + + if !bytes.Equal(cb, db) { + return false, fmt.Errorf("failed to verify") + } + + //create a signature using the private key + //1. read and extrqct the private key + pubKey, err := ioutil.ReadFile(d.dir + PubKeyFileName) + if err != nil { + return false, err + } + + _, pubKeyByte, err := crypto.DecodeKeyPair("", nil, pubKey) + if err != nil { + return false, err + } + + hashPvtSign := util.HexToStr(util.CalculateHash([]byte(pSig), "SHA3-256")) + if !crypto.Verify(pubKeyByte, []byte(hashPvtSign), pvtKeySIg) { + return false, fmt.Errorf("failed to verify private key singature") + } + return true, nil + +} + +func (d *DIDLite) PvtSign(hash []byte) ([]byte, error) { + privKey, err := ioutil.ReadFile(d.dir + PvtKeyFileName) + if err != nil { + return nil, err + } + + pwd, err := d.getPassword() + if err != nil { + return nil, err + } + + Privatekey, _, err := crypto.DecodeBIPKeyPair(pwd, privKey, nil) + if err != nil { + return nil, err + } + + privkeyback := secp256k1.PrivKeyFromBytes(Privatekey) + privKeySer := privkeyback.ToECDSA() + pvtKeySign, err := crypto.BIPSign(privKeySer, hash) + if err != nil { + return nil, err + } + return pvtKeySign, nil +} + +// Verify PKI based signature +func (d *DIDLite) PvtVerify(hash []byte, sign []byte) (bool, error) { + pubKey, err := ioutil.ReadFile(d.dir + PubKeyFileName) + if err != nil { + return false, err + } + + _, pubKeyByte, err := crypto.DecodeBIPKeyPair("", nil, pubKey) + if err != nil { + return false, err + } + + pubkeyback, _ := secp256k1.ParsePubKey(pubKeyByte) + pubKeySer := pubkeyback.ToECDSA() + if !crypto.BIPVerify(pubKeySer, hash, sign) { + return false, fmt.Errorf("failed to verify private key singature") + } + return true, nil +} diff --git a/did/model.go b/did/model.go index 1ff71eb0..bf18b21f 100644 --- a/did/model.go +++ b/did/model.go @@ -5,6 +5,12 @@ const ( StandardDIDMode WalletDIDMode ChildDIDMode + LiteDIDMode +) + +const ( + BIPVersion int = iota + NlssVersion ) const ( @@ -17,6 +23,7 @@ const ( PubKeyFileName string = "pubKey.pem" QuorumPvtKeyFileName string = "quorumPrivKey.pem" QuorumPubKeyFileName string = "quorumPubKey.pem" + MnemonicFileName string = "mnemonic.txt" ) const ( @@ -40,6 +47,8 @@ type DIDCreate struct { PrivKeyFile string `json:"priv_key_file"` QuorumPubKeyFile string `json:"quorum_pub_key_file"` QuorumPrivKeyFile string `json:"quorum_priv_key_file"` + MnemonicFile string `json:"mnemonic_file"` + ChildPath int `json:"childPath"` } type DIDSignature struct { diff --git a/did/quorum.go b/did/quorum.go index 3542f5e7..d1955d8c 100644 --- a/did/quorum.go +++ b/did/quorum.go @@ -48,6 +48,10 @@ func (d *DIDQuorum) GetDID() string { return d.did } +func (d *DIDQuorum) GetSignType() int { + return NlssVersion +} + // Sign will return the singature of the DID func (d *DIDQuorum) Sign(hash string) ([]byte, []byte, error) { if d.privKey == nil { @@ -67,6 +71,7 @@ func (d *DIDQuorum) Sign(hash string) ([]byte, []byte, error) { finalPos := randPosObject.PosForSign pvtPos := util.GetPrivatePositions(finalPos, ps) pvtPosStr := util.IntArraytoStr(pvtPos) + hashPvtSign := util.HexToStr(util.CalculateHash([]byte(pvtPosStr), "SHA3-256")) pvtKeySign, err := crypto.Sign(d.privKey, []byte(hashPvtSign)) if err != nil { @@ -79,8 +84,8 @@ func (d *DIDQuorum) Sign(hash string) ([]byte, []byte, error) { return bs, pvtKeySign, err } -// Sign will verifyt he signature -func (d *DIDQuorum) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +// verify the quorum's nlss based signature +func (d *DIDQuorum) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { // read senderDID didImg, err := util.GetPNGImagePixels(d.dir + DIDImgFileName) if err != nil { @@ -116,7 +121,7 @@ func (d *DIDQuorum) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (b } hashPvtSign := util.HexToStr(util.CalculateHash([]byte(pSig), "SHA3-256")) if !crypto.Verify(d.pubKey, []byte(hashPvtSign), pvtKeySIg) { - return false, fmt.Errorf("failed to verify private key singature") + return false, fmt.Errorf("failed to verify nlss private key singature") } return true, nil } diff --git a/did/quorum_lite.go b/did/quorum_lite.go new file mode 100644 index 00000000..a56f7172 --- /dev/null +++ b/did/quorum_lite.go @@ -0,0 +1,160 @@ +package did + +import ( + "bytes" + "fmt" + "io/ioutil" + + secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/rubixchain/rubixgoplatform/crypto" + "github.com/rubixchain/rubixgoplatform/nlss" + "github.com/rubixchain/rubixgoplatform/util" +) + +// DIDQuorum_Lt will handle lite DID +type DIDQuorum_Lt struct { + did string + dir string + pwd string + privKey crypto.PrivateKey + pubKey crypto.PublicKey +} + +// InitDIDQuorum_Lt will return the Quorum did handle in lite mode +func InitDIDQuorum_Lt(did string, baseDir string, pwd string) *DIDQuorum_Lt { + d := &DIDQuorum_Lt{did: did, dir: util.SanitizeDirPath(baseDir) + did + "/", pwd: pwd} + if d.pwd != "" { + privKey, err := ioutil.ReadFile(d.dir + PvtKeyFileName) + if err != nil { + return nil + } + d.privKey, _, err = crypto.DecodeBIPKeyPair(d.pwd, privKey, nil) + if err != nil { + return nil + } + } + + pubKey, err := ioutil.ReadFile(d.dir + PubKeyFileName) + if err != nil { + return nil + } + _, d.pubKey, err = crypto.DecodeBIPKeyPair("", nil, pubKey) + if err != nil { + return nil + } + return d +} + +func (d *DIDQuorum_Lt) GetDID() string { + return d.did +} + +func (d *DIDQuorum_Lt) GetSignType() int { + return BIPVersion +} + +// Sign will return the singature of the DID +func (d *DIDQuorum_Lt) Sign(hash string) ([]byte, []byte, error) { + pvtKeySign, err := d.PvtSign([]byte(hash)) + // byteImg, err := util.GetPNGImagePixels(d.dir + PvtShareFileName) + + if err != nil { + fmt.Println(err) + return nil, nil, err + } + + bs := []byte{} + return bs, pvtKeySign, err +} + +// verify the quorum's nlss based signature +func (d *DIDQuorum_Lt) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { + // read senderDID + didImg, err := util.GetPNGImagePixels(d.dir + DIDImgFileName) + if err != nil { + return false, err + } + pubImg, err := util.GetPNGImagePixels(d.dir + PubShareFileName) + + if err != nil { + return false, err + } + + pSig := util.BytesToBitstream(pvtShareSig) + + ps := util.StringToIntArray(pSig) + + didBin := util.ByteArraytoIntArray(didImg) + pubBin := util.ByteArraytoIntArray(pubImg) + pubPos := util.RandomPositions("verifier", hash, 32, ps) + pubPosInt := util.GetPrivatePositions(pubPos.PosForSign, pubBin) + pubStr := util.IntArraytoStr(pubPosInt) + orgPos := make([]int, len(pubPos.OriginalPos)) + for i := range pubPos.OriginalPos { + orgPos[i] = pubPos.OriginalPos[i] / 8 + } + didPosInt := util.GetPrivatePositions(orgPos, didBin) + didStr := util.IntArraytoStr(didPosInt) + cb := nlss.Combine2Shares(nlss.ConvertBitString(pSig), nlss.ConvertBitString(pubStr)) + + db := nlss.ConvertBitString(didStr) + + if !bytes.Equal(cb, db) { + return false, fmt.Errorf("failed to verify") + } + + hashPvtSign := util.HexToStr(util.CalculateHash([]byte(pSig), "SHA3-256")) + + pubKey, err := ioutil.ReadFile(d.dir + PubKeyFileName) + if err != nil { + return false, err + } + + _, pubKeyByte, err := crypto.DecodeKeyPair("", nil, pubKey) + if err != nil { + return false, err + } + + if !crypto.Verify(pubKeyByte, []byte(hashPvtSign), pvtKeySIg) { + return false, fmt.Errorf("failed to verify private key singature") + } + return true, nil +} +func (d *DIDQuorum_Lt) PvtSign(hash []byte) ([]byte, error) { + privKey, err := ioutil.ReadFile(d.dir + PvtKeyFileName) + if err != nil { + return nil, err + } + + Privatekey, _, err := crypto.DecodeBIPKeyPair(d.pwd, privKey, nil) + if err != nil { + return nil, err + } + + privkeyback := secp256k1.PrivKeyFromBytes(Privatekey) + privKeySer := privkeyback.ToECDSA() + pvtKeySign, err := crypto.BIPSign(privKeySer, hash) + if err != nil { + return nil, err + } + return pvtKeySign, nil +} +func (d *DIDQuorum_Lt) PvtVerify(hash []byte, sign []byte) (bool, error) { + pubKey, err := ioutil.ReadFile(d.dir + PubKeyFileName) + if err != nil { + return false, err + } + + _, pubKeyByte, err := crypto.DecodeBIPKeyPair("", nil, pubKey) + if err != nil { + return false, err + } + + pubkeyback, _ := secp256k1.ParsePubKey(pubKeyByte) + pubKeySer := pubkeyback.ToECDSA() + + if !crypto.BIPVerify(pubKeySer, hash, sign) { + return false, fmt.Errorf("failed to verify private key singature") + } + return true, nil +} diff --git a/did/standard.go b/did/standard.go index ecb2466d..d4dbe999 100644 --- a/did/standard.go +++ b/did/standard.go @@ -55,6 +55,10 @@ func (d *DIDStandard) GetDID() string { return d.did } +func (d *DIDStandard) GetSignType() int { + return NlssVersion +} + // Sign will return the singature of the DID func (d *DIDStandard) Sign(hash string) ([]byte, []byte, error) { byteImg, err := util.GetPNGImagePixels(d.dir + PvtShareFileName) @@ -84,7 +88,7 @@ func (d *DIDStandard) Sign(hash string) ([]byte, []byte, error) { } // Sign will verifyt he signature -func (d *DIDStandard) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +func (d *DIDStandard) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { // read senderDID didImg, err := util.GetPNGImagePixels(d.dir + DIDImgFileName) if err != nil { diff --git a/did/wallet.go b/did/wallet.go index 1a07f8c0..77fc3a77 100644 --- a/did/wallet.go +++ b/did/wallet.go @@ -56,6 +56,10 @@ func (d *DIDWallet) GetDID() string { return d.did } +func (d *DIDWallet) GetSignType() int { + return NlssVersion +} + // Sign will return the singature of the DID func (d *DIDWallet) Sign(hash string) ([]byte, []byte, error) { bs, pvtKeySign, err := d.getSignature([]byte(hash), false) @@ -66,7 +70,7 @@ func (d *DIDWallet) Sign(hash string) ([]byte, []byte, error) { } // Sign will verifyt he signature -func (d *DIDWallet) Verify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { +func (d *DIDWallet) NlssVerify(hash string, pvtShareSig []byte, pvtKeySIg []byte) (bool, error) { // read senderDID didImg, err := util.GetPNGImagePixels(d.dir + DIDImgFileName) if err != nil { diff --git a/go.mod b/go.mod index 1c8877bd..7d0ff4ba 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,10 @@ require ( ) require ( + filippo.io/keygen v0.0.0-20230306160926-5201437acf8e // indirect github.com/btcsuite/btcd v0.23.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/denisenkom/go-mssqldb v0.12.3 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fatih/color v1.15.0 @@ -47,6 +50,8 @@ require ( github.com/stretchr/testify v1.8.3 // indirect github.com/swaggo/files v1.0.0 // indirect github.com/swaggo/http-swagger v1.3.4 + github.com/tyler-smith/go-bip32 v1.0.0 + github.com/tyler-smith/go-bip39 v1.1.0 github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b // indirect golang.org/x/net v0.10.0 golang.org/x/tools v0.9.3 // indirect diff --git a/go.sum b/go.sum index f090b70a..37458085 100644 --- a/go.sum +++ b/go.sum @@ -592,6 +592,10 @@ cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vf cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/bigmod v0.0.1 h1:OaEqDr3gEbofpnHbGqZweSL/bLMhy1pb54puiCDeuOA= +filippo.io/bigmod v0.0.1/go.mod h1:KyzqAbH7bRH6MOuOF1TPfUjvLoi0mRF2bIyD2ouRNQI= +filippo.io/keygen v0.0.0-20230306160926-5201437acf8e h1:+xwUCyMiCWKWsI0RowhzB4sngpUdMHgU6lLuWJCX5Dg= +filippo.io/keygen v0.0.0-20230306160926-5201437acf8e/go.mod h1:ZGSiF/b2hd6MRghF/cid0vXw8pXykRTmIu+JSPw/NCQ= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= @@ -606,6 +610,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/EnsurityTechnologies/helper v1.0.0 h1:x6kaCFvqH35/P8eD8Vkx/IUUkbvDqyh4KVwqDJWIfWA= github.com/EnsurityTechnologies/helper v1.0.0/go.mod h1:KESBOBjgzlkimm3dDwr49wjbMXWtaoqrA3SiwhxGDwQ= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -662,6 +670,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 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/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -683,8 +692,13 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 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/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 h1:0XErmfJBiVbl0NvyclGn4jr+1hIylDf5beFi9W0o7Fc= +github.com/decred/dcrd/dcrec/secp256k1 v1.0.4/go.mod h1:00z7mJdugt+GBAzPN1QrDRGCXxyKUiexEHu6ukxEw3k= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= @@ -1085,6 +1099,7 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1106,6 +1121,10 @@ github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE= +github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs= github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b h1:wA3QeTsaAXybLL2kb2cKhCAQTHgYTMwuI8lBlJSv5V8= @@ -1132,6 +1151,7 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1156,6 +1176,7 @@ golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1838,6 +1859,7 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= diff --git a/grpcclient/command.go b/grpcclient/command.go index b52c1a02..92cc2e2b 100644 --- a/grpcclient/command.go +++ b/grpcclient/command.go @@ -114,6 +114,7 @@ type Command struct { grpcAddr string grpcPort int grpcSecure bool + mnemonicFile string } func showVersion() { @@ -181,6 +182,7 @@ func runCommand() { flag.StringVar(&cmd.didImgFile, "didImgFile", did.DIDImgFileName, "DID image") flag.StringVar(&cmd.privImgFile, "privImgFile", did.PvtShareFileName, "DID public share image") flag.StringVar(&cmd.pubImgFile, "pubImgFile", did.PubShareFileName, "DID public share image") + flag.StringVar(&cmd.mnemonicFile, "mnemonicKeyFile", did.MnemonicFileName, "Mnemonic key file") flag.StringVar(&cmd.privKeyFile, "privKeyFile", did.PvtKeyFileName, "Private key file") flag.StringVar(&cmd.pubKeyFile, "pubKeyFile", did.PubKeyFileName, "Public key file") flag.StringVar(&cmd.quorumList, "quorumList", "quorumlist.json", "Quorum list") diff --git a/grpcclient/token.go b/grpcclient/token.go index 37c29126..77370d25 100644 --- a/grpcclient/token.go +++ b/grpcclient/token.go @@ -34,6 +34,8 @@ func (cmd *Command) GenerateRBT() { Mode: br.SignRequest.Mode, } switch int(br.SignRequest.Mode) { + case did.LiteDIDMode: + resp.Password = cmd.privPWD case did.BasicDIDMode: resp.Password = cmd.privPWD case did.ChildDIDMode: diff --git a/grpcserver/did.go b/grpcserver/did.go index 62e3bca2..21606ed0 100644 --- a/grpcserver/did.go +++ b/grpcserver/did.go @@ -84,22 +84,26 @@ func (rn *RubixNative) CreateDID(ctx context.Context, req *protos.CreateDIDReq) return nil, status.Errorf(codes.Internal, "failed to create folder") } defer os.RemoveAll(folderName) - if req.DidImage != "" { - err = createFile(folderName+"/"+did.DIDImgFileName, req.DidImage, true) - if err != nil { - rn.log.Error(err.Error()) - return nil, status.Errorf(codes.Internal, err.Error()) + + if dc.Type != did.LiteDIDMode { + if req.DidImage != "" { + err = createFile(folderName+"/"+did.DIDImgFileName, req.DidImage, true) + if err != nil { + rn.log.Error(err.Error()) + return nil, status.Errorf(codes.Internal, err.Error()) + } + dc.ImgFile = folderName + "/" + did.DIDImgFileName } - dc.ImgFile = folderName + "/" + did.DIDImgFileName - } - if req.PublicShare != "" { - err = createFile(folderName+"/"+did.PubShareFileName, req.PublicShare, true) - if err != nil { - rn.log.Error(err.Error()) - return nil, status.Errorf(codes.Internal, err.Error()) + if req.PublicShare != "" { + err = createFile(folderName+"/"+did.PubShareFileName, req.PublicShare, true) + if err != nil { + rn.log.Error(err.Error()) + return nil, status.Errorf(codes.Internal, err.Error()) + } + dc.PubImgFile = folderName + "/" + did.PubShareFileName } - dc.PubImgFile = folderName + "/" + did.PubShareFileName } + if req.PublicKey != "" { err = createFile(folderName+"/"+did.PubKeyFileName, req.PublicKey, false) if err != nil { diff --git a/server/config.go b/server/config.go index e6b7f959..f4aa3c63 100644 --- a/server/config.go +++ b/server/config.go @@ -112,3 +112,43 @@ func (s *Server) APISetupDB(req *ensweb.Request) *ensweb.Result { } return s.BasicResponse(req, true, "DB setup done successfully", nil) } + +// APIGetAllExplorer will get all explorer URLs from the db +func (s *Server) APIGetAllExplorer(req *ensweb.Request) *ensweb.Result { + links, err := s.c.GetAllExplorer() + if err != nil { + return s.BasicResponse(req, false, "Failed to get explorer urls"+err.Error(), nil) + } + m := model.ExplorerLinks{ + Links: links, + } + return s.BasicResponse(req, true, "Got all the explorer URLs successfully", m) +} + +// APIAddExplorer will add bootstrap peers to the configuration +func (s *Server) APIAddExplorer(req *ensweb.Request) *ensweb.Result { + var m model.ExplorerLinks + err := s.ParseJSON(req, &m) + if err != nil { + return s.BasicResponse(req, false, "invlid input request", nil) + } + err = s.c.AddExplorer(m.Links) + if err != nil { + return s.BasicResponse(req, false, "failed to add explorer, "+err.Error(), nil) + } + return s.BasicResponse(req, true, "explorer added successfully", nil) +} + +// APIRemoveExplorer will remove bootstrap peers from the configuration +func (s *Server) APIRemoveExplorer(req *ensweb.Request) *ensweb.Result { + var m model.ExplorerLinks + err := s.ParseJSON(req, &m) + if err != nil { + return s.BasicResponse(req, false, "invlid input request", nil) + } + err = s.c.RemoveExplorer(m.Links) + if err != nil { + return s.BasicResponse(req, false, "failed to remove explorer, "+err.Error(), nil) + } + return s.BasicResponse(req, true, "explorer removed successfully", nil) +} diff --git a/server/did.go b/server/did.go index 8373b257..777a2835 100644 --- a/server/did.go +++ b/server/did.go @@ -62,18 +62,22 @@ func (s *Server) APICreateDID(req *ensweb.Request) *ensweb.Result { } for _, fileName := range fileNames { - if strings.Contains(fileName, did.ImgFileName) { - didCreate.ImgFile = fileName - } - if strings.Contains(fileName, did.DIDImgFileName) { - didCreate.DIDImgFileName = fileName - } - if strings.Contains(fileName, did.PubShareFileName) { - didCreate.PubImgFile = fileName - } + if strings.Contains(fileName, did.PubKeyFileName) { didCreate.PubKeyFile = fileName } + + if didCreate.Type != did.LiteDIDMode { + if strings.Contains(fileName, did.ImgFileName) { + didCreate.ImgFile = fileName + } + if strings.Contains(fileName, did.DIDImgFileName) { + didCreate.DIDImgFileName = fileName + } + if strings.Contains(fileName, did.PubShareFileName) { + didCreate.PubImgFile = fileName + } + } } if !s.cfg.EnableAuth { didCreate.Dir = DIDRootDir @@ -199,26 +203,30 @@ func (s *Server) APISetupDID(req *ensweb.Request) *ensweb.Result { } for _, fileName := range fileNames { - if strings.Contains(fileName, did.DIDImgFileName) { - didCreate.DIDImgFileName = fileName - } - if strings.Contains(fileName, did.PubShareFileName) { - didCreate.PubImgFile = fileName - } - if strings.Contains(fileName, did.PvtShareFileName) { - didCreate.PrivImgFile = fileName - } + if strings.Contains(fileName, did.PvtKeyFileName) { didCreate.PrivKeyFile = fileName } if strings.Contains(fileName, did.PubKeyFileName) { didCreate.PubKeyFile = fileName } - if strings.Contains(fileName, did.QuorumPvtKeyFileName) { - didCreate.QuorumPrivKeyFile = fileName - } - if strings.Contains(fileName, did.QuorumPubKeyFileName) { - didCreate.QuorumPubKeyFile = fileName + + if didCreate.Type != did.LiteDIDMode { + if strings.Contains(fileName, did.DIDImgFileName) { + didCreate.DIDImgFileName = fileName + } + if strings.Contains(fileName, did.PubShareFileName) { + didCreate.PubImgFile = fileName + } + if strings.Contains(fileName, did.PvtShareFileName) { + didCreate.PrivImgFile = fileName + } + if strings.Contains(fileName, did.QuorumPvtKeyFileName) { + didCreate.QuorumPrivKeyFile = fileName + } + if strings.Contains(fileName, did.QuorumPubKeyFileName) { + didCreate.QuorumPubKeyFile = fileName + } } } dir, ok := s.validateAccess(req) diff --git a/server/node.go b/server/node.go new file mode 100644 index 00000000..37ccf3ed --- /dev/null +++ b/server/node.go @@ -0,0 +1,9 @@ +package server + +import ( + "github.com/rubixchain/rubixgoplatform/wrapper/ensweb" +) + +func (s *Server) APIPeerID(req *ensweb.Request) *ensweb.Result { + return s.BasicResponse(req, true, s.c.GetPeerID(), nil) +} diff --git a/server/server.go b/server/server.go index 4c501c21..b16b6ef1 100644 --- a/server/server.go +++ b/server/server.go @@ -154,9 +154,13 @@ func (s *Server) RegisterRoutes() { s.AddRoute(setup.APIRegisterCallBackURL, "POST", s.AuthHandle(s.APIRegisterCallbackURL, true, s.AuthError, false)) s.AddRoute(setup.APIGetTxnByNode, "GET", s.AuthHandle(s.APIGetTxnByNode, true, s.AuthError, false)) s.AddRoute(setup.APIRemoveTokenChainBlock, "POST", s.AuthHandle(s.APIRemoveTokenChainBlock, true, s.AuthError, false)) + s.AddRoute(setup.APIPeerID, "GET", s.AuthHandle(s.APIPeerID, false, s.AuthError, false)) s.AddRoute(setup.APIReleaseAllLockedTokens, "GET", s.AuthHandle(s.APIReleaseAllLockedTokens, true, s.AuthError, false)) s.AddRoute(setup.APICheckQuorumStatus, "GET", s.AuthHandle(s.APICheckQuorumStatus, false, s.AuthError, false)) + s.AddRoute(setup.APIGetAllExplorer, "GET", s.AuthHandle(s.APIGetAllExplorer, false, s.AuthError, true)) + s.AddRoute(setup.APIAddExplorer, "POST", s.AuthHandle(s.APIAddExplorer, false, s.AuthError, true)) + s.AddRoute(setup.APIRemoveExplorer, "POST", s.AuthHandle(s.APIRemoveExplorer, false, s.AuthError, true)) } func (s *Server) ExitFunc() error { diff --git a/setup/setup.go b/setup/setup.go index 6c9954ee..5aa8b15d 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -61,8 +61,12 @@ const ( APIRegisterCallBackURL string = "/api/register-callback-url" APIGetTxnByNode string = "/api/get-by-node" APIRemoveTokenChainBlock string = "/api/remove-token-chain-block" + APIPeerID string = "/api/get-peer-id" APIReleaseAllLockedTokens string = "/api/release-all-locked-tokens" APICheckQuorumStatus string = "/api/check-quorum-status" + APIGetAllExplorer string = "/api/get-all-explorer" + APIAddExplorer string = "/api/add-explorer" + APIRemoveExplorer string = "/api/remove-explorer" ) // jwt.RegisteredClaims diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..bff954d1 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,82 @@ +# Test Scripts + +The test script does the complete setup by building the rubix node based on the Operatng System, downloads IPFS binary for a specific version (refer `IPFS_KUBO_VERSION` variable in `run.py`) and sets up quorum and non-quorum nodes before proceeding with running all the test cases. + +The test script covers the following RBT Transfer scenarios: + +1. Shuttle Transfer (Success Case) + 1.1 Generate 2 whole RBT for A + 1.2 Transfer 0.5 from A to B + 1.3 Transfer 1.499 from A to B + 1.4 (Transfer 0.25 from B to A) * 4 + 1.5 Transfer 1 RBT from A to B + 1.6 Generate 2 whole RBT for A + 1.7 Transfer 2 RBT from A to B + 1.8 Transfer 0.001 from A to B + +2. Insufficient Balance Transfer (Failure Case) + 2.1 Transferring 100 RBT from A which has zero balance + 2.2 Transferring 100 RBT from B which has insufficient balance + +3. Transferring 0.00000009 RBT from B which is more than allowed decimal places (Failure Case) + +4. Transferring whole, part and mix RBT from NLSS DID to BIP39 DID + 4.1 Transfer 1 RBT from NLSS DID to BIP39 DID + 4.2 Transfer 1.5 RBT from NLSS DID to BIP39 DID + 4.3 Transfer 0.5 RBT from NLSS DID to BIP39 DID + +5. Transferring whole, part and mix RBT from BIP39 DID to NLSS DID + 5.1 Transfer 0.5 RBT from NLSS DID to BIP39 DID + 5.2 Transfer 1.5 RBT from NLSS DID to BIP39 DID + 5.3 Transfer 1 RBT from NLSS DID to BIP39 DID + +## Prerequisites + +- Python 3.10+ ([Install Ref](https://www.python.org/downloads/)) +- tmux for MacOs and Ubuntu based systems ([Install Ref](https://github.com/tmux/tmux/wiki/Installing#binary-packages)) +- `pip` package manger ([Install Ref](https://pip.pypa.io/en/stable/installation/)) +- `requests` package. After installing Python and pip, run `pip install requests` to install this package + +## Running the tests + +To start the test. Please NOTE that it must be run from the `tests` directory only. + +``` +python3 run.py +``` + +## Running tests in Docker + +To run the tests in a Docker Ubuntu environment, run the following: + +1. Build the image +``` +docker build -t rubix_test_image_ubuntu --no-cache -f tests/docker/ubuntu_test.Dockerfile . +``` + +2. Run the container +``` +docker run --rm --name rubix_test_container_ubuntu rubix_test_image_ubuntu +``` + +### Flags + +The test script is equipped with CLI Parser. Following are the flags and their description + +``` +usage: run.py [-h] [--skip_prerequisite | --no-skip_prerequisite] [--run_nodes_only | --no-run_nodes_only] [--skip_adding_quorums | --no-skip_adding_quorums] + [--run_tests_only | --no-run_tests_only] + +CLI to run tests for Rubix + +options: + -h, --help show this help message and exit + --skip_prerequisite, --no-skip_prerequisite + skip prerequisite steps such as installing IPFS and building Rubix + --run_nodes_only, --no-run_nodes_only + only run the rubix nodes and skip the setup + --skip_adding_quorums, --no-skip_adding_quorums + skips adding quorums + --run_tests_only, --no-run_tests_only + only proceed with running tests +``` \ No newline at end of file diff --git a/tests/config/__init__.py b/tests/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/node_registry.json b/tests/config/node_registry.json new file mode 100644 index 00000000..08c4e27b --- /dev/null +++ b/tests/config/node_registry.json @@ -0,0 +1,5 @@ +{ + "quorum": [4, 5, 6, 7, 8], + "rbt_transfer": [9, 10], + "bip39_nlss": [11, 12] +} \ No newline at end of file diff --git a/tests/config/utils.py b/tests/config/utils.py new file mode 100644 index 00000000..af10b5cb --- /dev/null +++ b/tests/config/utils.py @@ -0,0 +1,39 @@ +import json +import os + +def get_node_registry(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + node_registry_path = os.path.join(current_dir, "node_registry.json") + config = load_from_config_file(node_registry_path) + + if config == {}: + raise Exception("node registry config is empty") + return config + +def load_from_config_file(config_file_path): + try: + with open(config_file_path, 'r') as file: + config_data = json.load(file) + return config_data + except FileNotFoundError as e: + return {} + except json.JSONDecodeError as e: + raise ValueError(f"Error: The file at {config_file_path} is not a valid JSON file.") from e + except Exception as e: + raise Exception(f"An unexpected error occurred: {e}") from e + +def save_to_config_file(config_file_path, config): + try: + if os.path.exists(config_file_path): + os.remove(config_file_path) + + with open(config_file_path, 'w') as f: + json.dump(config, f, indent=4) + except FileNotFoundError as e: + raise FileNotFoundError(f"Error: The file at {config_file_path} could not be found.") from e + except PermissionError as e: + raise PermissionError(f"Error: Permission denied when trying to write to {config_file_path}.") from e + except TypeError as e: # JSON serialization errors raise TypeError, not JSONDecodeError + raise TypeError(f"Error: Failed to serialize the config data to JSON.") from e + except Exception as e: + raise Exception(f"An unexpected error occurred: {e}") from e diff --git a/tests/docker/ubuntu_test.Dockerfile b/tests/docker/ubuntu_test.Dockerfile new file mode 100644 index 00000000..8ba1dd2c --- /dev/null +++ b/tests/docker/ubuntu_test.Dockerfile @@ -0,0 +1,18 @@ +FROM amd64/ubuntu + +# Install python3 and requests dependency +RUN apt-get update && apt-get install -y python3 python3-requests wget build-essential tmux + +# Install golang 1.21 +RUN wget https://go.dev/dl/go1.21.10.linux-amd64.tar.gz +RUN tar -C /usr/local -xzf go1.21.10.linux-amd64.tar.gz +ENV PATH="/usr/local/go/bin:${PATH}" + +# Copy the tests directory +COPY . /usr/node/ + +# Change directory to test +WORKDIR /usr/node/tests + +# Run tests +CMD ["python3", "-u", "run.py"] diff --git a/tests/fixtures/didimage.png.file b/tests/fixtures/didimage.png.file new file mode 100644 index 00000000..09bf52e0 Binary files /dev/null and b/tests/fixtures/didimage.png.file differ diff --git a/tests/fixtures/testswarm_linux.key b/tests/fixtures/testswarm_linux.key new file mode 100644 index 00000000..534e7acb --- /dev/null +++ b/tests/fixtures/testswarm_linux.key @@ -0,0 +1,3 @@ +/key/swarm/psk/1.0.0/ +/base16/ +278b9a199c43fa84178920bd9f5cbcd69e933ddf02a8f69e47a3ea5a1705513f \ No newline at end of file diff --git a/tests/fixtures/testswarm_mac.key b/tests/fixtures/testswarm_mac.key new file mode 100644 index 00000000..7490ebe9 --- /dev/null +++ b/tests/fixtures/testswarm_mac.key @@ -0,0 +1,3 @@ +/key/swarm/psk/1.0.0/ +/base16/ +e105d6caa191dc76ff553d36d094fafb2ad1c646000a52ebc89b488e17f61f92 \ No newline at end of file diff --git a/tests/fixtures/testswarm_windows.key b/tests/fixtures/testswarm_windows.key new file mode 100644 index 00000000..7490ebe9 --- /dev/null +++ b/tests/fixtures/testswarm_windows.key @@ -0,0 +1,3 @@ +/key/swarm/psk/1.0.0/ +/base16/ +e105d6caa191dc76ff553d36d094fafb2ad1c646000a52ebc89b488e17f61f92 \ No newline at end of file diff --git a/tests/helper/__init__.py b/tests/helper/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/helper/utils.py b/tests/helper/utils.py new file mode 100644 index 00000000..d0732faa --- /dev/null +++ b/tests/helper/utils.py @@ -0,0 +1,16 @@ +def expect_success(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except: + raise Exception("The transaction/action was expected to pass, but it failed") + return wrapper + +def expect_failure(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + raise Exception("The transaction/action was expected to fail, but it passed") + except: + return 0 + return wrapper \ No newline at end of file diff --git a/tests/node/__init__.py b/tests/node/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/node/actions.py b/tests/node/actions.py new file mode 100644 index 00000000..629e253d --- /dev/null +++ b/tests/node/actions.py @@ -0,0 +1,109 @@ +from .commands import cmd_run_rubix_servers, cmd_get_peer_id, cmd_create_did, cmd_register_did, \ + cmd_generate_rbt, cmd_add_quorum_dids, cmd_setup_quorum_dids, cmd_rbt_transfer, get_build_dir +from .utils import get_node_name_from_idx, get_did_by_alias +from config.utils import save_to_config_file, get_node_registry + +def add_quorums(node_config: dict): + for config in node_config.values(): + cmd_add_quorum_dids( + config["server"], + config["grpcPort"] + ) + +def setup_quorums(node_config: dict, node_did_alias_map: dict): + for node, config in node_config.items(): + did = get_did_by_alias(config, node_did_alias_map[node]) + cmd_setup_quorum_dids( + did, + config["server"], + config["grpcPort"] + ) + +def quorum_config(node_config: dict, node_did_alias_map: dict, skip_adding_quorums: bool = False): + # Prepare quorumlist.json + quorum_list = [] + build_dir = get_build_dir() + quorum_list_file_path = f"../{build_dir}/quorumlist.json" + + if skip_adding_quorums: + setup_quorums(node_config, node_did_alias_map) + else: + for node, config in node_config.items(): + did = get_did_by_alias(config, node_did_alias_map[node]) + quorum_info = { + "type": 2, + "address": config["peerId"] + "." + did + } + + quorum_list.append(quorum_info) + + save_to_config_file(quorum_list_file_path, quorum_list) + + add_quorums(node_config) + + setup_quorums(node_config, node_did_alias_map) + + +def setup_rubix_nodes(node_registry_config_key): + if node_registry_config_key == "": + raise Exception("a key is needed to fetch node_registry.json config") + + node_registry = get_node_registry() + if not node_registry_config_key in node_registry: + raise Exception(f"config key {node_registry_config_key} not found in node_registry.json config") + + node_indices = node_registry[node_registry_config_key] + + if not isinstance(node_indices, list): + raise Exception(f"the correspoding value for {node_registry_config_key} in node_registry.json must of List type") + + if len(node_indices) == 0: + raise Exception(f"no indices found for {node_registry_config_key} in node_registry.json, provide at least one index") + + node_config = {} + + for idx in node_indices: + node_name = "node" + str(idx) + node_server, grpc_server = cmd_run_rubix_servers(node_name, idx) + + cfg = { + "dids": {}, + "server": node_server, + "grpcPort": grpc_server, + "peerId": "" + } + + fetch_peer_id(cfg) + node_config[node_name] = cfg + + return node_config + +def fetch_peer_id(config): + peer_id = cmd_get_peer_id(config["server"], config["grpcPort"]) + config["peerId"] = peer_id + +def create_and_register_did(config: dict, did_alias: str, did_type: int = 4, register_did: bool = True): + did = cmd_create_did(config["server"], config["grpcPort"], did_type) + print(f"DID {did} has been created successfully") + + config["dids"] = { + did_alias: did + } + + if register_did: + cmd_register_did(did, config["server"], config["grpcPort"]) + print(f"DID {did} has been registered successfully") + + return did + +def fund_did_with_rbt(node_config: dict, did: str, rbt_amount: int = 70): + cmd_generate_rbt(did, rbt_amount, node_config["server"], node_config["grpcPort"]) + print("DID ", did, f" is funded with {rbt_amount} RBT") + +def rbt_transfer( + sender_address: str, + receiver_address: str, + transfer_rbt: float, + sender_server_port: int, + sender_grpc_port: int): + cmd_rbt_transfer(sender_address, receiver_address, transfer_rbt, sender_server_port, sender_grpc_port) diff --git a/tests/node/commands.py b/tests/node/commands.py new file mode 100644 index 00000000..999f316c --- /dev/null +++ b/tests/node/commands.py @@ -0,0 +1,217 @@ +import subprocess +import os +import re +import platform +import time +import requests +from .utils import get_base_ports + +def is_windows_os(): + os_name = platform.system() + return os_name == "Windows" + +def get_build_dir(): + os_name = platform.system() + build_folder = "" + if os_name == "Linux": + build_folder = "linux" + elif os_name == "Windows": + build_folder = "windows" + elif os_name == "Darwin": + build_folder = "mac" + + return build_folder + +def run_command(cmd_string, is_output_from_stderr=False): + assert isinstance(cmd_string, str), "command must be of string type" + cmd_result = subprocess.run(cmd_string, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + code = cmd_result.returncode + + if int(code) != 0: + err_output = cmd_result.stderr.decode('utf-8')[:-1] + print(err_output) + return err_output, int(code) + + output = "" + if not is_output_from_stderr: + output = cmd_result.stdout.decode('utf-8')[:-1] + print(output) + if output.find('[ERROR]') > 0 or output.find('parse error') > 0: + return output, 1 + else: + return output, code + else: + output = cmd_result.stderr.decode('utf-8')[:-1] + if output.find('[ERROR]') > 0 or output.find('parse error') > 0: + print(output) + return output, 1 + else: + return output, code + +def cmd_run_rubix_servers(node_name, server_port_idx): + os.chdir("../" + get_build_dir()) + + base_node_server, base_grpc_port = get_base_ports() + grpc_port = base_grpc_port + server_port_idx + node_server = base_node_server + server_port_idx + + cmd_string = "" + if is_windows_os(): + cmd_string = f"powershell -Command Start-Process -FilePath '.\\rubixgoplatform.exe' -ArgumentList 'run -p {node_name} -n {server_port_idx} -s -testNet -grpcPort {grpc_port}' -WindowStyle Hidden" + else: + cmd_string = f"tmux new -s {node_name} -d ./rubixgoplatform run -p {node_name} -n {server_port_idx} -s -testNet -grpcPort {grpc_port}" + + _, code = run_command(cmd_string) + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + print("Waiting for 40 seconds before checking if its running....") + time.sleep(40) + try: + check_if_nodes_is_running(server_port_idx) + except Exception as e: + raise e + + os.chdir("../tests") + return node_server, grpc_port + +def check_if_nodes_is_running(server_idx): + base_server, _ = get_base_ports() + port = base_server + int(server_idx) + print(f"Check if server with ENS web server port {port} is running...") + url = f"http://localhost:{port}/api/getalldid" + try: + print(f"Sending GET request to URL: {url}") + response = requests.get(url) + if response.status_code == 200: + print(f"Server with port {port} is running successfully") + else: + raise Exception(f"Failed with Status Code: {response.status_code} | Server with port {port} is NOT running successfully") + except: + raise Exception(f"ConnectionError | Server with port {port} is NOT running successfully") + +def cmd_create_did(server_port, grpc_port, did_type = 4): + os.chdir("../" + get_build_dir()) + + cmd_string = f"./rubixgoplatform createdid -port {server_port} -grpcPort {grpc_port} -didType {did_type}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform createdid -port {server_port} -grpcPort {grpc_port} -didType {did_type}" + output, code = run_command(cmd_string, True) + print(output) + + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + did_id = "" + if "successfully" in output: + pattern = r'bafybmi\w+' + matches = re.findall(pattern, output) + if matches: + did_id = matches[0] + else: + raise Exception("unable to extract DID ID") + + os.chdir("../tests") + return did_id + +def cmd_register_did(did_id, server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform registerdid -did {did_id} -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform registerdid -did {did_id} -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string, True) + print(output) + + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + os.chdir("../tests") + return output + +def cmd_generate_rbt(did_id, numTokens, server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform generatetestrbt -did {did_id} -numTokens {numTokens} -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform generatetestrbt -did {did_id} -numTokens {numTokens} -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string, True) + + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + os.chdir("../tests") + return output + +def cmd_add_quorum_dids(server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform addquorum -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform addquorum -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string, True) + print(output) + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + os.chdir("../tests") + return output + +def cmd_shutdown_node(server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform shutdown -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform shutdown -port {server_port} -grpcPort {grpc_port}" + output, _ = run_command(cmd_string, True) + print(output) + + os.chdir("../tests") + return output + +def cmd_setup_quorum_dids(did, server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform setupquorum -did {did} -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform setupquorum -did {did} -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string, True) + print(output) + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + os.chdir("../tests") + return output + +def cmd_get_peer_id(server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform get-peer-id -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform get-peer-id -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string) + + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + os.chdir("../tests") + return output + +def check_account_info(did, server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform getaccountinfo -did {did} -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform getaccountinfo -did {did} -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string) + + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + os.chdir("../tests") + return output + +# Note: address != did, address = peerId.didId +def cmd_rbt_transfer(sender_address, receiver_address, rbt_amount, server_port, grpc_port): + os.chdir("../" + get_build_dir()) + cmd_string = f"./rubixgoplatform transferrbt -senderAddr {sender_address} -receiverAddr {receiver_address} -rbtAmount {rbt_amount} -port {server_port} -grpcPort {grpc_port}" + if is_windows_os(): + cmd_string = f".\\rubixgoplatform transferrbt -senderAddr {sender_address} -receiverAddr {receiver_address} -rbtAmount {rbt_amount} -port {server_port} -grpcPort {grpc_port}" + output, code = run_command(cmd_string, True) + print(output) + if code != 0: + raise Exception("Error occurred while run the command: " + cmd_string) + + os.chdir("../tests") + return output \ No newline at end of file diff --git a/tests/node/quorum.py b/tests/node/quorum.py new file mode 100644 index 00000000..cb65ccbd --- /dev/null +++ b/tests/node/quorum.py @@ -0,0 +1,40 @@ +import pprint +import time + +from .actions import setup_rubix_nodes, create_and_register_did, \ + fund_did_with_rbt, quorum_config +from config.utils import save_to_config_file, load_from_config_file + +QUORUM_CONFIG_PATH = "./quorum_config.json" + +def run_quorum_nodes(only_run_nodes, skip_adding_quorums): + print("Running Rubix Quorum nodes......") + node_registry_key = "quorum" + node_config = setup_rubix_nodes(node_registry_key) + print("Rubix Quorum nodes are now running") + + if not only_run_nodes: + did_alias = "did_quorum" + node_did_alias_map = {} + + print("Creating, Registering and Funding Quorum DIDs\n") + for node, config in node_config.items(): + did = create_and_register_did(config, did_alias) + + fund_did_with_rbt(config, did) + + # Selecting DIDs for quorum setup + node_did_alias_map[node] = did_alias + save_to_config_file(QUORUM_CONFIG_PATH, node_config) + print("\nquorum_config.json is created") + + print("Setting up quorums and saving the info in quorumlist.json") + quorum_config(node_config, node_did_alias_map, skip_adding_quorums=skip_adding_quorums) + + pprint.pp(node_config) + print("Quorums have been configured") + else: + quorum_config(node_config, node_did_alias_map, skip_adding_quorums=True) + +def get_quorum_config(): + return load_from_config_file(QUORUM_CONFIG_PATH) diff --git a/tests/node/utils.py b/tests/node/utils.py new file mode 100644 index 00000000..668c1d10 --- /dev/null +++ b/tests/node/utils.py @@ -0,0 +1,17 @@ +import json +import sys +import os +sys.path.insert(1, os.getcwd()) +from .vars import QUORUM_NODES + +def get_node_name_from_idx(idx, prefix_string: str = "node"): + return prefix_string + str(idx) + +def get_base_ports(): + base_ens_server = 20000 + base_grpc_port = 10500 + + return base_ens_server, base_grpc_port + +def get_did_by_alias(node_config, alias): + return node_config["dids"][alias] \ No newline at end of file diff --git a/tests/node/vars.py b/tests/node/vars.py new file mode 100644 index 00000000..1cdf27f6 --- /dev/null +++ b/tests/node/vars.py @@ -0,0 +1,2 @@ +QUORUM_NODES = 5 + diff --git a/tests/pack_node_logs.py b/tests/pack_node_logs.py new file mode 100644 index 00000000..afe56433 --- /dev/null +++ b/tests/pack_node_logs.py @@ -0,0 +1,33 @@ +# This script packs all the quorum and non-quorum node logs in `tests/node_logs` + +import os +import shutil + +def collect_logs(base_dir, output_dir): + # Ensure the output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Iterate over files and directories in the build directory + for env in ['linux', 'windows', 'mac']: + env_path = os.path.join(base_dir, env) + if os.path.isdir(env_path): + for item in os.listdir(env_path): + item_path = os.path.join(env_path, item) + # Check if the directory name starts with `node`. + if os.path.isdir(item_path) and item.startswith('node'): + log_file_path = os.path.join(item_path, 'log.txt') + if os.path.exists(log_file_path): + # Rename the log file in `log__.txt` + new_log_name = f'log_{env}_{item}.txt' + new_log_path = os.path.join(output_dir, new_log_name) + # Copy the log file to the output directory + shutil.copyfile(log_file_path, new_log_path) + print(f'Copied {new_log_name} to {output_dir}') + else: + print(f'log.txt not found in {item_path}') + +if __name__ == "__main__": + base_directory = '..' # Parent directory of 'tests' where build directory is located + output_directory = './node_logs' # Directory to store collected logs + collect_logs(base_directory, output_directory) + print(f'All logs collected into {output_directory}') \ No newline at end of file diff --git a/tests/run.py b/tests/run.py new file mode 100644 index 00000000..67f3705f --- /dev/null +++ b/tests/run.py @@ -0,0 +1,171 @@ +import platform +import os +import shutil +import requests +import argparse +from node.commands import run_command +from node.quorum import run_quorum_nodes + +from scenarios import ( + rbt_transfer, + bip39_nlss_test +) + +IPFS_KUBO_VERSION = "v0.21.0" + +def get_os_info(): + os_name = platform.system() + build_folder = "" + + if os_name == "Linux": + build_folder = "linux" + elif os_name == "Windows": + build_folder = "windows" + elif os_name == "Darwin": + build_folder = "mac" + else: + print("Unsupported operating system to build Rubix") + return None, None + + return os_name, build_folder + +def download_ipfs_binary(os_name, version, build_dir): + download_url = "" + + if os_name == "Linux": + download_url = f"https://dist.ipfs.tech/kubo/{version}/kubo_{version}_linux-amd64.tar.gz" + elif os_name == "Windows": + download_url = f"https://dist.ipfs.tech/kubo/{version}/kubo_{version}_windows-amd64.zip" + elif os_name == "Darwin": # MacOS + download_url = f"https://dist.ipfs.tech/kubo/{version}/kubo_{version}_darwin-amd64.tar.gz" + else: + raise ValueError("Unsupported operating system") + + # Download the IPFS binary archive + download_path = f"kubo_{version}_{os_name.lower()}-amd64.tar.gz" if os_name != "Windows" else f"kubo_{version}_{os_name.lower()}-amd64.zip" + print("Downloading IPFS binary...") + response = requests.get(download_url) + with open(download_path, "wb") as f: + f.write(response.content) + print("Download completed.") + + # Extract the archive + print("Extracting IPFS binary...") + if os_name == "Windows": + # For Windows, we need to use the 'zipfile' module to extract + import zipfile + with zipfile.ZipFile(download_path, "r") as zip_ref: + zip_ref.extractall("kubo") + else: + # For Linux and MacOS, we use tar + import tarfile + with tarfile.open(download_path, "r:gz" if os_name != "Darwin" else "r") as tar_ref: + tar_ref.extractall("kubo") + print("Extraction completed.") + + # Check the contents of the kubo directory + print("Contents of kubo directory:") + for item in os.listdir("kubo"): + print(item) + + # Move IPFS binary to the appropriate folder + print("Moving IPFS binary...") + + ipfs_bin_name = "ipfs" + if os_name == "Windows": + ipfs_bin_name = "ipfs.exe" + + src_file = os.path.join("kubo", "kubo", ipfs_bin_name) + dest_dir = os.path.join(build_dir, ipfs_bin_name) + if os.path.exists(src_file): + shutil.move(src_file, dest_dir) + print("IPFS binary moved to", dest_dir) + + # Check if the file is present at the destination + dest_file = os.path.join(dest_dir) + if not os.path.exists(dest_file): + raise FileNotFoundError("IPFS binary not found at the destination after move operation.") + else: + raise FileNotFoundError("Installed IPFS binary file does not exist.") + + # Clean up + os.remove(download_path) + shutil.rmtree("kubo") + print("\nIPFS has been installed succesfully.") + +def copy_fixtures_to_build_dir(build_directory): + fixtures_directory = os.path.join("tests", "fixtures") + + # Copy didimage.png.file + image_file_src = os.path.join(fixtures_directory, "didimage.png.file") + image_file_dest = os.path.join(build_directory, "image.png") + shutil.copyfile(image_file_src, image_file_dest) + + if not os.path.exists(image_file_dest): + raise FileNotFoundError(f"Copy operation for didimage.png.file failed. Destination file not found: {image_file_dest}") + + # Copy testswarm.key + swarmkey_src = os.path.join(fixtures_directory, f"testswarm_{build_directory}.key") + swarmkey_dest = os.path.join(build_directory, f"testswarm.key") + shutil.copyfile(swarmkey_src, swarmkey_dest) + + if not os.path.exists(swarmkey_dest): + raise FileNotFoundError(f"Copy operation for testswarm_{build_directory}.key failed. Destination file not found: {swarmkey_dest}") + + print("\nimage.png and swarm key have been added to build directory successfully") + +def cli(): + parser = argparse.ArgumentParser(description="CLI to run tests for Rubix") + parser.add_argument("--skip_prerequisite", action=argparse.BooleanOptionalAction, help="skip prerequisite steps such as installing IPFS and building Rubix") + parser.add_argument("--run_nodes_only", action=argparse.BooleanOptionalAction, help="only run the rubix nodes and skip the setup") + parser.add_argument("--skip_adding_quorums", action=argparse.BooleanOptionalAction, help="skips adding quorums") + parser.add_argument("--run_tests_only", action=argparse.BooleanOptionalAction, help="only proceed with running tests") + return parser.parse_args() + +if __name__=='__main__': + args = cli() + skip_prerequisite = args.skip_prerequisite + run_nodes_only = args.run_nodes_only + skip_adding_quorums = args.skip_adding_quorums + run_tests_only = args.run_tests_only + + non_quorum_node_config = {} + + if not run_tests_only: + os_name, build_folder = get_os_info() + if os_name is None: + exit(1) + + if not skip_prerequisite: + os.chdir("../") + print(f"Building Rubix binary for {os_name}\n") + build_command = "" + if os_name == "Linux": + build_command = "make compile-linux" + elif os_name == "Windows": + build_command = "make compile-windows" + elif os_name == "Darwin": + build_command = "make compile-mac" + + output, code = run_command(build_command) + if code != 0: + print("build failed with error:", output) + exit(1) + else: + print("\nBuild successful\n") + + download_ipfs_binary(os_name, IPFS_KUBO_VERSION, build_folder) + copy_fixtures_to_build_dir(build_folder) + os.chdir("./tests") + + run_quorum_nodes(run_nodes_only, skip_adding_quorums=skip_adding_quorums) + + # It will carry list of Python files containing the function `run()` + # that consists of logic to run all the necessary tests + modules = [ + rbt_transfer, + bip39_nlss_test + ] + + for module in modules: + module.run() diff --git a/tests/scenarios/__init__.py b/tests/scenarios/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/scenarios/bip39_nlss_test.py b/tests/scenarios/bip39_nlss_test.py new file mode 100644 index 00000000..74aa300c --- /dev/null +++ b/tests/scenarios/bip39_nlss_test.py @@ -0,0 +1,89 @@ +from node.actions import rbt_transfer, fund_did_with_rbt, setup_rubix_nodes, \ + create_and_register_did, add_quorums +from node.utils import get_did_by_alias +from config.utils import save_to_config_file, load_from_config_file +from helper.utils import expect_failure, expect_success + +__node_config_path = "./bip39_nlss_config.json" + +def setup(): + print("Setting up test.....") + print("Configuring and running node11 and node12...") + + node_config = setup_rubix_nodes("bip39_nlss") + + config_bip39 = node_config["node11"] + config_nlss = node_config["node12"] + + create_and_register_did(config_bip39, "bip39_1", did_type=4) + + + create_and_register_did(config_nlss, "nlss_1", did_type=0) + + save_to_config_file(__node_config_path, node_config) + + print("Adding quorums") + add_quorums(node_config) + + print("Setup Done\n") + return node_config + +def run(skip_setup: bool = False): + print("\n----------- 2. Running Tests related to RBT Transfer between BIP39 and NLSS dids -----------\n") + node_config = {} + + if not skip_setup: + node_config = setup() + else: + node_config = load_from_config_file(__node_config_path) + + nlss_to_bip39(node_config) + bip39_to_nlss(node_config) + print("\n-------------- Tests Completed -------------------\n") + +def nlss_to_bip39(node_config): + node_bip39, node_nlss = node_config["node11"], node_config["node12"] + server_port_nlss, grpc_port_nlss = node_nlss["server"], node_nlss["grpcPort"] + did_bip39, did_nlss = get_did_by_alias(node_bip39, "bip39_1"), get_did_by_alias(node_nlss, "nlss_1") + address_bip39, address_nlss = node_bip39["peerId"]+"."+did_bip39, node_nlss["peerId"]+"."+did_nlss + + print("------ Test Case (PASS): Transferring whole, part and mix RBT from NLSS DID to BIP39 DID ------\n") + + print("\n1. Generating 3 RBT for NLSS DID") + expect_success(fund_did_with_rbt)(node_nlss, did_nlss, 3) + print("Funded NLSS DID with 3 RBT") + + print("\n2. Transferring 1 RBT from NLSS DID to BIP39 DID....") + expect_success(rbt_transfer)(address_nlss, address_bip39, 1, server_port_nlss, grpc_port_nlss) + print("Transferred 1 RBT from NLSS DID to BIP39 DID") + + print("\n3. Transferring 1.5 RBT from NLSS DID to BIP39 DID....") + expect_success(rbt_transfer)(address_nlss, address_bip39, 1.5, server_port_nlss, grpc_port_nlss) + print("Transferred 1.5 RBT from NLSS DID to BIP39 DID") + + print("\n4. Transferring 0.5 RBT from NLSS DID to BIP39 DID....") + expect_success(rbt_transfer)(address_nlss, address_bip39, 0.5, server_port_nlss, grpc_port_nlss) + print("Transferred 0.5 RBT from NLSS DID to BIP39 DID") + + print("\n------ Test Case (PASS): Transferring whole, part and mix RBT from NLSS DID to BIP39 DID completed ------\n") + +def bip39_to_nlss(node_config): + node_bip39, node_nlss = node_config["node11"], node_config["node12"] + server_port_bip39, grpc_port_bip39 = node_bip39["server"], node_bip39["grpcPort"] + did_bip39, did_nlss = get_did_by_alias(node_bip39, "bip39_1"), get_did_by_alias(node_nlss, "nlss_1") + address_bip39, address_nlss = node_bip39["peerId"]+"."+did_bip39, node_nlss["peerId"]+"."+did_nlss + + print("------ Test Case (PASS): Transferring whole, part and mix RBT from BIP39 DID to NLSS DID ------\n") + + print("\n4. Transferring 0.5 RBT from BIP39 DID to NLSS DID....") + expect_success(rbt_transfer)(address_bip39, address_nlss, 0.5, server_port_bip39, grpc_port_bip39) + print("Transferred 0.5 RBT from BIP39 DID to NLSS DID") + + print("\n3. Transferring 1.5 RBT from BIP39 DID to NLSS DID....") + expect_success(rbt_transfer)(address_bip39, address_nlss, 1.5, server_port_bip39, grpc_port_bip39) + print("Transferred 1.5 RBT from BIP39 DID to NLSS DID") + + print("\n2. Transferring 1 RBT from BIP39 DID to NLSS DID....") + expect_success(rbt_transfer)(address_bip39, address_nlss, 1, server_port_bip39, grpc_port_bip39) + print("Transferred 1 RBT from BIP39 DID to NLSS DID") + diff --git a/tests/scenarios/rbt_transfer.py b/tests/scenarios/rbt_transfer.py new file mode 100644 index 00000000..7f5c2da0 --- /dev/null +++ b/tests/scenarios/rbt_transfer.py @@ -0,0 +1,135 @@ +from node.actions import rbt_transfer, fund_did_with_rbt, setup_rubix_nodes, \ + create_and_register_did, add_quorums +from node.utils import get_did_by_alias +from config.utils import save_to_config_file, load_from_config_file +from helper.utils import expect_failure, expect_success + +__node_config_path = "./rbt_transfer_config.json" + +def setup(): + print("Setting up test.....") + print("Configuring and running node9 and node10...") + + node_config = setup_rubix_nodes("rbt_transfer") + + config_A = node_config["node9"] + config_B = node_config["node10"] + + create_and_register_did(config_A, "did_a") + create_and_register_did(config_B, "did_b") + + save_to_config_file(__node_config_path, node_config) + + print("Adding quorums") + add_quorums(node_config) + + print("Setup Done\n") + return node_config + +def run(skip_setup: bool = False): + print("\n----------- 1. Running Tests related to RBT Transfer -----------\n") + node_config = {} + + # In some cases, we may wish to run tests for an existing test configuration + # where the nodes are running already. If skip_setup is True, the setup steps + # are skipped and we proceed to directly run the test cases and load the config + # from the config file + if not skip_setup: + node_config = setup() + else: + node_config = load_from_config_file(__node_config_path) + + shuttle_transfer(node_config) + insufficient_balance_transfer(node_config) + max_decimal_place_transfer(node_config) + + print("\n-------------- Tests Completed -------------------\n") + +def max_decimal_place_transfer(config): + node_A_info, node_B_info = config["node9"], config["node10"] + server_port_B, grpc_port_B = node_B_info["server"], node_B_info["grpcPort"] + did_A, did_B = get_did_by_alias(node_A_info, "did_a"), get_did_by_alias(node_B_info, "did_b") + address_A, address_B = node_A_info["peerId"]+"."+did_A, node_B_info["peerId"]+"."+did_B + + print("------ Test Case (FAIL) : Transferring 0.00000009 RBT from B which is more than allowed decimal places ------") + + print("\nTransferring 0.00000009 RBT from B to A....") + expect_failure(rbt_transfer)(address_B, address_A, 0.00000009, server_port_B, grpc_port_B) + + print("\n------ Test Case (FAIL) : Transferring 0.00000009 RBT from B which is more than allowed decimal places completed ------\n") + +def insufficient_balance_transfer(config): + node_A_info, node_B_info = config["node9"], config["node10"] + server_port_A, grpc_port_A = node_A_info["server"], node_A_info["grpcPort"] + server_port_B, grpc_port_B = node_B_info["server"], node_B_info["grpcPort"] + did_A, did_B = get_did_by_alias(node_A_info, "did_a"), get_did_by_alias(node_B_info, "did_b") + address_A, address_B = node_A_info["peerId"]+"."+did_A, node_B_info["peerId"]+"."+did_B + + print("\n------ Test Case (FAIL) : Transferring 100 RBT from A which has zero balance ------") + + print("\nTransferring 100 RBT from A to B....") + expect_failure(rbt_transfer)(address_A, address_B, 100, server_port_A, grpc_port_A) + + print("\n------ Test Case (FAIL) : Transferring 100 RBT from A which has zero balance completed ------\n") + + print("\n------ Test Case (FAIL) : Transferring 100 RBT from B which has insufficient balance ------") + + print("\nTransferring 100 RBT from B to A....") + expect_failure(rbt_transfer)(address_B, address_A, 0.25, server_port_B, grpc_port_B) + + print("\n------ Test Case (FAIL) : Transferring 100 RBT from B which has insufficient balance completed ------\n") + +def shuttle_transfer(config): + node_A_info, node_B_info = config["node9"], config["node10"] + server_port_A, grpc_port_A = node_A_info["server"], node_A_info["grpcPort"] + server_port_B, grpc_port_B = node_B_info["server"], node_B_info["grpcPort"] + did_A, did_B = get_did_by_alias(node_A_info, "did_a"), get_did_by_alias(node_B_info, "did_b") + address_A, address_B = node_A_info["peerId"]+"."+did_A, node_B_info["peerId"]+"."+did_B + + print("------ Test Case (PASS): Shuttle transfer started ------\n") + + print("\n1. Generating 2 whole RBT for A") + expect_success(fund_did_with_rbt)(node_A_info, did_A, 2) + print("Funded node A with 2 RBT") + + print("\n2. Transferring 0.5 RBT from A to B....") + expect_success(rbt_transfer)(address_A, address_B, 0.5, server_port_A, grpc_port_A) + print("Transferred 0.5 RBT from A to B") + + print("\n3. Transferring 1.499 RBT from A to B....") + expect_success(rbt_transfer)(address_A, address_B, 1.499, server_port_A, grpc_port_A) + print("Transferred 1.499 RBT from A to B") + + print("\n4. Transferring 0.25 RBT from B to A....") + expect_success(rbt_transfer)(address_B, address_A, 0.25, server_port_B, grpc_port_B) + print("Transferred 0.25 RBT from B to A") + + print("\n5. Transferring 0.25 RBT from B to A....") + expect_success(rbt_transfer)(address_B, address_A, 0.25, server_port_B, grpc_port_B) + print("Transferred 0.25 RBT from B to A") + + print("\n6. Transferring 0.25 RBT from B to A....") + expect_success(rbt_transfer)(address_B, address_A, 0.25, server_port_B, grpc_port_B) + print("Transferred 0.25 RBT from B to A") + + print("\n7. Transferring 0.25 RBT from B to A....") + expect_success(rbt_transfer)(address_B, address_A, 0.25, server_port_B, grpc_port_B) + print("Transferred 0.25 RBT from B to A") + + print("\n8. Transferring 1 RBT from A to B....") + expect_success(rbt_transfer)(address_A, address_B, 1, server_port_A, grpc_port_A) + print("Transferred 1 RBT from A to B") + + print("\n9. Generating 2 whole RBT for A") + expect_success(fund_did_with_rbt)(node_A_info, did_A, 2) + print("Funded node A with 2 RBT") + + print("\n10. Transferring 2 RBT from A to B....") + expect_success(rbt_transfer)(address_A, address_B, 2, server_port_A, grpc_port_A) + print("Transferred 2 RBT from A to B") + + print("\n11. Transferring 0.001 RBT from A to B....") + expect_success(rbt_transfer)(address_A, address_B, 0.001, server_port_A, grpc_port_A) + print("Transferred 0.001 RBT from A to B") + + print("\n------ Test Case (PASS): Shuttle transfer completed ------\n") diff --git a/tests/shutdown.py b/tests/shutdown.py new file mode 100644 index 00000000..8d96c103 --- /dev/null +++ b/tests/shutdown.py @@ -0,0 +1,14 @@ +from node.commands import cmd_shutdown_node +from node.utils import get_base_ports +from config.utils import get_node_registry + +if __name__=='__main__': + base_node_server, base_grpc_server = get_base_ports() + node_registry_config = get_node_registry() + + for indices in node_registry_config.values(): + for i in indices: + server_port = base_node_server + i + grpc_port = base_grpc_server + i + print(f"Shutting down server running at {server_port}") + cmd_shutdown_node(server_port, grpc_port) diff --git a/wrapper/ensweb/client.go b/wrapper/ensweb/client.go index bb996c91..31571457 100644 --- a/wrapper/ensweb/client.go +++ b/wrapper/ensweb/client.go @@ -128,6 +128,30 @@ func (c *Client) JSONRequest(method string, requestPath string, model interface{ return req, err } +func (c *Client) JSONRequestForExplorer(method string, requestPath string, model interface{}, explorerURL string) (*http.Request, error) { + var body *bytes.Buffer + if model != nil { + j, err := json.Marshal(model) + if err != nil { + return nil, err + } + body = bytes.NewBuffer(j) + } else { + body = bytes.NewBuffer(make([]byte, 0)) + } + + url := explorerURL + requestPath + + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + return req, err +} + func (c *Client) MultiFormRequest(method string, requestPath string, field map[string]string, files map[string]string) (*http.Request, error) { var b bytes.Buffer w := multipart.NewWriter(&b)